├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .gitlab-ci.yml ├── .gitlab ├── issue_templates │ ├── Bug.md │ └── Default.md └── merge_request_templates │ ├── Feature.md │ └── Fix.md ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .solcover.js ├── .soliumignore ├── .soliumrc.json ├── CHANGELOG.md ├── Dockerfile ├── README.md ├── config └── deployed-contracts.js ├── contracts ├── AccessToken.sol ├── BrickblockAccount.sol ├── BrickblockToken.sol ├── ContractRegistry.sol ├── EmployeeTokenSalaryPayout.sol ├── ExchangeRateProvider.sol ├── ExchangeRates.sol ├── FeeManager.sol ├── Migrations.sol ├── PoaCommon.sol ├── PoaCrowdsale.sol ├── PoaLogger.sol ├── PoaManager.sol ├── PoaProxy.sol ├── PoaProxyCommon.sol ├── PoaToken.sol ├── Whitelist.sol ├── external │ ├── OraclizeAPI.sol │ └── SafeMathPower.sol ├── interfaces │ ├── IAccessToken.sol │ ├── IBrickblockToken.sol │ ├── IExchangeRateProvider.sol │ ├── IExchangeRates.sol │ ├── IFeeManager.sol │ ├── IPoaCrowdsale.sol │ ├── IPoaManager.sol │ ├── IPoaToken.sol │ ├── IPoaTokenCrowdsale.sol │ ├── IRegistry.sol │ └── IWhitelist.sol ├── stubs │ ├── BrickblockFountainStub.sol │ ├── BrokenRemoteContractStub.sol │ ├── ExchangeRateProviderStub.sol │ ├── RemoteContractStub.sol │ ├── RemoteContractUserStub.sol │ └── UpgradedPoa.sol └── utils │ ├── AccessTokenUpgradeExample.sol │ ├── PoaCheckForFunctionCollision.sol │ └── WarpTool.sol ├── docs ├── ACTORS_PROCESSES.md ├── ECOSYSTEM.md ├── POA-FUNDING-FLOW.md ├── TEST-AGAINST-GETH.md ├── UPGRADING-POA-TOKEN.md ├── WORST-CASE-SCENARIOS.md ├── architecture │ ├── architecture.png │ └── architecture.xml ├── audits │ ├── 2017-12-01 - BrickblockToken (BBK) - Audit by SmartDec v1.pdf │ ├── 2018-01-17 - BrickblockToken (BBK) - Audit by SmartDec v2.pdf │ ├── 2018-01-30 - BrickblockToken (BBK) - Audit by SmartDec v3.pdf │ ├── 2018-03 - Full Ecosystem [Phase 1] - Audit by ConsenSys v1.pdf │ ├── 2018-03 - Full Ecosystem [Phase 1] - Audit by ConsenSys v2.pdf │ └── 2018-09-20 - Full Ecosystem [Phase 2] - Audit by ConsenSys final.pdf ├── bug-bounties │ └── 2018-09-18 - Full Ecosystem.md ├── ecosystem-diagram.png └── sequence-diagrams │ ├── BBK-ACT-interaction.mmd │ ├── POA-Broker-interaction.mmd │ ├── POA-Custodian-interaction.mmd │ ├── POA-Investor-funding-fails.mmd │ ├── POA-Investor-interaction.mmd │ ├── legend.mmd │ └── render.js ├── e2e-tooling ├── INFO.md ├── bootstrap.js ├── deploy-poa-in-every-stage.js └── setup-smart-contract-ecosystem.js ├── frozen ├── contracts │ ├── BrickblockAccount.sol │ ├── BrickblockToken.sol │ ├── ContractRegistry.sol │ ├── CustomPOAToken.sol │ ├── stubs │ │ └── BrickblockFountainStub.sol │ └── tools │ │ └── WarpTool.sol ├── migrations │ └── .git-empty-folder ├── test │ ├── helpers │ │ ├── bat.js │ │ ├── cpoa.js │ │ ├── general.js │ │ └── stress │ │ │ └── cpoa.js │ ├── main-tests │ │ ├── BrickblockAccount.js │ │ ├── BrickblockToken.js │ │ ├── ContractRegistry.js │ │ └── CustomPOAToken.js │ └── stress-tests │ │ └── CustomPOAToken.js └── truffle.js ├── helpers ├── general.js ├── index.js └── time-travel.js ├── migrations ├── 0_init.js ├── 1_deploy_contracts.js ├── actions │ ├── deployment-actions.js │ ├── index.js │ └── poa-actions.js └── helpers │ ├── arguments.js │ ├── bbk.js │ ├── constants.js │ ├── deployment.js │ ├── exchange-rates.js │ ├── general.js │ ├── index.js │ ├── owner-management.js │ ├── poa-manager.js │ ├── registry.js │ ├── statistics.js │ └── whitelist.js ├── package.json ├── private-chain-data └── keystore │ ├── UTC--2018-06-05T14-09-55.076603042Z--0aff697345acfe4224ded0732dd4b8ad65ac5268 │ ├── UTC--2018-06-05T14-10-32.107340526Z--b95e24b6b29d2d216ef35baf06093bdcb5d9ef9a │ ├── UTC--2018-06-06T07-55-37.120385883Z--df5255f6bbabb6e91e5b7c9177602cf6de7e1485 │ ├── UTC--2018-06-06T07-56-45.763078036Z--7dcdf74cad9a90692a9d24121ff61a216f528ff5 │ ├── UTC--2018-06-06T07-57-20.156037626Z--e2b3c5292ea4f6cf53ad822e7c2ff749c0e8dc23 │ ├── UTC--2018-06-06T07-58-16.626381710Z--d4be220860d26bcce32a5e98826a61f10c95794c │ ├── UTC--2018-06-06T07-58-41.965010624Z--47010231863e4171c65aafd173f464a5397409e7 │ ├── UTC--2018-06-06T07-59-10.698813967Z--da6a355dd311e18b35a23de174de9ac8638e532a │ ├── UTC--2018-06-06T07-59-34.745859619Z--25842757534c2ed386f55226852e818a199b82a2 │ ├── UTC--2018-06-06T08-00-01.499176142Z--d9648e66fa7f8b16b3ec39ccca559b0fa334b853 │ └── UTC--2018-06-19T21-54-35.832389126Z--331bded3ee57a580925befca2884c57e4472e4a0 ├── scripts ├── ci-release.sh ├── gasUsage.js ├── geth │ ├── auto-mine.js │ ├── genesis.json │ ├── manage-accounts.js │ └── start-geth.sh ├── healthcheck.sh ├── inject-deployed-contracts.js ├── lib │ └── logger.js ├── post-merge-githook.sh └── test-frozen.js ├── stress-tests ├── AccessToken.js ├── CustomPOAToken.js ├── POA │ ├── st-poa-eth-funding.js │ ├── st-poa-fiat-eth-funding.js │ └── st-poa-fiat-funding.js └── helpers │ └── st-poa-helper.js ├── test ├── .eslintrc ├── helpers │ ├── act.js │ ├── bat.js │ ├── bbk.js │ ├── employeeTokenSalaryPayoutHelper.js │ ├── exr.js │ ├── fmr.js │ ├── general.js │ ├── log.js │ ├── pmr.js │ ├── poa.js │ ├── pxy.js │ ├── storage.js │ └── whitelist.js ├── main-tests │ ├── AccessToken.js │ ├── BrickblockAccount.js │ ├── BrickblockToken.js │ ├── ContractRegistry.js │ ├── EmployeeTokenSalaryPayout.js │ ├── ExchangeRates.js │ ├── FeeManager.js │ ├── PoaLogger.js │ ├── PoaManager.js │ ├── PoaProxy.js │ ├── WarpTool.js │ ├── Whitelist.js │ └── poaTokenProxy │ │ ├── poaActive.js │ │ ├── poaCancelled.js │ │ ├── poaEthFunding.js │ │ ├── poaFailed.js │ │ ├── poaFiatFunding.js │ │ ├── poaFundingSuccessful.js │ │ ├── poaInitialization.js │ │ ├── poaNoStage.js │ │ ├── poaNormalFlow.js │ │ ├── poaPreFunding.js │ │ ├── poaPreview.js │ │ ├── poaScenarios.js │ │ └── poaTerminated.js └── misc │ └── ExchangeRateGasCosts.js ├── truffle.js └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | NODE_PATH=. 2 | LOG_LEVEL=trace 3 | #TESTNET_MNEMONIC= 4 | #MAINNET_MNEMONIC= 5 | #INFURA_API_KEY= 6 | #NEW_OWNER= 7 | 8 | # These addresses are for development purposes only. 9 | # They will override the ones in 'config/deployed-contracts.js' and in Contract Registry too. 10 | # When you want make them persistent please add them (Usually ContractRegistry is enough after registering everything) to 'config/deployed-contracts.js' file 11 | #CONTRACT_REGISTRY=0x17e91224c30c5b0b13ba2ef1e84fe880cb902352 12 | #ACCESS_TOKEN= 13 | #BRICKBLOCK_ACCOUNT= 14 | #BRICKBLOCK_TOKEN= 15 | #POA_LOGGER= 16 | #EXCHANGE_RATE_PROVIDER= 17 | #EXCHANGE_RATES= 18 | #FEE_MANAGER= 19 | #POA_CROWDSALE_MASTER= 20 | #POA_TOKEN_MASTER= 21 | #POA_MANAGER= 22 | #WHITELIST= 23 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .soliumrc.json 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | parserOptions: { 4 | ecmaFeatures: { 5 | generators: true, 6 | experimentalObjectRestSpread: true, 7 | }, 8 | sourceType: 'module', 9 | allowImportExportEverywhere: false, 10 | }, 11 | extends: [ 12 | 'eslint:recommended', 13 | 'plugin:import/errors', 14 | 'plugin:import/warnings', 15 | 'plugin:prettier/recommended', 16 | 'plugin:promise/recommended', 17 | 'plugin:security/recommended', 18 | ], 19 | plugins: ['compat', 'promise', 'security'], 20 | settings: { 21 | 'import/resolver': { 22 | node: { 23 | extensions: ['.js', '.jsx', '.json', '.css'], 24 | paths: './src', 25 | }, 26 | }, 27 | polyfills: ['fetch', 'promises'], 28 | }, 29 | env: { 30 | node: true, 31 | }, 32 | globals: { 33 | __DEV__: true, 34 | __dirname: true, 35 | after: true, 36 | afterAll: true, 37 | afterEach: true, 38 | artifacts: true, 39 | before: true, 40 | beforeAll: true, 41 | beforeEach: true, 42 | console: true, 43 | contract: true, 44 | describe: true, 45 | expect: true, 46 | fetch: true, 47 | global: true, 48 | it: true, 49 | module: true, 50 | process: true, 51 | Promise: true, 52 | require: true, 53 | setTimeout: true, 54 | test: true, 55 | xdescribe: true, 56 | xit: true, 57 | web3: true, 58 | }, 59 | rules: { 60 | 'compat/compat': 'error', 61 | 'import/first': 'error', 62 | 'import/no-anonymous-default-export': 'error', 63 | 'import/no-unassigned-import': 'error', 64 | 'import/prefer-default-export': 'error', 65 | 'import/no-named-as-default': 'off', 66 | 'import/no-unresolved': 'error', 67 | 'promise/avoid-new': 'off', 68 | 'security/detect-object-injection': 'off', 69 | 'arrow-body-style': 'off', 70 | 'lines-between-class-members': ['error', 'always'], 71 | 'no-console': ['error', { allow: ['assert'] }], 72 | 'no-shadow': 'error', 73 | 'no-var': 'error', 74 | 'padding-line-between-statements': [ 75 | 'error', 76 | { blankLine: 'always', prev: 'class', next: '*' }, 77 | { blankLine: 'always', prev: 'do', next: '*' }, 78 | { blankLine: 'always', prev: '*', next: 'export' }, 79 | { blankLine: 'always', prev: 'for', next: '*' }, 80 | { blankLine: 'always', prev: 'if', next: '*' }, 81 | { blankLine: 'always', prev: 'switch', next: '*' }, 82 | { blankLine: 'always', prev: 'try', next: '*' }, 83 | { blankLine: 'always', prev: 'while', next: '*' }, 84 | { blankLine: 'always', prev: 'with', next: '*' }, 85 | ], 86 | 'prefer-const': 'error', 87 | }, 88 | } 89 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | package-lock.json 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # testing 15 | coverage 16 | coverage.json 17 | 18 | # production 19 | build 20 | deployed-contracts 21 | 22 | # misc 23 | .DS_Store 24 | .env 25 | .node-xmlhttprequest* 26 | 27 | # tmp files 28 | ~* 29 | .#* 30 | .editorconfig 31 | 32 | # Local geth folders 33 | private-chain-data/geth 34 | private-chain-data/history 35 | 36 | # Local Settings 37 | .vscode 38 | 39 | # generated diagrams 40 | sequence-diagrams/*.svg 41 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: node:10 2 | cache: 3 | key: $CI_BUILD_REF_SLUG 4 | paths: 5 | - node_modules/ 6 | 7 | stages: 8 | - setup 9 | - test 10 | - build 11 | - release 12 | - deploy 13 | 14 | ######### 15 | # SETUP # 16 | ######### 17 | setup: 18 | stage: setup 19 | script: 20 | - yarn install --frozen-lockfile 21 | artifacts: 22 | paths: 23 | - node_modules/ 24 | 25 | ######## 26 | # TEST # 27 | ######## 28 | lint: 29 | stage: test 30 | cache: 31 | policy: pull 32 | script: 33 | - yarn lint 34 | 35 | test: 36 | stage: test 37 | cache: 38 | policy: pull 39 | script: 40 | - cp .env.example .env 41 | - yarn test 42 | 43 | ######### 44 | # BUILD # 45 | ######### 46 | build_image: 47 | stage: build 48 | image: docker:latest 49 | variables: 50 | DOCKER_DRIVER: overlay2 51 | services: 52 | - docker:dind 53 | script: 54 | - docker login --username gitlab-ci-token --password $CI_BUILD_TOKEN $CI_REGISTRY 55 | # if the branch is master, use the latest tag. When the repository is tagged (git tag), the docker image tag should reflect that. 56 | - '[ "$CI_COMMIT_REF_SLUG" == "master" ] && CI_COMMIT_REF_SLUG=latest' 57 | - docker build --network host --cache-from $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG . 58 | - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG 59 | only: 60 | - master 61 | except: 62 | - tags 63 | # skip artifacts and cache download for this job 64 | dependencies: [] 65 | cache: {} 66 | 67 | ############### 68 | # NPM RELEASE # 69 | ############### 70 | release: 71 | stage: release 72 | cache: 73 | policy: pull 74 | only: 75 | - master 76 | script: 77 | - /bin/sh $(pwd)/scripts/ci-release.sh 78 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/Bug.md: -------------------------------------------------------------------------------- 1 | **[REMOVE ME]: Please fill out as many of the below sections as possible and remove the ones that don't apply to this issue** 2 | 3 | ### Description 4 | Provide a short description on what's wrong 5 | 6 | ### Steps to reproduce the problem 7 | 1. How can 8 | 1. reviewers 9 | 1. reproduce 10 | 1. the problem 11 | 12 | ### Expected Behavior 13 | What _should_ happen? 14 | 15 | ### Actual Behavior 16 | What _actually_ happens? 17 | 18 | ### Screenshots 19 | Drag-and-drop screenshots or animated GIFs to illustrate the problem 20 | -------------------------------------------------------------------------------- /.gitlab/issue_templates/Default.md: -------------------------------------------------------------------------------- 1 | **[REMOVE ME]: Please fill out as many of the below sections as possible and remove the ones that don't apply to this issue** 2 | 3 | ### Description 4 | Explain what needs to be done 5 | 6 | ### Possible Approaches 7 | Suggest potential solutions if possible 8 | 9 | ### Tasks 10 | - [ ]  List 11 | - [ ]  actionable 12 | - [ ]  Tasks 13 | -------------------------------------------------------------------------------- /.gitlab/merge_request_templates/Feature.md: -------------------------------------------------------------------------------- 1 | **[REMOVE ME]: Please fill out as many of the below sections as possible and remove the ones that don't apply to this MR** 2 | 3 | ### Description 4 | Provide a short description of the feature that was implemented 5 | 6 | ##### What should it look like? 7 | Add avocado links, screenshots or animated GIFs showing the new feature in action 8 | 9 | ### Related issues 10 | e.g. Fixes #123 11 | 12 | ### Done 13 | * [x] List 14 | * [x] of 15 | * [x] done 16 | * [x] Tasks 17 | 18 | ### Open Tasks for future MR 19 | * [ ] List => https://git.brickblock-dev.io/group/project/issue/ 20 | * [ ] of => https://git.brickblock-dev.io/group/project/issue/ 21 | * [ ] open => https://git.brickblock-dev.io/group/project/issue/ 22 | * [ ] Tasks => https://git.brickblock-dev.io/group/project/issue/ 23 | 24 | ### How to test this MR 25 | ##### Test the feature 26 | 1. Detailed Instructions 27 | 1. for reviewers 28 | 1. on how to test 29 | 1. the new feature 30 | -------------------------------------------------------------------------------- /.gitlab/merge_request_templates/Fix.md: -------------------------------------------------------------------------------- 1 | **[REMOVE ME]: Please fill out as many of the below sections as possible and remove the ones that don't apply to this MR** 2 | 3 | ### Description 4 | Provide a short description on what was wrong 5 | 6 | ### Related issues 7 | e.g. Fixes #123 8 | 9 | ### Steps to reproduce the problem 10 | 1. How can 11 | 1. reviewers 12 | 1. reproduce 13 | 1. the problem 14 | 15 | ### Steps to see the fix 16 | 1. How can 17 | 1. reviewers 18 | 1. see your 19 | 1. fix 20 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ignoring everything and then whitelist what to include 2 | * 3 | 4 | # built contract artifacts, and minimal contract for downstream consumers 5 | !deployed-contracts/*.json 6 | !build/contracts/*.json 7 | 8 | # migration helpers used to deploy contracts in e2e testing in platform 9 | !config/* 10 | !helpers/* 11 | !migrations/helpers/* 12 | !scripts/lib/* 13 | !truffle.js 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | README.md 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | compileCommand: '../node_modules/.bin/truffle compile', 3 | copyPackages: ['openzeppelin-solidity'], 4 | skipFiles: ['CustomPOAToken.sol', 'external/'], 5 | testCommand: 'yarn test', 6 | } 7 | -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/external 3 | frozen/ 4 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:all", 3 | "plugins": ["security"], 4 | "rules": { 5 | "arg-overflow": "error", 6 | "blank-lines": "error", 7 | "camelcase": "error", 8 | "deprecated-suicide": "error", 9 | "emit": "error", 10 | "error-reason": "off", 11 | "function-order": "off", 12 | "indentation": ["error", 2], 13 | "linebreak-style": ["error", "unix"], 14 | "no-constant": "error", 15 | "no-empty-blocks": "error", 16 | "pragma-on-top": "error", 17 | "quotes": ["error", "double"], 18 | "uppercase": "off", 19 | "visibility-first": "error", 20 | "whitespace": "error", 21 | 22 | "security/enforce-loop-bounds": "warning", 23 | "security/no-assign-params": "off", 24 | "security/no-func-overriding": "error", 25 | "security/no-inline-assembly": "off", 26 | "security/no-low-level-calls": "off", 27 | "security/no-suicide-or-selfdestruct": "error", 28 | "security/no-var": "error" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8 as build 2 | COPY . /app 3 | WORKDIR /app 4 | 5 | # we install the dev dependencies as we dont want other downstream projects (like portal) to have 6 | # conflicts with express versions due to this packaging in a Docker container 7 | RUN yarn --non-interative 8 | 9 | FROM node:8-alpine 10 | WORKDIR /app 11 | COPY --from=build /app /app 12 | CMD [ "yarn", "start" ] 13 | -------------------------------------------------------------------------------- /config/deployed-contracts.js: -------------------------------------------------------------------------------- 1 | // This file contains the addresses that are injected into the build/contracts artifacts for 2 | // consuming applications to use. 3 | // 4 | // While we still like using `truffle-contract` we should inject, as this file could also be 5 | // included in the npm publish and used directly 6 | // ie. `SomeContract.at(pick-address-based-on-network)` 7 | 8 | module.exports = { 9 | // mainnet 10 | '1': { 11 | ContractRegistry: '0x5973376b603268fe4251d13040226078257014f8', 12 | }, 13 | 14 | // ropsten 15 | '3': { 16 | ContractRegistry: '0xae472faaf28b2979d4d36be8ad8947ed7c827e99', 17 | }, 18 | 19 | // rinkeby 20 | '4': { 21 | ContractRegistry: '0xf166a2c755c2ad404b42c9be146acefbb3907aee', 22 | }, 23 | 24 | // kovan 25 | '42': { 26 | ContractRegistry: '0x138d5bb1eef88dad0b6dde5e46a746ef31a22f6e', 27 | }, 28 | 29 | // local testnet (dedicated ganache when running `yarn ganache-cli --network 4448` in platform to run end-to-end tests against) 30 | '4448': { 31 | ContractRegistry: '0x17e91224c30c5b0b13ba2ef1e84fe880cb902352', 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /contracts/BrickblockAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "./interfaces/IRegistry.sol"; 5 | import "./interfaces/IBrickblockToken.sol"; 6 | import "./interfaces/IFeeManager.sol"; 7 | import "./interfaces/IAccessToken.sol"; 8 | 9 | 10 | contract BrickblockAccount is Ownable { 11 | uint8 public constant version = 1; 12 | uint256 public releaseTimeOfCompanyBBKs; 13 | IRegistry private registry; 14 | 15 | constructor( 16 | address _registryAddress, 17 | uint256 _releaseTimeOfCompanyBBKs 18 | ) 19 | public 20 | { 21 | // solium-disable-next-line security/no-block-members 22 | require(_releaseTimeOfCompanyBBKs > block.timestamp); 23 | releaseTimeOfCompanyBBKs = _releaseTimeOfCompanyBBKs; 24 | registry = IRegistry(_registryAddress); 25 | } 26 | 27 | function pullFunds() 28 | external 29 | onlyOwner 30 | returns (bool) 31 | { 32 | IBrickblockToken bbk = IBrickblockToken( 33 | registry.getContractAddress("BrickblockToken") 34 | ); 35 | uint256 _companyFunds = bbk.balanceOf(address(bbk)); 36 | 37 | return bbk.transferFrom(address(bbk), address(this), _companyFunds); 38 | } 39 | 40 | function lockBBK(uint256 _value) 41 | external 42 | onlyOwner 43 | returns (bool) 44 | { 45 | IAccessToken act = IAccessToken( 46 | registry.getContractAddress("AccessToken") 47 | ); 48 | IBrickblockToken bbk = IBrickblockToken( 49 | registry.getContractAddress("BrickblockToken") 50 | ); 51 | 52 | require(bbk.approve(address(act), _value)); 53 | 54 | return act.lockBBK(_value); 55 | } 56 | 57 | function unlockBBK(uint256 _value) 58 | external 59 | onlyOwner 60 | returns (bool) 61 | { 62 | IAccessToken act = IAccessToken( 63 | registry.getContractAddress("AccessToken") 64 | ); 65 | 66 | return act.unlockBBK(_value); 67 | } 68 | 69 | function claimFee(uint256 _value) 70 | external 71 | onlyOwner 72 | returns (bool) 73 | { 74 | IFeeManager fmr = IFeeManager( 75 | registry.getContractAddress("FeeManager") 76 | ); 77 | 78 | return fmr.claimFee(_value); 79 | } 80 | 81 | function withdrawEthFunds( 82 | address _address, 83 | uint256 _value 84 | ) 85 | external 86 | onlyOwner 87 | returns (bool) 88 | { 89 | require(address(this).balance >= _value); 90 | _address.transfer(_value); 91 | 92 | return true; 93 | } 94 | 95 | function withdrawActFunds( 96 | address _address, 97 | uint256 _value 98 | ) 99 | external 100 | onlyOwner 101 | returns (bool) 102 | { 103 | IAccessToken act = IAccessToken( 104 | registry.getContractAddress("AccessToken") 105 | ); 106 | 107 | return act.transfer(_address, _value); 108 | } 109 | 110 | function withdrawBbkFunds( 111 | address _address, 112 | uint256 _value 113 | ) 114 | external 115 | onlyOwner 116 | returns (bool) 117 | { 118 | // solium-disable-next-line security/no-block-members 119 | require(block.timestamp >= releaseTimeOfCompanyBBKs); 120 | IBrickblockToken bbk = IBrickblockToken( 121 | registry.getContractAddress("BrickblockToken") 122 | ); 123 | 124 | return bbk.transfer(_address, _value); 125 | } 126 | 127 | // ensure that we can be paid ether 128 | function() 129 | public 130 | payable 131 | {} 132 | } 133 | -------------------------------------------------------------------------------- /contracts/ContractRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | 5 | 6 | contract ContractRegistry is Ownable { 7 | uint8 public constant version = 1; 8 | 9 | mapping (bytes32 => address) private contractAddresses; 10 | 11 | event UpdateContract(string name, address indexed contractAddress); 12 | 13 | /** 14 | @notice Ensures that a given address is a contract by making sure it has code. 15 | */ 16 | function isContract(address _address) 17 | private 18 | view 19 | returns (bool) 20 | { 21 | uint256 _size; 22 | assembly { _size := extcodesize(_address) } 23 | 24 | return _size > 0; 25 | } 26 | 27 | function updateContractAddress( 28 | string _name, 29 | address _address 30 | ) 31 | public 32 | onlyOwner 33 | returns (address) 34 | { 35 | require(isContract(_address)); 36 | require(_address != contractAddresses[keccak256(_name)]); 37 | 38 | contractAddresses[keccak256(_name)] = _address; 39 | emit UpdateContract(_name, _address); 40 | 41 | return _address; 42 | } 43 | 44 | function getContractAddress(string _name) 45 | public 46 | view 47 | returns (address) 48 | { 49 | require(contractAddresses[keccak256(_name)] != address(0)); 50 | 51 | return contractAddresses[keccak256(_name)]; 52 | } 53 | 54 | function getContractAddress32(bytes32 _name32) 55 | public 56 | view 57 | returns (address) 58 | { 59 | require(contractAddresses[_name32] != address(0)); 60 | 61 | return contractAddresses[_name32]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /contracts/EmployeeTokenSalaryPayout.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 5 | import "./interfaces/IBrickblockToken.sol"; 6 | 7 | 8 | /** 9 | @title Contract for doing payouts to Brickblock employees on quarterly basis. 10 | */ 11 | contract EmployeeTokenSalaryPayout is Ownable { 12 | using SafeMath for uint256; 13 | 14 | // Events 15 | event Distribute(uint256 timestamp, uint256 amount); 16 | event AddEmployee(address indexed _address, uint256 timestamp); 17 | event RemoveEmployee(address indexed _address, uint256 timestamp); 18 | event ChangeQuarterlyAmount(address indexed _address, uint256 timestamp, uint256 newAmount); 19 | 20 | struct Employee { 21 | uint256 initialPayoutAmount; 22 | uint256 quarterlyAmount; 23 | uint256 index; 24 | } 25 | 26 | mapping(address => Employee) public employees; 27 | address[] public employeeAddressList; 28 | 29 | IBrickblockToken bbkToken; 30 | 31 | constructor(IBrickblockToken _bbkToken) 32 | public 33 | { 34 | require(_bbkToken != address(0)); 35 | 36 | bbkToken = _bbkToken; 37 | } 38 | 39 | function addEmployee ( 40 | address _beneficiary, 41 | uint256 _quarterlyAmount, 42 | uint256 _startingBalance 43 | ) 44 | public 45 | onlyOwner 46 | returns (bool) 47 | { 48 | Employee storage _employee = employees[_beneficiary]; 49 | 50 | require(_beneficiary != address(0)); 51 | require(_quarterlyAmount > 0); 52 | require(_employee.quarterlyAmount == 0); 53 | 54 | employeeAddressList.push(_beneficiary); 55 | _employee.initialPayoutAmount = _startingBalance; 56 | _employee.quarterlyAmount = _quarterlyAmount; 57 | _employee.index = employeeAddressList.length-1; 58 | 59 | // solium-disable-next-line security/no-block-members 60 | emit AddEmployee(_beneficiary, block.timestamp); 61 | 62 | return true; 63 | } 64 | 65 | function removeEmployee( 66 | address _beneficiary, 67 | uint256 _endingBalance 68 | ) 69 | public 70 | onlyOwner 71 | returns (bool) 72 | { 73 | Employee memory _deletedUser = employees[_beneficiary]; 74 | 75 | require(_beneficiary != address(0)); 76 | require(_deletedUser.quarterlyAmount > 0); 77 | require(payout(_beneficiary, _endingBalance)); 78 | 79 | // if index is not the last entry 80 | // swap deleted user index with the last one 81 | if (_deletedUser.index != employeeAddressList.length-1) { 82 | address lastAddress = employeeAddressList[employeeAddressList.length-1]; 83 | employeeAddressList[_deletedUser.index] = lastAddress; 84 | employees[lastAddress].index = _deletedUser.index; 85 | } 86 | delete employees[_beneficiary]; 87 | employeeAddressList.length--; 88 | // solium-disable-next-line security/no-block-members 89 | emit RemoveEmployee(_beneficiary, block.timestamp); 90 | 91 | return true; 92 | } 93 | 94 | function updateQuarterlyAmount( 95 | address _beneficiary, 96 | uint256 newAmount 97 | ) 98 | public 99 | onlyOwner 100 | returns (bool) 101 | { 102 | require(_beneficiary != address(0)); 103 | require(newAmount > 0); 104 | employees[_beneficiary].quarterlyAmount = newAmount; 105 | 106 | // solium-disable-next-line security/no-block-members 107 | emit ChangeQuarterlyAmount(_beneficiary, block.timestamp, newAmount); 108 | 109 | return true; 110 | } 111 | 112 | function payout( 113 | address _beneficiary, 114 | uint256 _bbkAmount 115 | ) 116 | private 117 | returns (bool) 118 | { 119 | return (bbkToken.transfer(_beneficiary, _bbkAmount)); 120 | } 121 | 122 | function getTotalPayoutAmount() 123 | public 124 | view 125 | returns (uint256) 126 | { 127 | uint256 _totalAmount; 128 | 129 | for (uint i = 0; i < employeeAddressList.length; i++) { 130 | address _address = employeeAddressList[i]; 131 | uint256 _amount = employees[_address].quarterlyAmount; 132 | 133 | if (employees[_address].initialPayoutAmount != 0) { 134 | _amount = _amount.add(employees[_address].initialPayoutAmount); 135 | } 136 | _totalAmount = _totalAmount.add(_amount); 137 | } 138 | 139 | return _totalAmount; 140 | } 141 | 142 | function distributePayouts() 143 | public 144 | onlyOwner 145 | { 146 | uint256 _totalAmount; 147 | 148 | for (uint i = 0; i < employeeAddressList.length; i++) { 149 | address _address = employeeAddressList[i]; 150 | uint256 _amount = employees[_address].quarterlyAmount; 151 | 152 | if (employees[_address].initialPayoutAmount != 0) { 153 | _amount = _amount.add(employees[_address].initialPayoutAmount); 154 | employees[_address].initialPayoutAmount = 0; 155 | } 156 | _totalAmount = _totalAmount.add(_amount); 157 | payout(_address, _amount); 158 | } 159 | 160 | // solium-disable-next-line security/no-block-members 161 | emit Distribute(block.timestamp, _totalAmount); 162 | } 163 | 164 | function claimAll() 165 | public 166 | onlyOwner 167 | { 168 | uint256 _amount = bbkToken.balanceOf(address(this)); 169 | bbkToken.transfer(owner, _amount); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /contracts/ExchangeRateProvider.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "./external/OraclizeAPI.sol"; 4 | import "./interfaces/IRegistry.sol"; 5 | import "./interfaces/IExchangeRates.sol"; 6 | 7 | 8 | /** 9 | @title This contract is the integration point for using the Oraclize service. 10 | */ 11 | contract ExchangeRateProvider is usingOraclize { 12 | uint8 public constant version = 1; 13 | 14 | IRegistry private registry; 15 | 16 | // ensure that only the oracle or ExchangeRates contract are allowed 17 | modifier onlyAllowed() 18 | { 19 | require( 20 | msg.sender == registry.getContractAddress("ExchangeRates") || 21 | msg.sender == oraclize_cbAddress() 22 | ); 23 | _; 24 | } 25 | 26 | modifier onlyOraclizer() 27 | { 28 | require(msg.sender == oraclize_cbAddress()); 29 | _; 30 | } 31 | 32 | modifier onlyExchangeRates() 33 | { 34 | require(msg.sender == registry.getContractAddress("ExchangeRates")); 35 | _; 36 | } 37 | 38 | constructor(address _registryAddress) 39 | public 40 | { 41 | require(_registryAddress != address(0)); 42 | registry = IRegistry(_registryAddress); 43 | } 44 | 45 | // set gas price used for oraclize callbacks 46 | function setCallbackGasPrice(uint256 _gasPrice) 47 | external 48 | onlyExchangeRates 49 | returns (bool) 50 | { 51 | oraclize_setCustomGasPrice(_gasPrice); 52 | 53 | return true; 54 | } 55 | 56 | // send query to oraclize, results sent to __callback 57 | // money can be forwarded on from ExchangeRates 58 | // current implementation requires > 1e5 & < 2e5 callbackGasLimit 59 | function sendQuery( 60 | string _queryString, 61 | uint256 _callInterval, 62 | uint256 _callbackGasLimit, 63 | string _queryType 64 | ) 65 | public 66 | onlyAllowed 67 | payable 68 | returns (bool) 69 | { 70 | // check that there is enough money to make the query 71 | if (oraclize_getPrice("URL") > address(this).balance) { 72 | setQueryId(0x0, ""); 73 | 74 | return false; 75 | } else { 76 | // make query based on currencySettings for a given _queryType 77 | bytes32 _queryId = oraclize_query( 78 | _callInterval, 79 | "URL", 80 | _queryString, 81 | _callbackGasLimit 82 | ); 83 | // set the queryId on ExchangeRates so that it knows about it and can 84 | // accept it when __callback tries to set the rate 85 | setQueryId(_queryId, _queryType); 86 | 87 | return true; 88 | } 89 | } 90 | 91 | // set queryIds on ExchangeRates for later validation when __callback happens 92 | function setQueryId( 93 | bytes32 _identifier, 94 | string _queryType 95 | ) 96 | private 97 | returns (bool) 98 | { 99 | // get current address of ExchangeRates 100 | IExchangeRates _exchangeRates = IExchangeRates( 101 | registry.getContractAddress("ExchangeRates") 102 | ); 103 | // run setQueryId on ExchangeRates 104 | return _exchangeRates.setQueryId(_identifier, _queryType); 105 | } 106 | 107 | // callback function for returned results of oraclize call 108 | // solium-disable-next-line mixedcase 109 | function __callback( 110 | bytes32 _queryId, 111 | string _result 112 | ) 113 | public 114 | onlyOraclizer 115 | { 116 | // get currency address of ContractRegistry 117 | IExchangeRates _exchangeRates = IExchangeRates( 118 | registry.getContractAddress("ExchangeRates") 119 | ); 120 | // get settings data from ExchangeRates 121 | bool _ratesActive = _exchangeRates.ratesActive(); 122 | uint256 _callInterval; 123 | uint256 _callbackGasLimit; 124 | string memory _queryString; 125 | string memory _queryType = _exchangeRates.queryTypes(_queryId); 126 | ( 127 | _callInterval, 128 | _callbackGasLimit, 129 | _queryString 130 | ) = _exchangeRates.getCurrencySettings(_queryType); 131 | 132 | // Set the rate on ExchangeRates contract giving queryId for validation. 133 | // The api returns a string which is parsed as int with 2 decimal places 134 | // ie. _result = 500.12 135 | // parseInt(_result, 2) => 50012 136 | require( 137 | _exchangeRates.setRate( 138 | _queryId, 139 | parseInt(_result, 2) 140 | ) 141 | ); 142 | 143 | // check if call interval has been set and that _ratesActive is still true 144 | // if so, call again with the interval 145 | if (_callInterval > 0 && _ratesActive) { 146 | sendQuery( 147 | _queryString, 148 | _callInterval, 149 | _callbackGasLimit, 150 | _queryType 151 | ); 152 | } 153 | } 154 | 155 | // used in case we need to get money out of the contract before replacing 156 | function selfDestruct(address _address) 157 | public 158 | onlyExchangeRates 159 | { 160 | // solium-disable-next-line security/no-suicide-or-selfdestruct 161 | selfdestruct(_address); 162 | } 163 | 164 | // ensure that we can fund queries by paying the contract 165 | function() 166 | public 167 | payable 168 | {} 169 | } 170 | -------------------------------------------------------------------------------- /contracts/FeeManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "./interfaces/IAccessToken.sol"; 5 | import "./interfaces/IRegistry.sol"; 6 | 7 | 8 | contract FeeManager { 9 | using SafeMath for uint256; 10 | 11 | uint8 public constant version = 1; 12 | uint256 actRate = 1000; 13 | 14 | IRegistry private registry; 15 | 16 | constructor(address _registryAddress) 17 | public 18 | { 19 | require(_registryAddress != address(0)); 20 | registry = IRegistry(_registryAddress); 21 | } 22 | 23 | function weiToAct(uint256 _wei) 24 | public 25 | view 26 | returns (uint256) 27 | { 28 | return _wei.mul(actRate); 29 | } 30 | 31 | function actToWei(uint256 _act) 32 | public 33 | view 34 | returns (uint256) 35 | { 36 | return _act.div(actRate); 37 | } 38 | 39 | function payFee() 40 | public 41 | payable 42 | returns (bool) 43 | { 44 | IAccessToken act = IAccessToken( 45 | registry.getContractAddress("AccessToken") 46 | ); 47 | require(act.distribute(weiToAct(msg.value))); 48 | 49 | return true; 50 | } 51 | 52 | function claimFee(uint256 _value) 53 | public 54 | returns (bool) 55 | { 56 | IAccessToken act = IAccessToken( 57 | registry.getContractAddress("AccessToken") 58 | ); 59 | require(act.burn(msg.sender, _value)); 60 | msg.sender.transfer(actToWei(_value)); 61 | 62 | return true; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | 4 | contract Migrations { 5 | address public owner; 6 | uint public lastCompletedMigration; 7 | 8 | modifier restricted() { 9 | if (msg.sender == owner) 10 | _; 11 | } 12 | 13 | constructor() 14 | public 15 | { 16 | owner = msg.sender; 17 | } 18 | 19 | function setCompleted(uint completed) 20 | public 21 | restricted 22 | { 23 | lastCompletedMigration = completed; 24 | } 25 | 26 | function upgrade(address newAddress) 27 | public 28 | restricted 29 | { 30 | Migrations upgraded = Migrations(newAddress); 31 | upgraded.setCompleted(lastCompletedMigration); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/PoaLogger.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "./interfaces/IRegistry.sol"; 4 | import "./interfaces/IPoaManager.sol"; 5 | import "./interfaces/IPoaToken.sol"; 6 | 7 | 8 | contract PoaLogger { 9 | uint8 public constant version = 1; 10 | // registry instance to get other contract addresses 11 | IRegistry public registry; 12 | 13 | constructor( 14 | address _registryAddress 15 | ) 16 | public 17 | { 18 | require(_registryAddress != address(0)); 19 | registry = IRegistry(_registryAddress); 20 | } 21 | 22 | // only allow listed poa tokens to trigger events 23 | modifier onlyActivePoaToken() { 24 | require( 25 | IPoaManager( 26 | registry.getContractAddress("PoaManager") 27 | ).isActiveToken(msg.sender) 28 | ); 29 | _; 30 | } 31 | 32 | // possible events from a PoaToken 33 | event Stage( 34 | address indexed tokenAddress, 35 | uint256 stage 36 | ); 37 | event Buy( 38 | address indexed tokenAddress, 39 | address indexed buyer, 40 | uint256 amount 41 | ); 42 | event ProofOfCustodyUpdated( 43 | address indexed tokenAddress, 44 | string ipfsHash 45 | ); 46 | event Payout( 47 | address indexed tokenAddress, 48 | uint256 amount 49 | ); 50 | event Claim( 51 | address indexed tokenAddress, 52 | address indexed claimer, 53 | uint256 payout 54 | ); 55 | event Terminated( 56 | address indexed tokenAddress 57 | ); 58 | event CustodianChanged( 59 | address indexed tokenAddress, 60 | address oldAddress, 61 | address newAddress 62 | ); 63 | event ReClaim( 64 | address indexed tokenAddress, 65 | address indexed reclaimer, 66 | uint256 amount 67 | ); 68 | 69 | // possible events from PoaProxy 70 | event ProxyUpgraded( 71 | address indexed tokenAddress, 72 | address upgradedFrom, 73 | address upgradedTo 74 | ); 75 | 76 | // event triggers for each event 77 | function logStage(uint256 stage) 78 | external 79 | onlyActivePoaToken 80 | { 81 | emit Stage(msg.sender, stage); 82 | } 83 | 84 | function logBuy( 85 | address buyer, 86 | uint256 amount 87 | ) 88 | external 89 | onlyActivePoaToken 90 | { 91 | emit Buy(msg.sender, buyer, amount); 92 | } 93 | 94 | function logProofOfCustodyUpdated() 95 | external 96 | onlyActivePoaToken 97 | { 98 | // easier to get the set ipfsHash from contract rather than send over string 99 | string memory _realIpfsHash = IPoaToken(msg.sender).proofOfCustody(); 100 | 101 | emit ProofOfCustodyUpdated( 102 | msg.sender, 103 | _realIpfsHash 104 | ); 105 | } 106 | 107 | function logPayout(uint256 _amount) 108 | external 109 | onlyActivePoaToken 110 | { 111 | emit Payout( 112 | msg.sender, 113 | _amount 114 | ); 115 | } 116 | 117 | function logClaim( 118 | address _claimer, 119 | uint256 _payout 120 | ) 121 | external 122 | onlyActivePoaToken 123 | { 124 | emit Claim( 125 | msg.sender, 126 | _claimer, 127 | _payout 128 | ); 129 | } 130 | 131 | function logTerminated() 132 | external 133 | onlyActivePoaToken 134 | { 135 | emit Terminated(msg.sender); 136 | } 137 | 138 | function logCustodianChanged( 139 | address _oldAddress, 140 | address _newAddress 141 | ) 142 | external 143 | onlyActivePoaToken 144 | { 145 | emit CustodianChanged( 146 | msg.sender, 147 | _oldAddress, 148 | _newAddress 149 | ); 150 | } 151 | 152 | function logReClaim( 153 | address _reclaimer, 154 | uint256 _amount 155 | ) 156 | external 157 | onlyActivePoaToken 158 | { 159 | emit ReClaim( 160 | msg.sender, 161 | _reclaimer, 162 | _amount 163 | ); 164 | } 165 | 166 | function logProxyUpgraded( 167 | address _upgradedFrom, 168 | address _upgradedTo 169 | ) 170 | external 171 | onlyActivePoaToken 172 | { 173 | emit ProxyUpgraded( 174 | msg.sender, 175 | _upgradedFrom, 176 | _upgradedTo 177 | ); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /contracts/PoaProxyCommon.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | 4 | /** 5 | @title PoaProxyCommon acts as a convention between: 6 | - PoaCommon (and its inheritants: PoaToken & PoaCrowdsale) 7 | - PoaProxy 8 | 9 | It dictates where to read and write storage 10 | */ 11 | contract PoaProxyCommon { 12 | /***************************** 13 | * Start Proxy Common Storage * 14 | *****************************/ 15 | 16 | // PoaTokenMaster logic contract used by proxies 17 | address public poaTokenMaster; 18 | 19 | // PoaCrowdsaleMaster logic contract used by proxies 20 | address public poaCrowdsaleMaster; 21 | 22 | // Registry used for getting other contract addresses 23 | address public registry; 24 | 25 | /*************************** 26 | * End Proxy Common Storage * 27 | ***************************/ 28 | 29 | 30 | /********************************* 31 | * Start Common Utility Functions * 32 | *********************************/ 33 | 34 | /// @notice Gets a given contract address by bytes32 in order to save gas 35 | function getContractAddress(string _name) 36 | public 37 | view 38 | returns (address _contractAddress) 39 | { 40 | bytes4 _signature = bytes4(keccak256("getContractAddress32(bytes32)")); 41 | bytes32 _name32 = keccak256(abi.encodePacked(_name)); 42 | 43 | assembly { 44 | let _registry := sload(registry_slot) // load registry address from storage 45 | let _pointer := mload(0x40) // Set _pointer to free memory pointer 46 | mstore(_pointer, _signature) // Store _signature at _pointer 47 | mstore(add(_pointer, 0x04), _name32) // Store _name32 at _pointer offset by 4 bytes for pre-existing _signature 48 | 49 | // staticcall(g, a, in, insize, out, outsize) => returns 0 on error, 1 on success 50 | let result := staticcall( 51 | gas, // g = gas: whatever was passed already 52 | _registry, // a = address: address in storage 53 | _pointer, // in = mem in mem[in..(in+insize): set to free memory pointer 54 | 0x24, // insize = mem insize mem[in..(in+insize): size of signature (bytes4) + bytes32 = 0x24 55 | _pointer, // out = mem out mem[out..(out+outsize): output assigned to this storage address 56 | 0x20 // outsize = mem outsize mem[out..(out+outsize): output should be 32byte slot (address size = 0x14 < slot size 0x20) 57 | ) 58 | 59 | // revert if not successful 60 | if iszero(result) { 61 | revert(0, 0) 62 | } 63 | 64 | _contractAddress := mload(_pointer) // Assign result to return value 65 | mstore(0x40, add(_pointer, 0x24)) // Advance free memory pointer by largest _pointer size 66 | } 67 | } 68 | 69 | /******************************* 70 | * End Common Utility Functions * 71 | *******************************/ 72 | } 73 | -------------------------------------------------------------------------------- /contracts/Whitelist.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/lifecycle/Pausable.sol"; 4 | 5 | 6 | contract Whitelist is Pausable { 7 | uint8 public constant version = 1; 8 | 9 | mapping (address => bool) private whitelistedMap; 10 | 11 | event Whitelisted(address indexed account, bool isWhitelisted); 12 | 13 | function whitelisted(address _address) 14 | public 15 | view 16 | returns (bool) 17 | { 18 | if (paused) { 19 | return false; 20 | } 21 | 22 | return whitelistedMap[_address]; 23 | } 24 | 25 | function addAddress(address _address) 26 | public 27 | onlyOwner 28 | { 29 | require(whitelistedMap[_address] != true); 30 | whitelistedMap[_address] = true; 31 | emit Whitelisted(_address, true); 32 | } 33 | 34 | function removeAddress(address _address) 35 | public 36 | onlyOwner 37 | { 38 | require(whitelistedMap[_address] != false); 39 | whitelistedMap[_address] = false; 40 | emit Whitelisted(_address, false); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/external/SafeMathPower.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | /** 5 | @title SafeMathPower 6 | @notice This library has been inspired by https://github.com/dapphub/ds-math/tree/49b38937c0c0b8af73b05f767a0af9d5e85a1e6c. 7 | It uses the same functions but with different visibility modifiers and has had unneeded functions removed. 8 | 9 | @dev This library can be used along side OpenZeppelin's SafeMath in the same manner. 10 | */ 11 | library SafeMathPower { 12 | uint internal constant RAY = 10 ** 27; 13 | 14 | function add(uint x, uint y) private pure returns (uint z) { 15 | require((z = x + y) >= x); 16 | } 17 | 18 | function mul(uint x, uint y) private pure returns (uint z) { 19 | require(y == 0 || (z = x * y) / y == x); 20 | } 21 | 22 | function rmul(uint x, uint y) private pure returns (uint z) { 23 | z = add(mul(x, y), RAY / 2) / RAY; 24 | } 25 | 26 | // This famous algorithm is called "exponentiation by squaring" 27 | // and calculates x^n with x as fixed-point and n as regular unsigned. 28 | // 29 | // It's O(log n), instead of O(n) for naive repeated multiplication. 30 | // 31 | // These facts are why it works: 32 | // 33 | // If n is even, then x^n = (x^2)^(n/2). 34 | // If n is odd, then x^n = x * x^(n-1), 35 | // and applying the equation for even x gives 36 | // x^n = x * (x^2)^((n-1) / 2). 37 | // 38 | // Also, EVM division is flooring and 39 | // floor[(n-1) / 2] = floor[n / 2]. 40 | // 41 | function rpow(uint x, uint n) internal pure returns (uint z) { 42 | z = n % 2 != 0 ? x : RAY; 43 | 44 | for (n /= 2; n != 0; n /= 2) { 45 | x = rmul(x, x); 46 | 47 | if (n % 2 != 0) { 48 | z = rmul(z, x); 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /contracts/interfaces/IAccessToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | interface IAccessToken { 4 | function lockBBK( 5 | uint256 _value 6 | ) 7 | external 8 | returns (bool); 9 | 10 | function unlockBBK( 11 | uint256 _value 12 | ) 13 | external 14 | returns (bool); 15 | 16 | function transfer( 17 | address _to, 18 | uint256 _value 19 | ) 20 | external 21 | returns (bool); 22 | 23 | function distribute( 24 | uint256 _amount 25 | ) 26 | external 27 | returns (bool); 28 | 29 | function burn( 30 | address _address, 31 | uint256 _value 32 | ) 33 | external 34 | returns (bool); 35 | } 36 | -------------------------------------------------------------------------------- /contracts/interfaces/IBrickblockToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | // limited BrickblockToken definition 4 | interface IBrickblockToken { 5 | function transfer( 6 | address _to, 7 | uint256 _value 8 | ) 9 | external 10 | returns (bool); 11 | 12 | function transferFrom( 13 | address from, 14 | address to, 15 | uint256 value 16 | ) 17 | external 18 | returns (bool); 19 | 20 | function balanceOf( 21 | address _address 22 | ) 23 | external 24 | view 25 | returns (uint256); 26 | 27 | function approve( 28 | address _spender, 29 | uint256 _value 30 | ) 31 | external 32 | returns (bool); 33 | } 34 | -------------------------------------------------------------------------------- /contracts/interfaces/IExchangeRateProvider.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | interface IExchangeRateProvider { 4 | function sendQuery( 5 | string _queryString, 6 | uint256 _callInterval, 7 | uint256 _callbackGasLimit, 8 | string _queryType 9 | ) 10 | external 11 | payable 12 | returns (bool); 13 | 14 | function setCallbackGasPrice(uint256 _gasPrice) 15 | external 16 | returns (bool); 17 | 18 | function selfDestruct(address _address) 19 | external; 20 | } 21 | -------------------------------------------------------------------------------- /contracts/interfaces/IExchangeRates.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | interface IExchangeRates { 4 | function queryTypes( 5 | bytes32 _queryId 6 | ) 7 | external 8 | returns (string); 9 | 10 | function ratesActive() 11 | external 12 | returns (bool); 13 | 14 | function getRate( 15 | string _queryType 16 | ) 17 | external 18 | view 19 | returns (uint256); 20 | 21 | function setRate( 22 | bytes32 _queryId, 23 | uint256 _rate 24 | ) 25 | external 26 | returns (bool); 27 | 28 | function setQueryId( 29 | bytes32 _queryId, 30 | string _queryType 31 | ) 32 | external 33 | returns (bool); 34 | 35 | function getCurrencySettings( 36 | string _queryType 37 | ) 38 | external 39 | view 40 | returns (uint256, uint256, string); 41 | } 42 | -------------------------------------------------------------------------------- /contracts/interfaces/IFeeManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | interface IFeeManager { 4 | function claimFee( 5 | uint256 _value 6 | ) 7 | external 8 | returns (bool); 9 | 10 | function payFee() 11 | external 12 | payable 13 | returns (bool); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /contracts/interfaces/IPoaCrowdsale.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | interface IPoaCrowdsale { 4 | function initializeCrowdsale( 5 | bytes32 _fiatCurrency32, // fiat currency string, e.g. 'EUR' 6 | uint256 _startTimeForFundingPeriod, // future UNIX timestamp 7 | uint256 _durationForFiatFundingPeriod, // duration of fiat funding period in seconds 8 | uint256 _durationForEthFundingPeriod, // duration of ETH funding period in seconds 9 | uint256 _durationForActivationPeriod, // duration of activation period in seconds 10 | uint256 _fundingGoalInCents // funding goal in fiat cents 11 | ) 12 | external 13 | returns (bool); 14 | } 15 | -------------------------------------------------------------------------------- /contracts/interfaces/IPoaManager.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | interface IPoaManager { 4 | function isActiveToken( 5 | address _tokenAddress 6 | ) 7 | external 8 | view 9 | returns (bool); 10 | } 11 | -------------------------------------------------------------------------------- /contracts/interfaces/IPoaToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | interface IPoaToken { 4 | function initializeToken 5 | ( 6 | bytes32 _name32, // bytes32 of name string 7 | bytes32 _symbol32, // bytes32 of symbol string 8 | address _issuer, 9 | address _custodian, 10 | address _registry, 11 | uint256 _totalSupply // token total supply 12 | ) 13 | external 14 | returns (bool); 15 | 16 | function issuer() 17 | external 18 | view 19 | returns (address); 20 | 21 | function startPreFunding() 22 | external 23 | returns (bool); 24 | 25 | function pause() 26 | external; 27 | 28 | function unpause() 29 | external; 30 | 31 | function terminate() 32 | external 33 | returns (bool); 34 | 35 | function proofOfCustody() 36 | external 37 | view 38 | returns (string); 39 | } 40 | -------------------------------------------------------------------------------- /contracts/interfaces/IRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | // limited ContractRegistry definition 4 | interface IRegistry { 5 | function owner() 6 | external 7 | returns (address); 8 | 9 | function updateContractAddress( 10 | string _name, 11 | address _address 12 | ) 13 | external 14 | returns (address); 15 | 16 | function getContractAddress( 17 | string _name 18 | ) 19 | external 20 | view 21 | returns (address); 22 | } 23 | -------------------------------------------------------------------------------- /contracts/interfaces/IWhitelist.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | interface IWhitelist { 4 | function whitelisted(address _address) 5 | external 6 | returns (bool); 7 | } 8 | -------------------------------------------------------------------------------- /contracts/stubs/BrickblockFountainStub.sol: -------------------------------------------------------------------------------- 1 | // THIS IS EXAMPLE CODE ONLY AND THE FUNCTIONS MOST LIKELY WILL CHANGE 2 | pragma solidity 0.4.24; 3 | 4 | import "../BrickblockToken.sol"; 5 | 6 | 7 | contract BrickblockFountainStub is Ownable { 8 | using SafeMath for uint256; 9 | 10 | struct Account { 11 | uint256 tokens; 12 | uint256 lastCheck; 13 | uint256 tokenHours; 14 | } 15 | 16 | mapping (address => Account) balances; 17 | 18 | // this will be set to the estimated block that will occur on November 30th 2020 19 | uint256 public constant companyShareReleaseBlock = 1234567; 20 | address public brickBlockTokenAddress; 21 | 22 | event BBTLocked(address _locker, uint256 _value); 23 | event CompanyTokensReleased(address _owner, uint256 _tokenAmount); 24 | event Placeholder(address _address, uint256 _value); 25 | 26 | constructor( 27 | address _brickBlockTokenAddress 28 | ) 29 | public 30 | { 31 | require(_brickBlockTokenAddress != address(0)); 32 | brickBlockTokenAddress = _brickBlockTokenAddress; 33 | } 34 | 35 | // basic implementation of balance return 36 | function balanceOf( 37 | address _user 38 | ) 39 | public 40 | view 41 | returns(uint256 balance) 42 | { 43 | return balances[_user].tokens; 44 | } 45 | 46 | // placeholder function there is much more currently under development 47 | function updateAccount( 48 | address _locker, 49 | uint256 _value 50 | ) 51 | private 52 | returns (uint256) 53 | { 54 | emit Placeholder(_locker, _value); 55 | } 56 | 57 | // this will be called to owner to lock in company tokens. they cannot be claimed until 2020 58 | function lockCompanyFunds() 59 | public 60 | onlyOwner 61 | returns (bool) 62 | { 63 | BrickblockToken _bbt = BrickblockToken(brickBlockTokenAddress); 64 | uint256 _value = _bbt.allowance(brickBlockTokenAddress, address(this)); 65 | require(_value > 0); 66 | _bbt.transferFrom(brickBlockTokenAddress, address(this), _value); 67 | updateAccount(brickBlockTokenAddress, balances[brickBlockTokenAddress].tokens.add(_value)); 68 | emit BBTLocked(brickBlockTokenAddress, _value); 69 | return true; 70 | } 71 | 72 | // this is a basic representation of how locking tokens will look for contributors 73 | function lockBBT() 74 | public 75 | returns (uint256 _value) 76 | { 77 | address user = msg.sender; 78 | BrickblockToken _bbt = BrickblockToken(brickBlockTokenAddress); 79 | _value = _bbt.allowance(user, address(this)); 80 | require(_value > 0); 81 | _bbt.transferFrom(user, address(this), _value); 82 | updateAccount(user, balances[user].tokens.add(_value)); 83 | emit BBTLocked(user, _value); 84 | } 85 | 86 | // the company will only be able to claim tokens through this function 87 | function claimCompanyTokens() 88 | public 89 | onlyOwner 90 | returns (bool) 91 | { 92 | require(block.number > companyShareReleaseBlock); 93 | BrickblockToken _bbt = BrickblockToken(brickBlockTokenAddress); 94 | uint256 _companyTokens = balanceOf(_bbt); 95 | balances[address(this)].tokens = balances[address(this)].tokens.sub(_companyTokens); 96 | balances[owner].tokens = balances[owner].tokens.add(_companyTokens); 97 | updateAccount(brickBlockTokenAddress, 0); 98 | _bbt.transfer(owner, _companyTokens); 99 | emit CompanyTokensReleased(owner, _companyTokens); 100 | } 101 | 102 | // much more functionality is already built and undergoing development and refinement! 103 | } 104 | -------------------------------------------------------------------------------- /contracts/stubs/BrokenRemoteContractStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | 4 | contract BrokenRemoteContractStub { 5 | uint256 public testNumber; 6 | 7 | constructor(uint256 _testNumber) 8 | public 9 | { 10 | testNumber = _testNumber; 11 | } 12 | 13 | function add( 14 | uint256 _num1, 15 | uint256 _num2 16 | ) 17 | public 18 | pure 19 | returns (uint256) 20 | { 21 | // intentionally broken for testing 22 | return _num1 + _num2 + 3; 23 | } 24 | 25 | function setTestNumber(uint256 _number) 26 | public 27 | returns (uint256) 28 | { 29 | testNumber = _number; 30 | return testNumber; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/stubs/RemoteContractStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | 4 | contract RemoteContractStub { 5 | uint256 public testNumber; 6 | address public testAddress; 7 | 8 | constructor(uint256 _testNumber) 9 | public 10 | { 11 | testNumber = _testNumber; 12 | } 13 | 14 | function add( 15 | uint256 _num1, 16 | uint256 _num2 17 | ) 18 | public 19 | pure 20 | returns (uint256) 21 | { 22 | return _num1 + _num2; 23 | } 24 | 25 | function setTestNumber(uint256 _number) 26 | public 27 | returns (bool) 28 | { 29 | testNumber = _number; 30 | 31 | return true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/stubs/RemoteContractUserStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "../interfaces/IRegistry.sol"; 4 | 5 | // shortened version of actual contract in order to save on costs 6 | interface RemoteContractStubInterface { 7 | function testNumber() 8 | external 9 | view 10 | returns (uint256); 11 | 12 | function add( 13 | uint256 _num1, 14 | uint256 _num2 15 | ) 16 | external 17 | pure 18 | returns (uint256); 19 | 20 | function setTestNumber(uint256 _number) 21 | external 22 | returns (bool); 23 | } 24 | 25 | 26 | contract RemoteContractUserStub { 27 | IRegistry private registry; 28 | 29 | constructor(address _registryAddress) 30 | public 31 | { 32 | require(_registryAddress != address(0)); 33 | registry = IRegistry(_registryAddress); 34 | } 35 | 36 | function getRemoteContractStub() 37 | private 38 | view 39 | returns (RemoteContractStubInterface) 40 | { 41 | return RemoteContractStubInterface( 42 | registry.getContractAddress("TestName") 43 | ); 44 | } 45 | 46 | function remoteAdd( 47 | uint256 _num1, 48 | uint256 _num2 49 | ) 50 | public 51 | view 52 | returns (uint256) 53 | { 54 | RemoteContractStubInterface genericRemoteContract = getRemoteContractStub(); 55 | 56 | return genericRemoteContract.add(_num1, _num2); 57 | } 58 | 59 | function remoteTestNumber() 60 | public 61 | view 62 | returns (uint256) 63 | { 64 | RemoteContractStubInterface genericRemoteContract = getRemoteContractStub(); 65 | 66 | return genericRemoteContract.testNumber(); 67 | } 68 | 69 | function remoteSetNumber(uint256 _newNumber) 70 | public 71 | returns (bool) 72 | { 73 | RemoteContractStubInterface genericRemoteContract = getRemoteContractStub(); 74 | require(genericRemoteContract.setTestNumber(_newNumber)); 75 | 76 | return true; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /contracts/stubs/UpgradedPoa.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "../PoaToken.sol"; 4 | 5 | 6 | contract UpgradedPoa is PoaToken { 7 | bool public isUpgraded; 8 | 9 | function setUpgrade() 10 | public 11 | returns (bool) 12 | { 13 | isUpgraded = true; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /contracts/utils/AccessTokenUpgradeExample.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | /* 4 | * This is an example of how we would upgrade the AccessToken contract if we had to. 5 | * Instead of doing a full data migration from ACTv1 to ACTv2, we could make 6 | * use of inheritance to keep the old functionality we want and change what is needed. 7 | * We could then contact the old contract to retrieve old balances. 8 | * 9 | * NOTE: This should probably only be done once because every subsequent 10 | * update will get more confusing. If we really have to update the ACT 11 | * contract we should investigate then whether we should just use 12 | * the same proxy pattern we are using for the POA contract. 13 | */ 14 | 15 | import "../AccessToken.sol"; 16 | 17 | 18 | contract AccessTokenUpgradeExample is AccessToken { 19 | 20 | constructor(address _registry) 21 | public 22 | AccessToken(_registry) 23 | {} 24 | 25 | function balanceOf(address _address) 26 | public 27 | view 28 | returns (uint256) 29 | { 30 | return totalMintedActPerLockedBbkToken == 0 31 | ? 0 32 | : AccessToken( 33 | registry.getContractAddress("AccessTokenOld") 34 | ).balanceOf(_address) 35 | .add(lockedBbkPerUser[_address]) 36 | .mul(totalMintedActPerLockedBbkToken.sub(mintedActPerUser[_address])) 37 | .div(1e18) 38 | .add(mintedActFromPastLockPeriodsPerUser[_address]) 39 | .add(receivedAct[_address]) 40 | .sub(spentAct[_address]); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /contracts/utils/PoaCheckForFunctionCollision.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "../PoaCrowdsale.sol"; 4 | import "../PoaToken.sol"; 5 | 6 | 7 | /** 8 | @title This contract is never deployed; we are letting the compiler tell us if there is ever a 9 | function signature collision between PoaCrowdsale and PoaToken. 10 | 11 | To understand more about what this means, this article does a good job explaining it 12 | https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357 13 | 14 | An example of a failing contract is 15 | 16 | pragma solidity "0.4.25"; 17 | 18 | contract M1 { 19 | function clash550254402() public { } 20 | } 21 | 22 | contract M2 { 23 | function proxyOwner() public { } 24 | } 25 | 26 | contract Child is M1, M2 { } 27 | 28 | */ 29 | 30 | contract PoaCheckForFunctionCollision is PoaCrowdsale, PoaToken { 31 | constructor() public { 32 | // here so solium doesn't complain about an empty contract 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /contracts/utils/WarpTool.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | 4 | // used to change state and move block forward in testrpc 5 | contract WarpTool { 6 | bool public state; 7 | 8 | function warp() 9 | public 10 | { 11 | state = !state; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/POA-FUNDING-FLOW.md: -------------------------------------------------------------------------------- 1 | # POA Token sale steps 2 | 3 | ## 1. [Brickblock] Add issuer address to `PoaManager` 4 | 5 | - Check if issuer exist by calling: 6 | ``` 7 | poaManager.isRegisteredIssuer(address issuer) 8 | ``` 9 | - If the result is false, add issuer to the `PoaManager` 10 | ``` 11 | poaManager.addIssuer(address issuer) 12 | ``` 13 | 14 | ## 2. [Issuer] Deploy Poa Token: 15 | 16 | ### 2.1 Issuer: 17 | 18 | - Deploy POA token by calling: 19 | ``` 20 | poaManager.addNewToken( 21 | bytes32 _name32, // Token name 22 | bytes32 _symbol32, // Token symbol 23 | bytes32 _fiatCurrency32, // Fiat currency 24 | address _custodian, // custodian address 25 | uint256 _totalSupply, 26 | uint256 _startTimeForEthFundingPeriod, 27 | uint256 _durationForEthFundingPeriod, 28 | uint256 _durationForActivationPeriod, 29 | uint256 _fundingGoalInCents 30 | ) 31 | ``` 32 | 33 | ### 2.2 Brickblock: 34 | 35 | - List PoaToken on Poa Manager 36 | ``` 37 | poaManager.listToken(address _tokenAddress) 38 | ``` 39 | 40 | Depending on the sale, issuer should start `Fiat funding` or `Eth Funding` period 41 | 42 | ## 3. [Issuer] Fiat Funding Period 43 | 44 | - To start `Fiat Funding`, -only- issuer must call: 45 | 46 | ``` 47 | poatoken.startFiatSale() 48 | ``` 49 | 50 | - He/she is responsible for adding correct investors with investment amounts by calling: 51 | 52 | ``` 53 | poatoken.buyFiat 54 | ( 55 | address _contributor, // Investor address 56 | uint256 _amountInCents // Investment amount in cents. Ex. for 1000 Eur the amount should be 100000 57 | ) 58 | ``` 59 | 60 | - If issuer makes a mistake it can be reverted by calling: 61 | 62 | ``` 63 | function removeFiat 64 | ( 65 | address _contributor, 66 | uint256 _amountInCents 67 | ) 68 | ``` 69 | 70 | - If funding goal is met at this stage, contract automatically switches to `FundingSuccessful` stage. 71 | - In `Fiat Funding` stage, issuer can always cancel the contract in case something goes wrong. 72 | 73 | ``` 74 | poatoken.cancelFunding() 75 | ``` 76 | 77 | ## 4. [Issuer] Eth Funding Period 78 | 79 | - To start `Eth Funding`, anyone -including issuer- can call: 80 | 81 | ``` 82 | poatoken.startEthSale() 83 | ``` 84 | 85 | Please remember, it only starts if the block time is greater than `_startTimeForEthFundingPeriod` mentioned above. Otherwise transaction fails. 86 | 87 | - If funding goal is met at this stage, contract automatically switches to `FundingSuccessful` stage. 88 | 89 | ## 5. Funding Succesful Stage 90 | 91 | - Issuer should check the fee amount required to activate the contract: 92 | 93 | ``` 94 | poatoken.calculateTotalFee() 95 | ``` 96 | 97 | ### 5.1 Execute required functions before activation: 98 | 99 | - `poatoken.payActivationFee()` should be called by any of `Issuer`, `Custodian` or `Brickblock`. If you are wondering why we made this function public to everyone, please see the explanation for this function in `PoaCrowdsale.sol` contract. 100 | - Custodian should update proof of custody (Hash of legal papers uploaded to IPFS) by calling: 101 | 102 | ``` 103 | poatoken.updateProofOfCustody(bytes32[2] _ipfsHash) 104 | ``` 105 | 106 | - Note: The order for `payActivationFee` and `updateProofOfCustody` does not matter. 107 | 108 | ### 5.2 Activation 109 | 110 | - Custodian must activate contract after activation fee paid and proof of custody updated before activation period ends, by calling: 111 | 112 | ``` 113 | poatoken.activate() 114 | ``` 115 | 116 | ## 6. Active Stage 117 | 118 | - Issuer can claim Eth, which was funded during `Eth Funding` period, by calling: 119 | 120 | ``` 121 | poatoken.claim() 122 | ``` 123 | -------------------------------------------------------------------------------- /docs/TEST-AGAINST-GETH.md: -------------------------------------------------------------------------------- 1 | # Testing Against Geth 2 | Before submitting contracts for audit or deploying to Mainnet, we always run our tests against `geth` because the [majority of Mainnet Ethereum nodes](https://ethstats.net) run `geth`. This gives us the greatest possible certainty that everything will work as expected on Mainnet. 3 | 4 | ## Requirements 5 | * [geth](https://geth.ethereum.org) `^1.8.10` 6 | * [yarn](https://yarnpkg.com) `^1.6` 7 | 8 | ## How to run tests against a private `geth` blockchain 9 | 10 | Open 2 terminal windows, `cd` into the project folder on both and then run: 11 | 12 | | Terminal 1 | Terminal 2 | 13 | |---|---| 14 | | Start a local geth blockchain | Run the tests against your local geth blockchain | 15 | | `yarn start:geth` | `yarn truffle test test/main-tests/.js --network devGeth` | 16 | 17 | ### Notes: 18 | - You can run all tests in the `./test/main-tests/` folder against `geth` 19 | - The initial geth config is in [./scripts/geth/genesis.json](./scripts/geth/genesis.json) 20 | - Find the arguments used for running geth in [./scripts/geth/start-geth.sh](./scripts/geth/start-geth.sh) file. 21 | - `yarn start:geth` first always deletes old chain data and starts with a fresh state 22 | - Tests run _a lot_ slower against `geth`. This is expected and also the reason why we run tests against `ganach` during normal development 23 | - **All JavaScript files loaded by geth via the `--preload` argument only support ES5.** Why? Because geth uses [otto](https://github.com/robertkrimen/otto) as its JavaScript interpreter. See [https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console#caveat](https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console#caveat) 24 | 25 | 26 | ### Auto Mining 27 | [./scripts/geth/auto-mine.js](./scripts/geth/auto-mine.js) makes geth auto mine blocks (which it doesn't do per default!). If you don't want automining, e.g. for debugging purposes, outcomment the `--preload` argument in [./scripts/geth/start-geth.sh](./scripts/geth/start-geth.sh). 28 | 29 | ### Account Management 30 | [./scripts/geth/manage-accounts.js](./scripts/geth/manage-accounts.js) unlocks 10 pre-made accounts for 60 minutes which should be enough for the test duration. 31 | 32 | ### Genesis Block 33 | [./scripts/geth/genesis.json](./scripts/geth/genesis.json) configures the genesis block for geth. It creates 10 accounts filled with 100 ETH each in the `alloc` section. -------------------------------------------------------------------------------- /docs/UPGRADING-POA-TOKEN.md: -------------------------------------------------------------------------------- 1 | ## Upgrading 2 | `PoA`s are upgradeable. A PoA is composed of a `Proxy` contract which makes use of `PoaToken` and `PoaCrowdsale` which both inherit from `PoaCommon`. PoaManager uses the currently set `PoaToken` and `PoaCrowdsale` contracts set in the `Registry` contract when deploying a new `PoA`. 3 | 4 | There are two components specifically that are upgradeable: `PoaToken` and `PoaCrowdsale`. If the inherited contract `PoaCommon` needs to be upgraded, BOTH `PoaToken` and `PoaCrowdsale` need to be upgraded. 5 | 6 | ### Before Upgrading 7 | 1. Write the new version which inherits from the old version: 8 | ``` 9 | contract PoaTokenV2 is PoaToken { 10 | 11 | } 12 | ``` 13 | 2. Test extensively! 14 | 1. Test every piece of storage manually making sure that no storage is being overwritten. 15 | 1. Test on testnet using a contract with already existing state mirroring something on mainnet as closely as possible. 16 | 1. `pause()` the token. 17 | 18 | ### The Actual Upgrade 19 | At a high level the following must happen: 20 | 1. deploy the new contract 21 | * `PoaToken` or `PoaCrowdsale` or both 22 | 1. call the appropriate function on `PoaProxy` to upgrade 23 | * `proxyChangeTokenMaster` if changing `PoaToken` 24 | * `proxyChangeCrowdsaleMaster` if changing `PoaCrowdsale` 25 | * if both, call each with the appropriate contract address 26 | 1. call the appropriate initializers (if necessary) to set any new storage that is needed. 27 | 1. repeat above steps for any other already deployed `PoA`s that need to be upgraded. 28 | 29 | ### How to Upgrade 30 | The upgraded master contracts can be deployed by anyone, this means that the owner does not need to be involved in the deployment process. Feel free to deploy the upgraded contract through truffle, remix, or myetherwallet. Just make sure to note the address once deployed. 31 | 32 | Because the owner should be in cold store at this point, all upgrade transactions must be done with the cold store tool. Transactions for this would look like the following: 33 | 34 | **Upgrading the PoaToken Portion** 35 | ``` 36 | { 37 | "defaults": { 38 | "to": "", 39 | "from": "", 40 | "fn": "proxyChangeTokenMaster(address)" 41 | }, 42 | "txs" : [ 43 | { 44 | "args": [""] 45 | } 46 | ], 47 | } 48 | ``` 49 | 50 | **Upgrading the PoaCrowdsale Portion** 51 | ``` 52 | { 53 | "defaults": { 54 | "to": "", 55 | "from": "", 56 | "fn": "proxyChangeCrowdsaleMaster(address)" 57 | }, 58 | "txs" : [ 59 | { 60 | "args": [""] 61 | } 62 | ], 63 | } 64 | ``` 65 | 66 | **Upgrading both PoaToken and PoaCrowdsale** 67 | ``` 68 | { 69 | "defaults": { 70 | "to": "", 71 | "from": "" 72 | }, 73 | "txs" : [ 74 | { 75 | "fn": "proxyChangeTokenMaster(address)", 76 | "args": [""] 77 | }, 78 | { 79 | "fn": "proxyChangeCrowdsaleMaster(address)", 80 | "args": [""] 81 | }, 82 | ], 83 | } 84 | ``` 85 | 86 | ### After Upgrading 87 | 1. `unpause()` the token. 88 | 1. check on any viewable state to ensure that no storage has been overwritten in some way. 89 | 1. change registry entries if this upgrade should be for all future contracts. -------------------------------------------------------------------------------- /docs/architecture/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brickblock-io/smart-contracts/74f52a973cfc32e99e388076bf915c7ffc23fcc3/docs/architecture/architecture.png -------------------------------------------------------------------------------- /docs/audits/2017-12-01 - BrickblockToken (BBK) - Audit by SmartDec v1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brickblock-io/smart-contracts/74f52a973cfc32e99e388076bf915c7ffc23fcc3/docs/audits/2017-12-01 - BrickblockToken (BBK) - Audit by SmartDec v1.pdf -------------------------------------------------------------------------------- /docs/audits/2018-01-17 - BrickblockToken (BBK) - Audit by SmartDec v2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brickblock-io/smart-contracts/74f52a973cfc32e99e388076bf915c7ffc23fcc3/docs/audits/2018-01-17 - BrickblockToken (BBK) - Audit by SmartDec v2.pdf -------------------------------------------------------------------------------- /docs/audits/2018-01-30 - BrickblockToken (BBK) - Audit by SmartDec v3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brickblock-io/smart-contracts/74f52a973cfc32e99e388076bf915c7ffc23fcc3/docs/audits/2018-01-30 - BrickblockToken (BBK) - Audit by SmartDec v3.pdf -------------------------------------------------------------------------------- /docs/audits/2018-03 - Full Ecosystem [Phase 1] - Audit by ConsenSys v1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brickblock-io/smart-contracts/74f52a973cfc32e99e388076bf915c7ffc23fcc3/docs/audits/2018-03 - Full Ecosystem [Phase 1] - Audit by ConsenSys v1.pdf -------------------------------------------------------------------------------- /docs/audits/2018-03 - Full Ecosystem [Phase 1] - Audit by ConsenSys v2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brickblock-io/smart-contracts/74f52a973cfc32e99e388076bf915c7ffc23fcc3/docs/audits/2018-03 - Full Ecosystem [Phase 1] - Audit by ConsenSys v2.pdf -------------------------------------------------------------------------------- /docs/audits/2018-09-20 - Full Ecosystem [Phase 2] - Audit by ConsenSys final.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brickblock-io/smart-contracts/74f52a973cfc32e99e388076bf915c7ffc23fcc3/docs/audits/2018-09-20 - Full Ecosystem [Phase 2] - Audit by ConsenSys final.pdf -------------------------------------------------------------------------------- /docs/bug-bounties/2018-09-18 - Full Ecosystem.md: -------------------------------------------------------------------------------- 1 | # Bug Bounty Program 2 | 3 | All contracts under review have been audited by [ConsenSys Diligence](https://consensys.net/diligence/). The audit reports are published under [/audits](/audits). 4 | 5 | ## Platform: Solidified 6 | All issues must be submitted through https://solidified.io. The official URL for this bug bounty is https://web.solidified.io/contract/5b9e754ffd407500116a9d0f 7 | 8 | ## Rewards 9 | 10 | ### Critical Bugs: 100 ETH 11 | Bugs that enable an attacker to steal ETH or BBK tokens or ACT tokens or POA tokens from any of the contracts under review or participants in our ecosystem holding BBK, ACT or POA. 12 | 13 | ### Major Bugs: 50 ETH 14 | Bugs that would lock or render our POA tokens, BBK tokens or ACT tokens otherwise unusable and/or make said tokens vulnerable to attacks. 15 | 16 | ### Minor Bugs: 3 ETH 17 | Bugs that break specified or reasonably assumed contract behavior but do not enable an attacker to steal, freeze or destroy funds. 18 | 19 | ### Available pool: 200 ETH 20 | 21 | ## Rules 22 | * Public disclosure of vulnerabilities outside of Solidified render the publisher and their associates ineligible for any bounty rewards. 23 | * Current and former Brickblock employees and contractors as well as ConsenSys Diligence employees and contractors are not eligible for bounty rewards. 24 | * The submission’s quality will factor into the level of compensation. A high-quality submission includes an explanation of how the bug can be reproduced, a failing test case, and ideally a fix that makes the test case pass. High-quality submissions may be awarded higher amounts than the amounts specified above. 25 | * Excluded are scenarios that rely on explicit user action like phishing, social engineering etc. 26 | 27 | ### Issues In Scope (including but not limited to listed bullet points) 28 | * Being able to steal funds 29 | * Being able to freeze funds or render them inaccessible by their owners 30 | * Being able to perform replay attacks 31 | 32 | ### Issues Out of Scope 33 | * Issues that have already been submitted by another user 34 | * Issues outlined in WORST CASE SCENARIOS 35 | * Issues outlined in the AUDIT REPORT 36 | 37 | ### Contracts in Scope 38 | The official commit hash at which to review is [46c9d13c14401eb0020d33588881208b38e2854f](/tree/46c9d13c14401eb0020d33588881208b38e2854f) 39 | 40 | * [AccessToken.sol](/contracts/AccessToken.sol) 41 | * [BrickblockAccount.sol](/contracts/BrickblockAccount.sol) 42 | * [BrickblockToken.sol](/contracts/BrickblockToken.sol) 43 | * [ContractRegistry.sol](/contracts/ContractRegistry.sol) 44 | * [ExchangeRateProvider.sol](/contracts/ExchangeRateProvider.sol) 45 | * [ExchangeRates.sol](/contracts/ExchangeRates.sol) 46 | * [FeeManager.sol](/contracts/FeeManager.sol) 47 | * [PoaCommon.sol](/contracts/PoaCommon.sol) 48 | * [PoaCrowdsale.sol](/contracts/PoaCrowdsale.sol) 49 | * [PoaLogger.sol](/contracts/PoaLogger.sol) 50 | * [PoaManager.sol](/contracts/PoaManager.sol) 51 | * [PoaProxy.sol](/contracts/PoaProxy.sol) 52 | * [PoaProxyCommon.sol](/contracts/PoaProxyCommon.sol) 53 | * [PoaToken.sol](/contracts/PoaToken.sol) 54 | * [SafeMathPower.sol](/contracts/tools/SafeMathPower.sol) 55 | * [Whitelist.sol](/contracts/Whitelist.sol) 56 | 57 | ### Help 58 | * Read [README.md](/README.md) for technical instructions on how to run our ecosystem locally 59 | * Read [ECOSYSTEM.md](/ECOSYSTEM.md) for an overview of our smart contract structure 60 | * Read [WORST-CASE-SCENARIOS.md](/WORST-CASE-SCENARIOS.md) to learn about accepted risks/tradeoffs and known issues that are not eligible for bounty rewards 61 | 62 | ### Legal 63 | We can end the bug bounty program at any time. Awards are at the sole discretion of the Brickblock bug bounty panel. In addition, we are not able to issue awards to individuals who are on sanctions lists or who are in countries on sanctions lists (e.g. North Korea, Iran, etc). You are responsible for all taxes. All awards are subject to applicable law. Finally, your testing must not violate any law or compromise any data that is not yours. -------------------------------------------------------------------------------- /docs/ecosystem-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brickblock-io/smart-contracts/74f52a973cfc32e99e388076bf915c7ffc23fcc3/docs/ecosystem-diagram.png -------------------------------------------------------------------------------- /docs/sequence-diagrams/BBK-ACT-interaction.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | participant User as BBK Token Holder 3 | participant ACT as AccessToken 4 | participant BBK as BrickblockToken 5 | participant FMR as FeeManager 6 | 7 | Note over User,FMR: User decides to lock BBK in order to participate in ACT distributions 8 | 9 | alt BBK Token Holder locks BBK 10 | User->>BBK: approve(act-address, act-allowance) 11 | User->>+ACT: lockBBK(act-allowance-or-less) 12 | ACT->>-ACT: update total-locked-BBK 13 | end 14 | 15 | Note over User,FMR: A fee is paid into FeeManager and ACT is minted 16 | 17 | alt BBK Token Holder claims ACT 18 | User->>+ACT: balanceOf(user-address) 19 | ACT-->>User: user-balance 20 | User->>+FMR: claimFee(user-balance-or-less) 21 | FMR->>ACT: burn(user-balance-or-less) 22 | FMR->>-User: transfer(user-balance-or-less / act-to-eth-rate) 23 | end 24 | 25 | Note over User,FMR: User decides to unlock BBK, possibly to transfer those tokens 26 | 27 | alt BBK Token Holder unlocks BBK 28 | User->>+ACT: lockedBbkOf(user-address) 29 | ACT-->>User: user-balance-locked-BBK 30 | User->>+ACT: unlockBBK(act-address, user-balance-locked-BBK-or-less) 31 | ACT->>-ACT: update total-locked-BBK 32 | end 33 | -------------------------------------------------------------------------------- /docs/sequence-diagrams/POA-Broker-interaction.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | participant Issuer 3 | participant PMR as PoaManager 4 | participant POA as PoaToken 5 | 6 | Note over Issuer,POA: Precondition: Issuer has been added to PoaManager 7 | Note over Issuer,POA: Precondition: Issuer is in active status 8 | 9 | alt Issuer lists a new POA 10 | Issuer->>+PMR: addNewToken(...) 11 | end 12 | 13 | Note over Issuer,POA: PoaToken is successfully funded and Custodian activates the contract 14 | 15 | alt Issuer claims the ETH from successful funding 16 | Issuer->>+POA: claim() 17 | POA->>Issuer: transfer all ETH from funding 18 | end 19 | -------------------------------------------------------------------------------- /docs/sequence-diagrams/POA-Custodian-interaction.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | participant Custodian 3 | participant POA as PoaToken 4 | participant FMR as FeeManager 5 | 6 | Note over Custodian,FMR: PoaToken has reached Funding goal 7 | 8 | alt Custodian is ready to activate the PoaToken 9 | Custodian->>+POA: activate(ipfsHash-proof-of-custody-document) 10 | POA->>POA: validIpfs(ipfsHash-proof-of-custody-document) 11 | POA->>FMR: payFee(0.5% * funding-goal) 12 | POA->>POA: update claim balance for issuer address with remaining funding-goal ETH 13 | POA->>-POA: enterStage(Active) 14 | end 15 | 16 | Note over Custodian,FMR: The underlying asset has a scheduled payout 17 | 18 | alt Custodian makes a payout 19 | Custodian->>+POA: payout().value(payout-amount) 20 | POA->>FMR: payFee(0.5% * payout-amount) 21 | POA->>-POA: update total-payout-per-token 22 | end 23 | 24 | Note over Custodian,FMR: The underlying asset is no longer tokenized and the PoaToken
contract must close 25 | 26 | alt Custodian terminates the PoaToken 27 | Custodian->>+POA: terminate() 28 | POA->>-POA: enterStage(Terminated) 29 | end 30 | 31 | Note over Custodian,FMR: Even when Terminated payouts can still happen, however they are
expected to stop occuring 32 | 33 | alt Custodian performs the final payout 34 | Custodian->>+POA: payout().value(payout-amount) 35 | POA->>FMR: payFee(0.5% * payout-amount) 36 | POA->>-POA: update total-payout-per-token 37 | end 38 | -------------------------------------------------------------------------------- /docs/sequence-diagrams/POA-Investor-funding-fails.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | participant Investor as POA Token Holder 3 | participant Custodian 4 | participant POA as PoaToken 5 | 6 | Note over Investor,POA: Precondition: Investor is whitelisted to participate in funding a
PoaToken 7 | Note over Investor,POA: Precondition: the PoaToken is in Funding stage 8 | 9 | alt Investor participates in funding 10 | Investor->>POA: buy().value(wei-amount) 11 | end 12 | 13 | Note over Investor,POA: Scenario 1: PoaToken has not reached funding goal by funding-timeout 14 | 15 | alt Investor wants their ETH returned 16 | Investor->>+POA: reclaim() 17 | POA->>POA: sets total-supply to 0 to show that reclaiming has started 18 | POA->>Investor: transfer all ETH from funding for this investor-address 19 | end 20 | 21 | 22 | Note over Investor,POA: Scenario 2: PoaToken reaches funding goal, but Custodian does not activate by activation-timeout 23 | 24 | alt Investor wants their ETH returned 25 | Investor->>+POA: reclaim() 26 | POA->>POA: sets total-supply to 0 to show that reclaiming has started 27 | POA->>Investor: transfer all ETH from funding for this investor-address 28 | end 29 | 30 | -------------------------------------------------------------------------------- /docs/sequence-diagrams/POA-Investor-interaction.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | participant Investor as POA Token Holder 3 | participant Custodian 4 | participant POA as PoaToken 5 | 6 | Note over Investor,POA: Precondition: the PoaToken is in Funding stage 7 | Note over Investor,POA: Precondition: Investor is whitelisted to participate in funding 8 | 9 | alt Investor participates in funding 10 | Investor->>+POA: buy().value(wei-amount) 11 | POA->>Investor: transaction rejected 12 | POA->>POA: check if funding target has been met 13 | 14 | alt PoaToken funding goal reached 15 | POA->>POA: enterStage(FundingSuccessful) 16 | POA-->>Investor: return any extra ETH 17 | end 18 | end 19 | 20 | Note over Investor,POA: PoaToken has reached Funding goal 21 | 22 | alt Custodian activates the contract 23 | Custodian->>+POA: activate() 24 | POA->>POA: enterStage(Terminated) 25 | end 26 | 27 | note over Investor,POA: After PoaToken is Activated, the balances of every token holder
can be shown 28 | 29 | alt Investor wants to see their balance 30 | Investor->>+POA: balanceOf(token-holder-address) 31 | POA-->>Investor: token-holder-balance 32 | end 33 | 34 | Note over Investor,POA: The underlying asset has a scheduled payout 35 | 36 | alt Investor wants to claim ETH from PoaToken 37 | Investor->>+POA: claim() 38 | POA->>POA: update unclaimed balance to 0 39 | POA->>Investor: transfer all ETH accumulated from payouts 40 | end 41 | 42 | note over Investor,POA: PoaToken is ERC20 which allows for token holders to transfer to
other addresses 43 | 44 | alt Investor wants to transfer tokens to another address 45 | Investor->>+POA: transfer(to-address, token-amount) 46 | 47 | alt transfer whitelisting is enabled AND to-address is not whitelisted 48 | POA-->>Investor: transaction rejected 49 | else transfer whitelist is not enabled OR to-address is whitelisted 50 | POA->>POA: update token balances 51 | end 52 | end 53 | 54 | Note over Investor,POA: The underlying asset is no longer tokenized 55 | 56 | alt Custodian terminates the PoaToken 57 | Custodian->>+POA: terminate() 58 | POA->>POA: enterStage(Terminated) 59 | end 60 | 61 | Note over Investor,POA: Even when Terminated claiming can still happen, however payouts
are expected to stop occuring 62 | 63 | alt Investor wants to claim final ETH from PoaToken 64 | Investor->>+POA: claim() 65 | POA->>Investor: all ETH accumulated in payouts 66 | end 67 | -------------------------------------------------------------------------------- /docs/sequence-diagrams/legend.mmd: -------------------------------------------------------------------------------- 1 | sequenceDiagram 2 | participant ActorNickname as Actor Display 3 | participant AnotherContract 4 | participant SmartContract 5 | 6 | Note over ActorNickname,SmartContract: this is a note, it will describe things that are preconditions as well
as other transactions that occur between scenarios 7 | 8 | alt this "alt" block contains one scenario 9 | ActorNickname->>+SmartContract: straight line into a smart contract represents a transaction 10 | SmartContract->>SmartContract: a contract may cause effects on itself 11 | SmartContract->>AnotherContract: there can be many effects in one transaction 12 | SmartContract->>SmartContract: an effect is: sending ETH, updating contract storage, calling other contracts 13 | SmartContract->>SmartContract: this grey bar represents the sequence of effects in one transaction 14 | SmartContract-->>-ActorNickname: a dotted line represents data returned 15 | end 16 | 17 | alt sometimes the call is simple and no data is returned 18 | ActorNickname->>+SmartContract: simple transaction 19 | end 20 | 21 | -------------------------------------------------------------------------------- /docs/sequence-diagrams/render.js: -------------------------------------------------------------------------------- 1 | const childProcess = require('child_process') 2 | const fs = require('fs') 3 | const path = require('path') 4 | 5 | // this helper functions allows us to run a command wrapped in a Promise; this allows for running 6 | // multiple processes at once, and use async control flow like Promise.all 7 | const promiseChildProcess = command => 8 | new Promise((resolve, reject) => { 9 | const proc = childProcess.exec(command) 10 | 11 | proc.stdout.pipe(process.stdout) 12 | proc.stderr.pipe(process.stderr) 13 | 14 | proc.on('exit', code => (code === 0 ? resolve() : reject(command))) 15 | }) 16 | 17 | // all the sequence diagram are using `mmd` which is "mermaid markdown" 18 | // 19 | // https://mermaidjs.github.io/sequenceDiagram.html 20 | const diagramFileRegEx = /\.mmd$/ 21 | 22 | const svgFileRegEx = /\.svg$/ 23 | 24 | const renderAll = async () => { 25 | const fileList = fs.readdirSync(path.resolve(__dirname)).reduce( 26 | (acc, filename) => { 27 | if (diagramFileRegEx.test(filename)) acc.diagrams.push(filename) 28 | 29 | if (svgFileRegEx.test(filename)) acc.svg.push(filename) 30 | 31 | return acc 32 | }, 33 | { diagrams: [], svg: [] } 34 | ) 35 | 36 | // clean rendered files (in case of name changes) 37 | fileList.svg.forEach(filename => 38 | fs.unlinkSync(path.resolve(__dirname, filename)) 39 | ) 40 | 41 | try { 42 | // generate new diagram files 43 | await Promise.all( 44 | fileList.diagrams.map(filename => 45 | promiseChildProcess( 46 | `yarn mmdc -i ${path.resolve(__dirname, filename)} -o ${path.resolve( 47 | __dirname, 48 | filename.replace('.mmd', '.svg') 49 | )}` 50 | ) 51 | ) 52 | ) 53 | } catch (error) { 54 | // eslint-disable-next-line no-console 55 | console.log(`command ${error} failed to execute`) 56 | } 57 | } 58 | 59 | renderAll() 60 | -------------------------------------------------------------------------------- /e2e-tooling/INFO.md: -------------------------------------------------------------------------------- 1 | # e2e-tooling info 2 | 3 | We are using ganache to easily deploy a blockchain for every `portal` review app, and staging deployment. 4 | 5 | When deploying the smart contract ecosystem, we are using the determinstic flag so that the mnemonic is stable over all deployments. This is useful so that you can open MetaMask to a known account set. Specifically: 6 | 7 | ```js 8 | const accounts = { 9 | fiatInvestor: web3.eth.accounts[0], 10 | ethInvestor: web3.eth.accounts[1], 11 | owner: web3.eth.accounts[2], 12 | bonus: web3.eth.accounts[3], 13 | issuer: web3.eth.accounts[4], 14 | custodian: web3.eth.accounts[5], 15 | anyone: web3.eth.accounts[9], 16 | } 17 | ``` 18 | 19 | and the mnemonic is `myth like bonus scare over problem client lizard pioneer submit female` 20 | -------------------------------------------------------------------------------- /e2e-tooling/bootstrap.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const exec = require('child_process').exec 3 | const express = require('express') 4 | const kill = require('tree-kill') 5 | const proxy = require('http-proxy-middleware') 6 | const waitOn = require('wait-on') 7 | 8 | const deploySmartContracts = require('./setup-smart-contract-ecosystem') 9 | 10 | const config = { 11 | ganachePort: 8000, 12 | expressPort: 8545, 13 | } 14 | 15 | const cleanupAndExitWithCode = code => { 16 | kill(process.pid, () => process.exit(code)) 17 | } 18 | 19 | const bootstrap = async () => { 20 | // networkId 4447 is the "standard" when using truffle develop since v4.0.0 21 | // https://github.com/trufflesuite/truffle/releases/tag/v4.0.0 22 | // 23 | // we're using 4448 here so its clear we are using a private testnet 24 | const ganache = exec( 25 | `node ./node_modules/.bin/ganache-cli --deterministic --networkId 4448 --port ${ 26 | config.ganachePort 27 | }` 28 | ) 29 | ganache.stdout.pipe(process.stdout) 30 | ganache.stderr.pipe(process.stderr) 31 | 32 | waitOn( 33 | { 34 | resources: [`http://localhost:${config.ganachePort}`], 35 | delay: 2000, 36 | interval: 1000, 37 | timeout: 30 * 1000, 38 | }, 39 | async () => { 40 | try { 41 | await deploySmartContracts(config.ganachePort) 42 | } catch (error) { 43 | console.log('ERROR: deploySmartContracts failed\n', error) 44 | cleanupAndExitWithCode(1) 45 | } 46 | 47 | const app = express() 48 | app.get('/ganache/health', (req, res) => { 49 | res.send({ 50 | status: 'up', 51 | }) 52 | }) 53 | app.use( 54 | '/ganache', 55 | proxy({ 56 | target: `http://localhost:${config.ganachePort}`, 57 | pathRewrite: { 58 | '/ganache': '/', 59 | }, 60 | }) 61 | ) 62 | app.listen(config.expressPort, () => { 63 | console.log('Server started!') 64 | }) 65 | } 66 | ) 67 | } 68 | 69 | process.on('SIGTERM', () => { 70 | cleanupAndExitWithCode(0) 71 | }) 72 | 73 | try { 74 | bootstrap() 75 | } catch (error) { 76 | console.log('ERROR: bootstrap failed\n', error) 77 | cleanupAndExitWithCode(1) 78 | } 79 | -------------------------------------------------------------------------------- /frozen/contracts/BrickblockAccount.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | import "./interfaces/IRegistry.sol"; 5 | import "./interfaces/IBrickblockToken.sol"; 6 | import "./interfaces/IFeeManager.sol"; 7 | import "./interfaces/IAccessToken.sol"; 8 | 9 | 10 | contract BrickblockAccount is Ownable { 11 | uint8 public constant version = 1; 12 | uint256 public releaseTimeOfCompanyBBKs; 13 | IRegistry private registry; 14 | 15 | constructor( 16 | address _registryAddress, 17 | uint256 _releaseTimeOfCompanyBBKs 18 | ) 19 | public 20 | { 21 | // solium-disable-next-line security/no-block-members 22 | require(_releaseTimeOfCompanyBBKs > block.timestamp); 23 | releaseTimeOfCompanyBBKs = _releaseTimeOfCompanyBBKs; 24 | registry = IRegistry(_registryAddress); 25 | } 26 | 27 | function pullFunds() 28 | external 29 | onlyOwner 30 | returns (bool) 31 | { 32 | IBrickblockToken bbk = IBrickblockToken( 33 | registry.getContractAddress("BrickblockToken") 34 | ); 35 | uint256 _companyFunds = bbk.balanceOf(address(bbk)); 36 | 37 | return bbk.transferFrom(address(bbk), address(this), _companyFunds); 38 | } 39 | 40 | function lockBBK(uint256 _value) 41 | external 42 | onlyOwner 43 | returns (bool) 44 | { 45 | IAccessToken act = IAccessToken( 46 | registry.getContractAddress("AccessToken") 47 | ); 48 | IBrickblockToken bbk = IBrickblockToken( 49 | registry.getContractAddress("BrickblockToken") 50 | ); 51 | 52 | require(bbk.approve(address(act), _value)); 53 | 54 | return act.lockBBK(_value); 55 | } 56 | 57 | function unlockBBK(uint256 _value) 58 | external 59 | onlyOwner 60 | returns (bool) 61 | { 62 | IAccessToken act = IAccessToken( 63 | registry.getContractAddress("AccessToken") 64 | ); 65 | 66 | return act.unlockBBK(_value); 67 | } 68 | 69 | function claimFee(uint256 _value) 70 | external 71 | onlyOwner 72 | returns (bool) 73 | { 74 | IFeeManager fmr = IFeeManager( 75 | registry.getContractAddress("FeeManager") 76 | ); 77 | 78 | return fmr.claimFee(_value); 79 | } 80 | 81 | function withdrawEthFunds( 82 | address _address, 83 | uint256 _value 84 | ) 85 | external 86 | onlyOwner 87 | returns (bool) 88 | { 89 | require(address(this).balance >= _value); 90 | _address.transfer(_value); 91 | 92 | return true; 93 | } 94 | 95 | function withdrawActFunds( 96 | address _address, 97 | uint256 _value 98 | ) 99 | external 100 | onlyOwner 101 | returns (bool) 102 | { 103 | IAccessToken act = IAccessToken( 104 | registry.getContractAddress("AccessToken") 105 | ); 106 | 107 | return act.transfer(_address, _value); 108 | } 109 | 110 | function withdrawBbkFunds( 111 | address _address, 112 | uint256 _value 113 | ) 114 | external 115 | onlyOwner 116 | returns (bool) 117 | { 118 | // solium-disable-next-line security/no-block-members 119 | require(block.timestamp >= releaseTimeOfCompanyBBKs); 120 | IBrickblockToken bbk = IBrickblockToken( 121 | registry.getContractAddress("BrickblockToken") 122 | ); 123 | 124 | return bbk.transfer(_address, _value); 125 | } 126 | 127 | // ensure that we can be paid ether 128 | function() 129 | public 130 | payable 131 | {} 132 | } 133 | -------------------------------------------------------------------------------- /frozen/contracts/ContractRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 4 | 5 | 6 | contract ContractRegistry is Ownable { 7 | uint8 public constant version = 1; 8 | 9 | mapping (bytes32 => address) private contractAddresses; 10 | 11 | event UpdateContract(string name, address indexed contractAddress); 12 | 13 | /** 14 | @notice Ensures that a given address is a contract by making sure it has code. 15 | */ 16 | function isContract(address _address) 17 | private 18 | view 19 | returns (bool) 20 | { 21 | uint256 _size; 22 | assembly { _size := extcodesize(_address) } 23 | 24 | return _size > 0; 25 | } 26 | 27 | function updateContractAddress( 28 | string _name, 29 | address _address 30 | ) 31 | public 32 | onlyOwner 33 | returns (address) 34 | { 35 | require(isContract(_address)); 36 | require(_address != contractAddresses[keccak256(_name)]); 37 | 38 | contractAddresses[keccak256(_name)] = _address; 39 | emit UpdateContract(_name, _address); 40 | 41 | return _address; 42 | } 43 | 44 | function getContractAddress(string _name) 45 | public 46 | view 47 | returns (address) 48 | { 49 | require(contractAddresses[keccak256(_name)] != address(0)); 50 | 51 | return contractAddresses[keccak256(_name)]; 52 | } 53 | 54 | function getContractAddress32(bytes32 _name32) 55 | public 56 | view 57 | returns (address) 58 | { 59 | require(contractAddresses[_name32] != address(0)); 60 | 61 | return contractAddresses[_name32]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /frozen/contracts/stubs/BrickblockFountainStub.sol: -------------------------------------------------------------------------------- 1 | // THIS IS EXAMPLE CODE ONLY AND THE FUNCTIONS MOST LIKELY WILL CHANGE 2 | pragma solidity ^0.4.18; 3 | 4 | import "../BrickblockToken.sol"; 5 | import "frozen-zeppelin-solidity/contracts/math/SafeMath.sol"; 6 | import "frozen-zeppelin-solidity/contracts/ownership/Ownable.sol"; 7 | 8 | 9 | contract BrickblockFountainStub is Ownable { 10 | using SafeMath for uint256; 11 | 12 | struct Account { 13 | uint256 tokens; 14 | uint256 lastCheck; 15 | uint256 tokenHours; 16 | } 17 | 18 | mapping (address => Account) balances; 19 | 20 | // this will be set to the estimated block that will occur on November 30th 2020 21 | uint256 public constant companyShareReleaseBlock = 1234567; 22 | address public brickBlockTokenAddress; 23 | 24 | event BBTLocked(address _locker, uint256 _value); 25 | event CompanyTokensReleased(address _owner, uint256 _tokenAmount); 26 | event Placeholder(address _address, uint256 _value); 27 | 28 | function BrickblockFountainStub(address _brickBlockTokenAddress) 29 | public 30 | { 31 | require(_brickBlockTokenAddress != address(0)); 32 | brickBlockTokenAddress = _brickBlockTokenAddress; 33 | } 34 | 35 | // basic implementation of balance return 36 | function balanceOf(address _user) 37 | public 38 | view 39 | returns(uint256 balance) 40 | { 41 | return balances[_user].tokens; 42 | } 43 | 44 | // placeholder function there is much more currently under development 45 | function updateAccount(address _locker, uint256 _value) 46 | private 47 | returns (uint256) 48 | { 49 | Placeholder(_locker, _value); 50 | } 51 | 52 | // this will be called to owner to lock in company tokens. they cannot be claimed until 2020 53 | function lockCompanyFunds() 54 | public 55 | onlyOwner 56 | returns (bool) 57 | { 58 | BrickblockToken _bbt = BrickblockToken(brickBlockTokenAddress); 59 | uint256 _value = _bbt.allowance(brickBlockTokenAddress, this); 60 | require(_value > 0); 61 | _bbt.transferFrom(brickBlockTokenAddress, this, _value); 62 | updateAccount(brickBlockTokenAddress, balances[brickBlockTokenAddress].tokens.add(_value)); 63 | BBTLocked(brickBlockTokenAddress, _value); 64 | return true; 65 | } 66 | 67 | // this is a basic representation of how locking tokens will look for contributors 68 | function lockBBT() 69 | public 70 | returns (uint256 _value) 71 | { 72 | address user = msg.sender; 73 | BrickblockToken _bbt = BrickblockToken(brickBlockTokenAddress); 74 | _value = _bbt.allowance(user, this); 75 | require(_value > 0); 76 | _bbt.transferFrom(user, this, _value); 77 | updateAccount(user, balances[user].tokens.add(_value)); 78 | BBTLocked(user, _value); 79 | } 80 | 81 | // the company will only be able to claim tokens through this function 82 | function claimCompanyTokens() 83 | public 84 | onlyOwner 85 | returns (bool) 86 | { 87 | require(block.number > companyShareReleaseBlock); 88 | BrickblockToken _bbt = BrickblockToken(brickBlockTokenAddress); 89 | uint256 _companyTokens = balanceOf(_bbt); 90 | balances[this].tokens = balances[this].tokens.sub(_companyTokens); 91 | balances[owner].tokens = balances[owner].tokens.add(_companyTokens); 92 | updateAccount(brickBlockTokenAddress, 0); 93 | _bbt.transfer(owner, _companyTokens); 94 | CompanyTokensReleased(owner, _companyTokens); 95 | } 96 | 97 | // much more functionality is already built and undergoing development and refinement! 98 | 99 | } 100 | -------------------------------------------------------------------------------- /frozen/contracts/tools/WarpTool.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | 4 | // used to change state and move block forward in testrpc 5 | contract WarpTool { 6 | 7 | bool public state; 8 | 9 | function warp() 10 | public 11 | { 12 | state = !state; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frozen/migrations/.git-empty-folder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brickblock-io/smart-contracts/74f52a973cfc32e99e388076bf915c7ffc23fcc3/frozen/migrations/.git-empty-folder -------------------------------------------------------------------------------- /frozen/test/helpers/general.js: -------------------------------------------------------------------------------- 1 | // snapshot from commit hash 60b8371a5caf75e37be74d809a24447e2e366cca 2 | 3 | const assert = require('assert') 4 | 5 | const WarpTool = artifacts.require('WarpTool') 6 | 7 | const BigNumber = require('bignumber.js') 8 | 9 | const warpBlocks = blocks => { 10 | return new Promise(async resolve => { 11 | const warpTool = await WarpTool.new() 12 | for (let i = 0; i < blocks - 1; i++) { 13 | // log every fifth block 14 | if (i % 5 === 0) { 15 | // eslint-disable-next-line no-console 16 | console.log('💫 warping blocks') 17 | } 18 | 19 | await warpTool.warp() 20 | } 21 | 22 | // eslint-disable-next-line no-console 23 | console.log('✅ warp complete') 24 | resolve(true) 25 | }) 26 | } 27 | 28 | const sendTransaction = (web3, args) => { 29 | return new Promise(function(resolve, reject) { 30 | web3.eth.sendTransaction(args, (err, res) => { 31 | if (err) { 32 | reject(err) 33 | } else { 34 | resolve(res) 35 | } 36 | }) 37 | }) 38 | } 39 | 40 | const testWillThrow = async (fn, args) => { 41 | try { 42 | await fn.apply(null, args) 43 | assert(false, 'the contract should throw here') 44 | } catch (error) { 45 | assert( 46 | /invalid opcode/.test(error) || /revert/.test(error), 47 | `the error message should be invalid opcode or revert, the error was ${error}` 48 | ) 49 | } 50 | } 51 | 52 | const getEtherBalance = address => { 53 | return new Promise((resolve, reject) => { 54 | web3.eth.getBalance(address, (err, res) => { 55 | if (err) reject(err) 56 | 57 | resolve(res) 58 | }) 59 | }) 60 | } 61 | 62 | const getReceipt = txHash => { 63 | // seems that sometimes actual transaction is returned instead of txHash 64 | if (typeof txHash === 'object') { 65 | return txHash.receipt 66 | } 67 | 68 | return new Promise(function(resolve, reject) { 69 | web3.eth.getTransactionReceipt(txHash, (err, res) => { 70 | if (err) { 71 | reject(err) 72 | } 73 | 74 | resolve(res) 75 | }) 76 | }) 77 | } 78 | 79 | const gasPrice = new BigNumber(30e9) 80 | const bigZero = new BigNumber(0) 81 | 82 | module.exports = { 83 | bigZero, 84 | gasPrice, 85 | getEtherBalance, 86 | getReceipt, 87 | sendTransaction, 88 | testWillThrow, 89 | warpBlocks, 90 | } 91 | -------------------------------------------------------------------------------- /frozen/test/helpers/stress/cpoa.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const BigNumber = require('bignumber.js') 3 | const CustomPoaToken = artifacts.require('CustomPoaToken') 4 | 5 | const { getEtherBalance } = require('../cpoa') 6 | 7 | /* eslint-disable no-console */ 8 | 9 | const getAccountInformation = async (address, contract) => { 10 | const etherBalance = await getEtherBalance(address) 11 | const tokenBalance = await contract.balanceOf(address) 12 | const currentPayout = await contract.currentPayout(address, true) 13 | 14 | return { 15 | etherBalance, 16 | tokenBalance, 17 | currentPayout, 18 | } 19 | } 20 | 21 | const activeContract = async ( 22 | _name, 23 | _symbol, 24 | _owner, 25 | _issuer, 26 | _custodian, 27 | _timeoutBlock, 28 | _totalSupply, 29 | _fundingGoal, 30 | _investors 31 | ) => { 32 | const fundingGoal = new BigNumber(_fundingGoal) 33 | const contract = await CustomPoaToken.new( 34 | _name, 35 | _symbol, 36 | _issuer, 37 | _custodian, 38 | _timeoutBlock, 39 | _totalSupply, 40 | _fundingGoal 41 | ) 42 | let remainingFunding = fundingGoal.sub(await contract.fundedAmount()) 43 | await Promise.all( 44 | _investors.map(investor => contract.whitelistAddress(investor)) 45 | ) 46 | let loopCounter = 0 47 | while (remainingFunding.greaterThan(0)) { 48 | const stage = await contract.stage() 49 | assert.equal(stage.toString(), '0', 'should be in funding stage') 50 | const investAmount = BigNumber.min( 51 | remainingFunding, 52 | BigNumber.random(18) 53 | .mul(fundingGoal.div(6)) 54 | .floor() 55 | ) 56 | 57 | const investor = _investors[loopCounter % _investors.length] 58 | const whitelisted = await contract.whitelisted(investor) 59 | assert(whitelisted, 'the investor should be whiteliseted') 60 | 61 | await contract.buy({ 62 | from: investor, 63 | value: investAmount, 64 | }) 65 | 66 | const newFundedAmount = await contract.fundedAmount() 67 | remainingFunding = fundingGoal.sub(newFundedAmount) 68 | 69 | console.log( 70 | 'remaining funding', 71 | remainingFunding.div(1e18).toString(), 72 | 'invest amount', 73 | investAmount.div(1e18).toString() 74 | ) 75 | 76 | loopCounter++ 77 | } 78 | 79 | assert.equal( 80 | (await contract.stage()).toString(), 81 | '1', 82 | 'should be in penidng stage now' 83 | ) 84 | const fee = await contract.calculateFee(fundingGoal) 85 | await contract.activate({ from: _custodian, value: fee }) 86 | console.log('claiming for OWNER (activation fee)') 87 | await contract.claim({ from: _owner }) 88 | console.log('claiming for CUSTODIAN (activation contract value)') 89 | await contract.claim.sendTransaction({ 90 | from: _custodian, 91 | }) 92 | assert.equal( 93 | (await contract.stage()).toString(), 94 | '3', 95 | 'should be in active stage now' 96 | ) 97 | return contract 98 | } 99 | 100 | const claimAll = async (cpoa, investorAccounts) => { 101 | const payouts = investorAccounts.map(() => new BigNumber(0)) 102 | for (let i = 0; i < investorAccounts.length; i += 1) { 103 | try { 104 | const investor = investorAccounts[i] 105 | const claimableAmount = await cpoa.currentPayout(investor, true) 106 | assert(claimableAmount.greaterThan(0), "0 balance won't claim") 107 | console.log( 108 | `claiming for ${investor} ${claimableAmount.div(1e18).toString()}` 109 | ) 110 | const meta = await cpoa.claim({ from: investor, gasPrice: 0 }) 111 | const payoutValue = meta.logs[0].args.payout 112 | payouts[i] = payoutValue 113 | } catch (error) { 114 | assert( 115 | !/invalid opcode/.test(error), 116 | 'Claim Failed(', 117 | await web3.eth.getBalance(cpoa.address), 118 | ') : ' + error.message 119 | ) 120 | console.error(error) 121 | } 122 | } 123 | 124 | return payouts 125 | } 126 | 127 | /* eslint-enable no-console */ 128 | 129 | module.exports = { 130 | getAccountInformation, 131 | activeContract, 132 | claimAll, 133 | } 134 | -------------------------------------------------------------------------------- /frozen/test/main-tests/ContractRegistry.js: -------------------------------------------------------------------------------- 1 | const ContractRegistry = artifacts.require('ContractRegistry') 2 | const RemoteContractStub = artifacts.require('stubs/RemoteContractStub') 3 | const BrokenRemoteContractStub = artifacts.require( 4 | 'stubs/BrokenRemoteContractStub' 5 | ) 6 | const RemoteContractUserStub = artifacts.require('stubs/RemoteContractUserStub') 7 | const { testWillThrow } = require('../helpers/general') 8 | 9 | const assert = require('assert') 10 | const BigNumber = require('bignumber.js') 11 | 12 | describe('when using the contract registry', () => { 13 | contract('ContractRegistry', accounts => { 14 | const initialTestNumber = new BigNumber(123) 15 | const notOwner = accounts[1] 16 | const contractNameInRegistry = 'TestName' 17 | let reg 18 | let brokenGrc 19 | let fixedGrc 20 | let grcu 21 | 22 | before('setup Registry', async () => { 23 | reg = await ContractRegistry.new() 24 | brokenGrc = await BrokenRemoteContractStub.new(initialTestNumber) 25 | grcu = await RemoteContractUserStub.new(reg.address) 26 | }) 27 | 28 | it('should error when no address for string key', async () => { 29 | await testWillThrow(reg.getContractAddress, [contractNameInRegistry]) 30 | }) 31 | 32 | it('should NOT set an address if NOT owner', async () => { 33 | await testWillThrow(reg.updateContractAddress, [ 34 | contractNameInRegistry, 35 | brokenGrc.address, 36 | { from: notOwner }, 37 | ]) 38 | }) 39 | 40 | it('should set an address', async () => { 41 | await reg.updateContractAddress(contractNameInRegistry, brokenGrc.address) 42 | const postValue = await reg.getContractAddress(contractNameInRegistry) 43 | assert.equal( 44 | postValue, 45 | brokenGrc.address, 46 | 'the address should be set to the grc address' 47 | ) 48 | }) 49 | 50 | describe('when using the remote contract through the registry', () => { 51 | it('should get the testNumber from remote contract', async () => { 52 | const testNumber = await grcu.remoteTestNumber() 53 | 54 | assert.equal(testNumber.toString(), initialTestNumber.toString()) 55 | }) 56 | 57 | it('should set the testNumber on remote contract', async () => { 58 | const newNumber = new BigNumber(321) 59 | const preNumber = await grcu.remoteTestNumber() 60 | 61 | await grcu.remoteSetNumber(newNumber) 62 | 63 | const postNumber = await grcu.remoteTestNumber() 64 | 65 | assert( 66 | preNumber.toString() != postNumber.toString(), 67 | 'the number should be different' 68 | ) 69 | assert.equal(newNumber.toString(), postNumber.toString()) 70 | }) 71 | 72 | it('should return an incorrect value when adding on broken contract', async () => { 73 | const brokenValue = await grcu.remoteAdd(1, 1) 74 | assert.equal( 75 | brokenValue.toString(), 76 | new BigNumber(2).add(3).toString(), 77 | 'the broken value should be the two numbers added plus 3' 78 | ) 79 | }) 80 | }) 81 | 82 | describe('when updating the broken contract in the registry', () => { 83 | it('should change the contract address in registry', async () => { 84 | fixedGrc = await RemoteContractStub.new(initialTestNumber) 85 | await reg.updateContractAddress( 86 | contractNameInRegistry, 87 | fixedGrc.address 88 | ) 89 | const updatedAddress = await reg.getContractAddress( 90 | contractNameInRegistry 91 | ) 92 | 93 | assert.equal( 94 | updatedAddress, 95 | fixedGrc.address, 96 | 'the updated address should match the new contract address' 97 | ) 98 | }) 99 | 100 | it('should now return the correct value', async () => { 101 | const value = await grcu.remoteAdd(1, 1) 102 | assert.equal( 103 | value.toString(), 104 | new BigNumber(2).toString(), 105 | 'the value should match the expected value' 106 | ) 107 | }) 108 | 109 | it('should not allow updating with the same address', async () => { 110 | await testWillThrow(reg.updateContractAddress, [ 111 | contractNameInRegistry, 112 | fixedGrc.address, 113 | ]) 114 | }) 115 | 116 | it('should not allow updating when the address is not a contract', async () => { 117 | await testWillThrow(reg.updateContractAddress, [ 118 | contractNameInRegistry, 119 | '0x00000000000000000000000000000000', 120 | ]) 121 | }) 122 | }) 123 | }) 124 | }) 125 | -------------------------------------------------------------------------------- /frozen/truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | networks: { 3 | dev: { 4 | host: 'localhost', 5 | port: 9545, 6 | network_id: 4447, 7 | gas: 8e6, 8 | gasPrice: 5e9, 9 | }, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /helpers/general.js: -------------------------------------------------------------------------------- 1 | const send = (method, params = []) => 2 | web3.currentProvider.send({ id: 0, jsonrpc: '2.0', method, params }) 3 | 4 | /* 5 | * IMPORTANT 6 | * We should always use this function to create the transaction config object 7 | * that gets passed to the actual smart contract calls to ensure we're using 8 | * the correct gas values and are not running into problem with different 9 | * default values between `truffle develop` and `yarn ganache-cli` 10 | */ 11 | const makeTransactionConfig = config => 12 | Object.assign( 13 | { 14 | // 20 GWei 15 | gasPrice: 20e9, 16 | /* 17 | * Mainnet has a gas limit of roughly 8e6 at the time of writing 18 | * (source: https://ethstats.net ) 19 | * 20 | * Truffle's gas limit defaults to 6721975 21 | * (source: https://github.com/trufflesuite/ganache-cli/blob/develop/args.js#L133 ) 22 | * 23 | * We could tune this per contract, but here we just want to deploy contracts and 24 | * set up their state for e2e testing 25 | */ 26 | gas: 6721975, 27 | }, 28 | config 29 | ) 30 | 31 | const resolvePromiseMap = async obj => { 32 | const keys = Object.keys(obj) 33 | const values = Object.values(obj) 34 | 35 | const resolvedValues = await Promise.all(values) 36 | 37 | return keys.reduce((acc, key, index) => { 38 | acc[key] = resolvedValues[index] 39 | return acc 40 | }, {}) 41 | } 42 | 43 | module.exports = { 44 | makeTransactionConfig, 45 | resolvePromiseMap, 46 | send, 47 | } 48 | -------------------------------------------------------------------------------- /helpers/index.js: -------------------------------------------------------------------------------- 1 | const { 2 | getCurrentBlockTime, 3 | getTimeInFutureBySeconds, 4 | timeTravel, 5 | timeTravelToTarget, 6 | } = require('./time-travel') 7 | const { send } = require('./general') 8 | 9 | module.exports = { 10 | getCurrentBlockTime, 11 | getTimeInFutureBySeconds, 12 | timeTravel, 13 | timeTravelToTarget, 14 | send, 15 | } 16 | -------------------------------------------------------------------------------- /helpers/time-travel.js: -------------------------------------------------------------------------------- 1 | const { send } = require('./general') 2 | 3 | // increases time through ganache evm command 4 | const timeTravel = async seconds => { 5 | /* eslint-disable no-console */ 6 | if (seconds > 0) { 7 | const startBlock = await web3.eth.getBlock(web3.eth.blockNumber) 8 | 9 | await send('evm_increaseTime', [seconds]) 10 | await send('evm_mine') 11 | 12 | const currentBlock = await web3.eth.getBlock(web3.eth.blockNumber) 13 | 14 | const oneMinuteInSeconds = 60 15 | const oneHourInSeconds = 3600 16 | const oneDayInSeconds = 86400 17 | 18 | let time = `${seconds} seconds` 19 | if (seconds >= oneMinuteInSeconds && seconds < oneHourInSeconds) { 20 | time = `${seconds / 60} minutes` 21 | } else if (seconds >= oneHourInSeconds && seconds < oneDayInSeconds) { 22 | time = `${seconds / 60 / 60} hours` 23 | } else if (seconds >= oneDayInSeconds) { 24 | time = `${seconds / 60 / 60 / 24} days` 25 | } 26 | 27 | console.log(`💫 Warped ${time} on new block`) 28 | console.log(`⏪ previous block timestamp: ${startBlock.timestamp}`) 29 | console.log(`✅ current block timestamp: ${currentBlock.timestamp}`) 30 | } else { 31 | console.log('💫 Did not warp... 0 seconds was given as an argument') 32 | } 33 | } 34 | 35 | const timeTravelToTarget = async targetTime => { 36 | const currentTime = await getCurrentBlockTime() 37 | 38 | // Add 1 second to compensate for `> targetTime` checks 39 | const timeToTravelInSeconds = targetTime 40 | .minus(currentTime) 41 | .add(1) 42 | .toNumber() 43 | 44 | return timeTravel(timeToTravelInSeconds) 45 | } 46 | 47 | const getCurrentBlockTime = async () => 48 | (await web3.eth.getBlock(web3.eth.blockNumber)).timestamp 49 | 50 | const getTimeInFutureBySeconds = async secondsInFuture => 51 | (await getCurrentBlockTime()) + secondsInFuture 52 | 53 | module.exports = { 54 | getCurrentBlockTime, 55 | getTimeInFutureBySeconds, 56 | timeTravel, 57 | timeTravelToTarget, 58 | } 59 | -------------------------------------------------------------------------------- /migrations/0_init.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const { init } = require('./helpers/arguments') 3 | 4 | // dummy exports to make truffle happy 5 | module.exports = (deployer, network, accounts) => { 6 | init(network) 7 | } 8 | -------------------------------------------------------------------------------- /migrations/1_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const AccessToken = artifacts.require('AccessToken') 3 | const BrickblockAccount = artifacts.require('BrickblockAccount') 4 | const BrickblockToken = artifacts.require('BrickblockToken') 5 | const ContractRegistry = artifacts.require('ContractRegistry') 6 | const ExchangeRateProvider = artifacts.require('ExchangeRateProvider') 7 | const ExchangeRateProviderStub = artifacts.require( 8 | 'stubs/ExchangeRateProviderStub' 9 | ) 10 | const ExchangeRates = artifacts.require('ExchangeRates') 11 | const FeeManager = artifacts.require('FeeManager') 12 | const PoaCrowdsaleMaster = artifacts.require('PoaCrowdsale') 13 | const PoaLogger = artifacts.require('PoaLogger') 14 | const PoaManager = artifacts.require('PoaManager') 15 | const PoaTokenMaster = artifacts.require('PoaToken') 16 | const Whitelist = artifacts.require('Whitelist') 17 | const IPoaTokenCrowdsale = artifacts.require('IPoaTokenCrowdsale') 18 | 19 | const { argv } = require('./helpers/arguments') 20 | 21 | const { setWeb3 } = require('./helpers/general.js') 22 | setWeb3(web3) 23 | 24 | const { deploymentActions, poaActions } = require('./actions') 25 | 26 | // artifacts is not available in other files... 27 | const contracts = { 28 | AccessToken, 29 | BrickblockAccount, 30 | BrickblockToken, 31 | ContractRegistry, 32 | ExchangeRateProvider, 33 | ExchangeRateProviderStub, 34 | ExchangeRates, 35 | FeeManager, 36 | PoaCrowdsaleMaster, 37 | PoaLogger, 38 | PoaManager, 39 | PoaTokenMaster, 40 | Whitelist, 41 | IPoaTokenCrowdsale, 42 | } 43 | 44 | module.exports = (deployer, network, accounts) => { 45 | console.log(`deploying on ${network} network`) 46 | deployer 47 | .then(async () => { 48 | switch (network) { 49 | case 'test': 50 | return true 51 | default: 52 | if (argv.skipMigrations) { 53 | return true 54 | } 55 | 56 | switch (argv.execute) { 57 | case 'PoaToken': 58 | await poaActions(deployer, accounts, contracts, web3, network) 59 | break 60 | 61 | default: 62 | await deploymentActions( 63 | deployer, 64 | accounts, 65 | contracts, 66 | web3, 67 | network 68 | ) 69 | } 70 | 71 | return true 72 | } 73 | }) 74 | .catch(err => { 75 | // eslint-disable-next-line no-console 76 | console.error(err) 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /migrations/actions/index.js: -------------------------------------------------------------------------------- 1 | const deploymentActions = require('./deployment-actions') 2 | const poaActions = require('./poa-actions') 3 | 4 | module.exports = { 5 | deploymentActions, 6 | poaActions, 7 | } 8 | -------------------------------------------------------------------------------- /migrations/actions/poa-actions.js: -------------------------------------------------------------------------------- 1 | const { argv } = require('../helpers/arguments') 2 | const logger = require('../../scripts/lib/logger') 3 | 4 | const migrationHelpers = require('../helpers') 5 | 6 | const poaActions = async (deployer, accounts, contracts, web3, network) => { 7 | const { 8 | general: { isValidAddress, isBigNumber, getAccounts }, 9 | deployment: { deployContracts }, 10 | } = migrationHelpers 11 | 12 | const { executeAddress, executeTxConfig, executeFunctionName } = argv 13 | 14 | const { IPoaTokenCrowdsale } = contracts 15 | const useStub = network.search('dev') > -1 16 | 17 | const instances = await deployContracts(deployer, accounts, contracts, { 18 | useExpStub: useStub, 19 | useExistingContracts: argv.useExistingContracts, 20 | network, 21 | }) 22 | 23 | let contractAddress 24 | if (isValidAddress(executeAddress)) { 25 | contractAddress = executeAddress 26 | } else if (Number.isInteger(executeAddress)) { 27 | const tokenList = await instances.PoaManager.getTokenAddressList() 28 | contractAddress = tokenList[executeAddress] 29 | } else { 30 | logger.error( 31 | '--execute-address must be a valid ethereum address or an index number' 32 | ) 33 | process.exit(1) 34 | } 35 | 36 | const poaTokenInstance = IPoaTokenCrowdsale.at(contractAddress) 37 | 38 | const txConfigParsed = 39 | typeof executeTxConfig === 'string' ? JSON.parse(executeTxConfig) : {} 40 | 41 | if ( 42 | typeof txConfigParsed.from !== 'undefined' && 43 | isValidAddress(txConfigParsed.from) === false 44 | ) { 45 | logger.info(`getting ${txConfigParsed.from} from accounts`) 46 | 47 | txConfigParsed.from = getAccounts(accounts)[txConfigParsed.from] 48 | if (typeof txConfigParsed.from === 'undefined') { 49 | logger.error( 50 | `Invalid address please check the name of the address. The possible choices are: ${Object.keys( 51 | getAccounts(accounts) 52 | )}` 53 | ) 54 | process.exit(1) 55 | } 56 | } 57 | 58 | const fnArguments = [...argv.executeArguments] 59 | if (Object.keys(txConfigParsed).length > 0) { 60 | fnArguments.push(txConfigParsed) 61 | } 62 | 63 | logger.info( 64 | `Calling ${executeFunctionName}(${JSON.stringify( 65 | ...fnArguments, 66 | null, 67 | 2 68 | )}) function in PoaToken Contract at ${contractAddress}` 69 | ) 70 | 71 | const tx = await poaTokenInstance[executeFunctionName].apply( 72 | poaTokenInstance, 73 | fnArguments 74 | ) 75 | 76 | logger.info(isBigNumber(tx) ? tx.toString() : tx) 77 | } 78 | 79 | module.exports = poaActions 80 | -------------------------------------------------------------------------------- /migrations/helpers/bbk.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const chalk = require('chalk') 3 | 4 | const distributeBbkToMany = async ( 5 | BrickblockToken, 6 | contributors, 7 | amount, 8 | txConfig = { from: null, gas: null } 9 | ) => { 10 | for (let index = 0; index < contributors.length; index++) { 11 | const address = contributors[index] 12 | await BrickblockToken.distributeTokens(address, amount, txConfig) 13 | } 14 | } 15 | 16 | const finalizeBbkCrowdsale = async ( 17 | BrickblockToken, 18 | params = { 19 | contributors: [], 20 | fountainAddress: null, 21 | tokenAmountPerContributor: null, 22 | }, 23 | txConfig = { 24 | from: null, 25 | gas: null, 26 | } 27 | ) => { 28 | const { 29 | contributors, 30 | fountainAddress, 31 | tokenAmountPerContributor, 32 | network, 33 | } = params 34 | 35 | console.log(chalk.cyan('\n------------------------------')) 36 | console.log(chalk.cyan('🚀 Finalizing BBK crowdsale…')) 37 | 38 | if (network === 'mainnet') { 39 | console.error( 40 | chalk.red( 41 | `\n➡️ I am not alllowed to do anything on mainnet for BrickblockToken, skipping...` 42 | ) 43 | ) 44 | 45 | return 46 | } 47 | 48 | const isTokenSaleActive = await BrickblockToken.tokenSaleActive.call() 49 | 50 | console.log( 51 | chalk.yellow( 52 | `\n➡️ Changing fountainContractAddress to ${fountainAddress}…` 53 | ) 54 | ) 55 | await BrickblockToken.changeFountainContractAddress(fountainAddress, txConfig) 56 | 57 | if (isTokenSaleActive) { 58 | console.log( 59 | chalk.yellow( 60 | `\n➡️ Distributing ${tokenAmountPerContributor.toString()} BBK each to ${contributors.toString()}…` 61 | ) 62 | ) 63 | await distributeBbkToMany( 64 | BrickblockToken, 65 | contributors, 66 | tokenAmountPerContributor, 67 | txConfig 68 | ) 69 | 70 | console.log(chalk.yellow('\n➡️ Finalizing token sale…')) 71 | await BrickblockToken.finalizeTokenSale(txConfig) 72 | } else { 73 | console.log(chalk.yellow(`\n➡️ Token sale finalized already, skipping…`)) 74 | } 75 | 76 | console.log(chalk.yellow('\n➡️ Unpausing BBK…')) 77 | const isPaused = await BrickblockToken.paused.call() 78 | if (isPaused) { 79 | await BrickblockToken.unpause(txConfig) 80 | } else { 81 | console.log(chalk.yellow(`\n➡️ BBK Token unpaused already, skipping…`)) 82 | } 83 | 84 | console.log(chalk.green('\n✅ Successfully finalized BBK crowdsale')) 85 | console.log(chalk.green('------------------------------------------\n\n')) 86 | } 87 | 88 | module.exports = { 89 | finalizeBbkCrowdsale, 90 | } 91 | -------------------------------------------------------------------------------- /migrations/helpers/constants.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js') 2 | 3 | /* 4 | * Human readable variables for large numbers. 5 | */ 6 | const oneWeekInSec = 7 * 24 * 60 * 60 7 | const twoWeeksInSec = 2 * oneWeekInSec 8 | const oneHundredThousandEuroInCents = new BigNumber(10000000) 9 | const oneHundredThousandTokensInWei = new BigNumber(100000e18) 10 | 11 | module.exports = { 12 | oneWeekInSec, 13 | twoWeeksInSec, 14 | oneHundredThousandTokensInWei, 15 | oneHundredThousandEuroInCents, 16 | } 17 | -------------------------------------------------------------------------------- /migrations/helpers/exchange-rates.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const chalk = require('chalk') 3 | 4 | const setFiatRate = async ( 5 | ExchangeRates, 6 | ExchangeRateProvider, 7 | params = { 8 | currencyName: 'EUR', 9 | desiredExchangeRate: '500.12', 10 | queryString: 11 | 'json(https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=EUR).EUR', 12 | callIntervalInSec: 30, 13 | callbackGasLimit: 150000, 14 | ratePenalty: 20, // in permille => 20/1000 = 2% 15 | useStub: true, 16 | }, 17 | txConfig = { 18 | from: null, 19 | gas: null, 20 | } 21 | ) => { 22 | const { 23 | currencyName, 24 | desiredExchangeRate, 25 | queryString, 26 | callIntervalInSec, 27 | callbackGasLimit, 28 | ratePenalty, 29 | useStub, 30 | } = params 31 | 32 | console.log( 33 | chalk.cyan(`\n--------------------------------------------------------`) 34 | ) 35 | console.log( 36 | chalk.cyan( 37 | `🚀 Setting up exchange rate fetching for "ETH <> ${currencyName}"…` 38 | ) 39 | ) 40 | console.log( 41 | chalk.yellow('\n➡️ Setting currency settings in ExchangeRates contract…') 42 | ) 43 | await ExchangeRates.setCurrencySettings( 44 | currencyName, 45 | queryString, 46 | callIntervalInSec, 47 | callbackGasLimit, 48 | ratePenalty, 49 | txConfig 50 | ) 51 | 52 | console.log( 53 | chalk.yellow('\n➡️ Fetching latest rate from ExchangeRates contract…') 54 | ) 55 | // This method needs some ETH to pay the Oraclize fees 56 | await ExchangeRates.fetchRate(currencyName, { ...txConfig, value: 1e17 }) 57 | if (useStub) { 58 | console.log(chalk.yellow('\n➡️ Using stub to generate a test queryId…')) 59 | const pendingQueryId = await ExchangeRateProvider.pendingTestQueryId( 60 | txConfig 61 | ) 62 | 63 | console.log( 64 | chalk.yellow( 65 | '\n➡️ Using stub to simulate a successful callback from Oraclize…' 66 | ) 67 | ) 68 | await ExchangeRateProvider.simulate__callback( 69 | pendingQueryId, 70 | // expected that this API returns a euro dollar value 71 | desiredExchangeRate, 72 | txConfig 73 | ) 74 | } 75 | 76 | console.log( 77 | chalk.green( 78 | `\n✅ Successfully set up exchange rate fetching for "ETH <> ${currencyName}"` 79 | ) 80 | ) 81 | console.log( 82 | chalk.green( 83 | '-----------------------------------------------------------------\n\n' 84 | ) 85 | ) 86 | 87 | // returning so caller can know the default values that were used 88 | return params 89 | } 90 | 91 | module.exports = { 92 | setFiatRate, 93 | } 94 | -------------------------------------------------------------------------------- /migrations/helpers/general.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const truffleConfig = require('../../truffle') 3 | 4 | let web3 5 | 6 | const setWeb3 = _web3 => (web3 = _web3) 7 | 8 | // given an offset in second, returns seconds since unix epoch 9 | const unixTimeWithOffsetInSec = (offset = 0) => 10 | Math.floor(Date.now() / 1000) + offset 11 | 12 | const getEtherBalance = address => { 13 | return new Promise((resolve, reject) => { 14 | web3.eth.getBalance(address, (err, res) => { 15 | if (err) reject(err) 16 | 17 | resolve(res) 18 | }) 19 | }) 20 | } 21 | 22 | const sendTransaction = args => { 23 | return new Promise(function(resolve, reject) { 24 | web3.eth.sendTransaction(args, (err, res) => { 25 | if (err) { 26 | reject(err) 27 | } else { 28 | resolve(res) 29 | } 30 | }) 31 | }) 32 | } 33 | 34 | const getDefaultGasPrice = networkName => { 35 | const networkProperties = truffleConfig.networks[networkName] 36 | 37 | return networkProperties.gasPrice 38 | } 39 | 40 | const calculateUsedGasFromCost = (networkName, totalcost) => { 41 | const gasPrice = getDefaultGasPrice(networkName) 42 | 43 | return totalcost.div(gasPrice) 44 | } 45 | 46 | const isValidAddress = address => { 47 | if (/^(0x)?[0-9a-f]{40}$/i.test(address)) { 48 | return true 49 | } else { 50 | return false 51 | } 52 | } 53 | 54 | const isBigNumber = value => { 55 | const keys = Object.keys(value) 56 | return keys.includes('s') && keys.includes('e') && keys.includes('c') 57 | } 58 | 59 | const getAccounts = accounts => { 60 | return { 61 | owner: accounts[0], 62 | issuer: accounts[1], 63 | custodian: accounts[2], 64 | whitelistedInvestor: accounts[3], 65 | contributors: accounts.slice(4, 6), 66 | } 67 | } 68 | 69 | module.exports = { 70 | setWeb3, 71 | getEtherBalance, 72 | unixTimeWithOffsetInSec, 73 | getDefaultGasPrice, 74 | calculateUsedGasFromCost, 75 | sendTransaction, 76 | isValidAddress, 77 | getAccounts, 78 | isBigNumber, 79 | } 80 | -------------------------------------------------------------------------------- /migrations/helpers/index.js: -------------------------------------------------------------------------------- 1 | const brickblockToken = require('./bbk') 2 | const constants = require('./constants') 3 | const general = require('./general') 4 | const poaManager = require('./poa-manager') 5 | const exchangeRates = require('./exchange-rates') 6 | const whitelist = require('./whitelist') 7 | const statistics = require('./statistics') 8 | const ownerManagement = require('./owner-management') 9 | const deployment = require('./deployment') 10 | const registry = require('./registry') 11 | 12 | /* 13 | * IMPORTANT CONVENTION 14 | * There's a clear convention for all migration helpers to avoid confusion. 15 | * The function signature of a migration helper always follows this pattern: 16 | * 17 | * function nameOfHelper( 18 | * contractInstanceToWorkWith, 19 | * params = { allArguments: 'passedTo', theSmartContract: 'method' }) 20 | * txConfig = { from: 'accountToSendTransactionFrom', gas: defaultValueCouldBeOverriddenHere, ... } 21 | * ) { 22 | * contractInstanceToWorkWith.smartContractMethod(...params, txConfig) 23 | * } 24 | * 25 | * 26 | * REAL EXAMPLE 27 | * 28 | * function addAddressToWhitelist(whitelist, params = { investor: null }, txConfig = {}) { 29 | * const { investor } = params 30 | * await whitelist.addAddress(investor, txConfig) 31 | * } 32 | */ 33 | 34 | module.exports = { 35 | brickblockToken, 36 | constants, 37 | exchangeRates, 38 | general, 39 | poaManager, 40 | whitelist, 41 | statistics, 42 | ownerManagement, 43 | deployment, 44 | registry, 45 | } 46 | -------------------------------------------------------------------------------- /migrations/helpers/owner-management.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const logger = require('../../scripts/lib/logger') 3 | 4 | const transferOwnershipOfAllContracts = async (contracts, owner, newOwner) => { 5 | const contractNames = Object.keys(contracts) 6 | let totalGasUsed = 0 7 | for (let index = 0; index < contractNames.length; index++) { 8 | const contractName = contractNames[index] 9 | const contract = contracts[contractName] 10 | const isOwnable = typeof contract.owner === 'function' 11 | 12 | if (isOwnable) { 13 | const isOwner = (await contract.owner.call()) === owner 14 | 15 | if (isOwner) { 16 | logger.info(chalk.yellow(`Transferring ownership of ${contractName}`)) 17 | const tx = await contract.transferOwnership(newOwner, { from: owner }) 18 | totalGasUsed += tx.receipt.gasUsed 19 | logger.info( 20 | chalk.yellow( 21 | `\n Transferred ownership of ${contractName} from '${owner}' to '${newOwner}'` 22 | ) 23 | ) 24 | } 25 | } 26 | } 27 | 28 | logger.info('total gas used', totalGasUsed) 29 | 30 | return { 31 | gasUsed: totalGasUsed, 32 | } 33 | } 34 | 35 | module.exports = { 36 | transferOwnershipOfAllContracts, 37 | } 38 | -------------------------------------------------------------------------------- /migrations/helpers/poa-manager.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const chalk = require('chalk') 3 | 4 | const { 5 | oneHundredThousandEuroInCents, 6 | oneHundredThousandTokensInWei, 7 | oneWeekInSec, 8 | twoWeeksInSec, 9 | } = require('./constants') 10 | const { unixTimeWithOffsetInSec } = require('./general.js') 11 | 12 | const addIssuer = async ( 13 | poaManager, 14 | params = { 15 | issuer: '', 16 | }, 17 | txConfig 18 | ) => { 19 | const { issuer } = params 20 | 21 | console.log( 22 | chalk.cyan( 23 | '\n--------------------------------------------------------------' 24 | ) 25 | ) 26 | console.log(chalk.gray(`Checking if issuer "${issuer}" exist…\n`)) 27 | const isRegisteredIssuer = await poaManager.isRegisteredIssuer( 28 | issuer, 29 | txConfig 30 | ) 31 | if (isRegisteredIssuer) { 32 | console.log(chalk.gray(`Issuer "${issuer}" already exist, skipping…\n`)) 33 | return 34 | } 35 | 36 | console.log(chalk.cyan(`🚀 Adding issuer "${issuer}"…\n`)) 37 | await poaManager.addIssuer(issuer, txConfig) 38 | console.log(chalk.green(`\n✅ Successfully added issuer "${issuer}"`)) 39 | console.log( 40 | chalk.green( 41 | '------------------------------------------------------------------------\n\n' 42 | ) 43 | ) 44 | } 45 | 46 | const deployPoa = async ( 47 | poaManager, 48 | params = { 49 | name: 'POA Test Token', 50 | symbol: 'BBK-RE-DE123', 51 | fiatCurrency: 'EUR', 52 | totalSupply: oneHundredThousandTokensInWei, 53 | startTimeForFundingPeriod: unixTimeWithOffsetInSec(600), 54 | durationForFiatFundingPeriod: oneWeekInSec, 55 | durationForEthFundingPeriod: oneWeekInSec, 56 | durationForActivationPeriod: twoWeeksInSec, 57 | fundingGoalInCents: oneHundredThousandEuroInCents, 58 | listToken: true, 59 | }, 60 | txConfig = {} 61 | ) => { 62 | const { 63 | name, 64 | symbol, 65 | fiatCurrency, 66 | custodian, 67 | totalSupply, 68 | startTimeForFundingPeriod, 69 | durationForFiatFundingPeriod, 70 | durationForEthFundingPeriod, 71 | durationForActivationPeriod, 72 | fundingGoalInCents, 73 | listToken, 74 | } = params 75 | console.log( 76 | chalk.cyan( 77 | '\n--------------------------------------------------------------------' 78 | ) 79 | ) 80 | console.log( 81 | chalk.cyan(`🚀 Deploying POA "${name}" with symbol "${symbol}"…\n`) 82 | ) 83 | 84 | const tx = await poaManager.addNewToken( 85 | name, 86 | symbol, 87 | fiatCurrency, 88 | custodian, 89 | totalSupply, 90 | startTimeForFundingPeriod, 91 | durationForFiatFundingPeriod, 92 | durationForEthFundingPeriod, 93 | durationForActivationPeriod, 94 | fundingGoalInCents, 95 | txConfig 96 | ) 97 | const poaAddress = tx.logs[0].args.token 98 | 99 | console.log( 100 | chalk.green( 101 | `\n✅ Successfully deployed POA "${symbol}" to "${poaAddress}"` 102 | ) 103 | ) 104 | 105 | if (listToken) { 106 | console.log(chalk.gray(`\n Listing POA "${symbol}" on PoaManager`)) 107 | await poaManager.listToken(poaAddress) 108 | console.log( 109 | chalk.green(`\n✅ Successfully listed POA "${symbol}" on PoaManager`) 110 | ) 111 | } 112 | 113 | const tokenList = await poaManager.getTokenAddressList() 114 | 115 | console.log(chalk.yellow(`\n POA token list in PoaManager`), tokenList) 116 | 117 | console.log( 118 | chalk.yellow( 119 | `\n POA "${symbol}" index in PoaManager is: ${tokenList.length - 1}` 120 | ) 121 | ) 122 | 123 | console.log( 124 | chalk.green( 125 | '-------------------------------------------------------------------------------------------\n\n' 126 | ) 127 | ) 128 | } 129 | 130 | module.exports = { 131 | addIssuer, 132 | deployPoa, 133 | } 134 | -------------------------------------------------------------------------------- /migrations/helpers/registry.js: -------------------------------------------------------------------------------- 1 | const logger = require('../../scripts/lib/logger') 2 | const chalk = require('chalk') 3 | 4 | const addContractsToRegistry = async ( 5 | contracts = {}, 6 | txConfig = { from: null, gas: null } 7 | ) => { 8 | logger.info(chalk.cyan('\n-----------------------------------------')) 9 | logger.info(chalk.cyan('\n🚀 Adding contracts to ContractRegistry…')) 10 | 11 | const { 12 | AccessToken, 13 | BrickblockAccount, 14 | BrickblockToken, 15 | PoaLogger, 16 | ContractRegistry, 17 | ExchangeRateProvider, 18 | ExchangeRates, 19 | FeeManager, 20 | PoaManager, 21 | PoaTokenMaster, 22 | PoaCrowdsaleMaster, 23 | Whitelist, 24 | } = contracts 25 | 26 | await conditionalRegister( 27 | ContractRegistry, 28 | 'AccessToken', 29 | AccessToken.address, 30 | txConfig 31 | ) 32 | 33 | await conditionalRegister( 34 | ContractRegistry, 35 | 'BrickblockAccount', 36 | BrickblockAccount.address, 37 | txConfig 38 | ) 39 | 40 | await conditionalRegister( 41 | ContractRegistry, 42 | 'BrickblockToken', 43 | BrickblockToken.address, 44 | txConfig 45 | ) 46 | 47 | await conditionalRegister( 48 | ContractRegistry, 49 | 'PoaLogger', 50 | PoaLogger.address, 51 | txConfig 52 | ) 53 | 54 | await conditionalRegister( 55 | ContractRegistry, 56 | 'ExchangeRates', 57 | ExchangeRates.address, 58 | txConfig 59 | ) 60 | 61 | await conditionalRegister( 62 | ContractRegistry, 63 | 'ExchangeRateProvider', 64 | ExchangeRateProvider.address, 65 | txConfig 66 | ) 67 | 68 | await conditionalRegister( 69 | ContractRegistry, 70 | 'FeeManager', 71 | FeeManager.address, 72 | txConfig 73 | ) 74 | 75 | await conditionalRegister( 76 | ContractRegistry, 77 | 'PoaManager', 78 | PoaManager.address, 79 | txConfig 80 | ) 81 | 82 | await conditionalRegister( 83 | ContractRegistry, 84 | 'PoaCrowdsaleMaster', 85 | PoaCrowdsaleMaster.address, 86 | txConfig 87 | ) 88 | 89 | await conditionalRegister( 90 | ContractRegistry, 91 | 'PoaTokenMaster', 92 | PoaTokenMaster.address, 93 | txConfig 94 | ) 95 | 96 | await conditionalRegister( 97 | ContractRegistry, 98 | 'Whitelist', 99 | Whitelist.address, 100 | txConfig 101 | ) 102 | 103 | logger.info(chalk.green('\n✅ Successfully updated ContractRegistry')) 104 | logger.info(chalk.green('------------------------------------------\n\n')) 105 | } 106 | 107 | const conditionalRegister = async ( 108 | ContractRegistry, 109 | contractName, 110 | contractAddress, 111 | txConfig 112 | ) => { 113 | let currentAddress 114 | try { 115 | currentAddress = await ContractRegistry.getContractAddress( 116 | contractName, 117 | txConfig 118 | ) 119 | } catch (error) { 120 | logger.info(chalk.gray(`${contractName} is not available in the registry`)) 121 | } 122 | 123 | if (contractAddress === currentAddress) { 124 | logger.info( 125 | chalk.gray( 126 | `\n➡️ ${contractName} address is identical with address in the registry. skipping...` 127 | ) 128 | ) 129 | return 130 | } 131 | 132 | logger.info(chalk.yellow(`\n➡️ Registering ${contractName}…`)) 133 | await ContractRegistry.updateContractAddress( 134 | contractName, 135 | contractAddress, 136 | txConfig 137 | ) 138 | } 139 | 140 | module.exports = { 141 | addContractsToRegistry, 142 | } 143 | -------------------------------------------------------------------------------- /migrations/helpers/statistics.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const chalk = require('chalk') 3 | const { table } = require('table') 4 | const { calculateUsedGasFromCost } = require('./general') 5 | 6 | const showStatistics = (costs, globals) => { 7 | const { 8 | deployContractsGasCost, 9 | addToRegistryGasCost, 10 | finalizeBbkCrowdsaleGasCost, 11 | setFiatRateGasCost, 12 | addIssuerGasCost, 13 | deployPoaTokenGasCost, 14 | whitelistAddressGasCost, 15 | changeOwnerGasCost, 16 | totalGasCost, 17 | } = costs 18 | 19 | const { web3, network } = globals 20 | 21 | const tableData = [ 22 | ['Action Name', 'Gas Cost GWei', 'Gas Cost Ether', 'Gas Used'], 23 | ] 24 | 25 | if (deployContractsGasCost) { 26 | tableData.push([ 27 | 'Deploy Contracts', 28 | web3.fromWei(deployContractsGasCost, 'gwei').toString(), 29 | `Ξ ${web3.fromWei(deployContractsGasCost).toString()}`, 30 | calculateUsedGasFromCost(network, deployContractsGasCost).toString(), 31 | ]) 32 | } 33 | 34 | if (addToRegistryGasCost) { 35 | tableData.push([ 36 | 'Register Contracts', 37 | web3.fromWei(addToRegistryGasCost, 'gwei').toString(), 38 | `Ξ ${web3.fromWei(addToRegistryGasCost).toString()}`, 39 | calculateUsedGasFromCost(network, addToRegistryGasCost).toString(), 40 | ]) 41 | } 42 | 43 | if (finalizeBbkCrowdsaleGasCost) { 44 | tableData.push([ 45 | 'Finalize BBK', 46 | web3.fromWei(finalizeBbkCrowdsaleGasCost, 'gwei').toString(), 47 | `Ξ ${web3.fromWei(finalizeBbkCrowdsaleGasCost).toString()}`, 48 | calculateUsedGasFromCost(network, finalizeBbkCrowdsaleGasCost).toString(), 49 | ]) 50 | } 51 | 52 | if (setFiatRateGasCost) { 53 | tableData.push([ 54 | 'Set Fiat Rate', 55 | web3.fromWei(setFiatRateGasCost, 'gwei').toString(), 56 | `Ξ ${web3.fromWei(setFiatRateGasCost).toString()}`, 57 | calculateUsedGasFromCost(network, setFiatRateGasCost).toString(), 58 | ]) 59 | } 60 | 61 | if (addIssuerGasCost) { 62 | tableData.push([ 63 | 'Add Issuer', 64 | web3.fromWei(addIssuerGasCost, 'gwei').toString(), 65 | `Ξ ${web3.fromWei(addIssuerGasCost).toString()}`, 66 | calculateUsedGasFromCost(network, addIssuerGasCost).toString(), 67 | ]) 68 | } 69 | 70 | if (deployPoaTokenGasCost) { 71 | tableData.push([ 72 | 'Deploy POA Token', 73 | web3.fromWei(deployPoaTokenGasCost, 'gwei').toString(), 74 | `Ξ ${web3.fromWei(deployPoaTokenGasCost).toString()}`, 75 | calculateUsedGasFromCost(network, deployPoaTokenGasCost).toString(), 76 | ]) 77 | } 78 | 79 | if (whitelistAddressGasCost) { 80 | tableData.push([ 81 | 'Whitelist Investor', 82 | web3.fromWei(whitelistAddressGasCost, 'gwei').toString(), 83 | `Ξ ${web3.fromWei(whitelistAddressGasCost).toString()}`, 84 | calculateUsedGasFromCost(network, whitelistAddressGasCost).toString(), 85 | ]) 86 | } 87 | 88 | if (changeOwnerGasCost) { 89 | tableData.push([ 90 | 'Change Owner', 91 | web3.fromWei(changeOwnerGasCost, 'gwei').toString(), 92 | `Ξ ${web3.fromWei(changeOwnerGasCost).toString()}`, 93 | calculateUsedGasFromCost(network, changeOwnerGasCost).toString(), 94 | ]) 95 | } 96 | 97 | if (totalGasCost) { 98 | tableData.push([ 99 | chalk.bold('Total'), 100 | web3.fromWei(totalGasCost, 'gwei').toString(), 101 | `Ξ ${web3.fromWei(totalGasCost).toString()}`, 102 | calculateUsedGasFromCost(network, totalGasCost).toString(), 103 | ]) 104 | } 105 | 106 | console.log(table(tableData)) 107 | } 108 | 109 | module.exports = { showStatistics } 110 | -------------------------------------------------------------------------------- /migrations/helpers/whitelist.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const chalk = require('chalk') 3 | 4 | const addAddress = async ( 5 | whitelist, 6 | params = { investor: null }, 7 | txConfig = {} 8 | ) => { 9 | const { investor } = params 10 | 11 | console.log( 12 | chalk.cyan( 13 | '\n---------------------------------------------------------------------------------' 14 | ) 15 | ) 16 | 17 | console.log( 18 | chalk.cyan(`🚀 Checking if investor "${investor}" is whitelisted…\n`) 19 | ) 20 | const isWhiteListed = await whitelist.whitelisted.call(investor, txConfig) 21 | 22 | if (isWhiteListed) { 23 | console.log(chalk.cyan(`🚀 "${investor}" is already whitelisted…\n`)) 24 | return 25 | } 26 | 27 | console.log(chalk.cyan(`🚀 Whitelisting investor "${investor}"…\n`)) 28 | await whitelist.addAddress(investor, txConfig) 29 | console.log( 30 | chalk.green(`\n✅ Successfully whitelisted investor "${investor}"`) 31 | ) 32 | console.log( 33 | chalk.green( 34 | '----------------------------------------------------------------------------------\n\n' 35 | ) 36 | ) 37 | } 38 | 39 | module.exports = { 40 | addAddress, 41 | } 42 | -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-05T14-09-55.076603042Z--0aff697345acfe4224ded0732dd4b8ad65ac5268: -------------------------------------------------------------------------------- 1 | {"address":"0aff697345acfe4224ded0732dd4b8ad65ac5268","crypto":{"cipher":"aes-128-ctr","ciphertext":"6e590461aac77b136da0f1b9125654ed0dc1126131d836a71ceb16a97a2b5bd6","cipherparams":{"iv":"2b490694c0707115e707f75bc5d8d964"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"881abbec1b109b7893460d100e4f5b1dbbeccc7b7e4258e37165f022036bd348"},"mac":"a98d0d73597898d27241d5e381c152685f63cc9ee1fe04bd2b4a0d6750cbc079"},"id":"f6c60795-df28-45e7-aa07-1bfa27adec05","version":3} -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-05T14-10-32.107340526Z--b95e24b6b29d2d216ef35baf06093bdcb5d9ef9a: -------------------------------------------------------------------------------- 1 | {"address":"b95e24b6b29d2d216ef35baf06093bdcb5d9ef9a","crypto":{"cipher":"aes-128-ctr","ciphertext":"7c209a902808d963645dfb4a6c338fd20d71df8863cf7d36a840a95ae78b2f51","cipherparams":{"iv":"9b9c5c3f7a017cf12da83fa55b2c2fa9"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"86e63513952f732e8f53cd3cb83953fd66e7fced41c1d1590e7fbd87899d9a1c"},"mac":"ae9301f88fc2f3a8c16c8eb82d417c8fda636e8b9e99fbb38e81e1b90b076885"},"id":"b512c294-29a7-4811-91c8-5aac7de8ce3c","version":3} -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-06T07-55-37.120385883Z--df5255f6bbabb6e91e5b7c9177602cf6de7e1485: -------------------------------------------------------------------------------- 1 | {"address":"df5255f6bbabb6e91e5b7c9177602cf6de7e1485","crypto":{"cipher":"aes-128-ctr","ciphertext":"3b99fdaca491979bd489fbc63f08a9fda4b91431eda3708f61ad7f02ee7863b6","cipherparams":{"iv":"93b47ed2397bcf8b8a4c4c5f2359a9e3"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"bbf5226c455af31843117f825fef82b12accead1539edad04896b08513964902"},"mac":"d40f9d887c3aa2079fdd01c2a0021b87a42f0e7ee56b8eec8341f790d9a82afb"},"id":"5cae3c79-b593-4708-8475-d3e92379e05d","version":3} -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-06T07-56-45.763078036Z--7dcdf74cad9a90692a9d24121ff61a216f528ff5: -------------------------------------------------------------------------------- 1 | {"address":"7dcdf74cad9a90692a9d24121ff61a216f528ff5","crypto":{"cipher":"aes-128-ctr","ciphertext":"9fba87888e559be5833f755bb8716c2322001fa0e31bb2dddfe4f660239e98b0","cipherparams":{"iv":"d0e76a71cce37a5ac2b5e56695fbba4d"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"5f20aeb9fd1b2fc5a775bd9e5d2b5fa58ec806ebbdea4271b3d50a0967b1feeb"},"mac":"f87ddbbdeb746ab261046deb0eac934b14eba5207f45ceef304a94ff78fee1cd"},"id":"50ad5c68-a988-4951-a794-7000e3da9c0a","version":3} -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-06T07-57-20.156037626Z--e2b3c5292ea4f6cf53ad822e7c2ff749c0e8dc23: -------------------------------------------------------------------------------- 1 | {"address":"e2b3c5292ea4f6cf53ad822e7c2ff749c0e8dc23","crypto":{"cipher":"aes-128-ctr","ciphertext":"3247fda94e1a64dd43c43fab6c8161d57c3b038417bd78f53d4522d743f7fa59","cipherparams":{"iv":"9190df97d7e8a6b36206024f57963f21"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"70f224b3851e43ae6c4c40132cc10f2017acfb0f8f558b5e69d7193836166150"},"mac":"78385531ec33547a24d9b2d0f3775cee64eadeb3cbcc880ad474bd949e1866c8"},"id":"ea333b39-082d-4540-9297-96cd7680a323","version":3} -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-06T07-58-16.626381710Z--d4be220860d26bcce32a5e98826a61f10c95794c: -------------------------------------------------------------------------------- 1 | {"address":"d4be220860d26bcce32a5e98826a61f10c95794c","crypto":{"cipher":"aes-128-ctr","ciphertext":"37e94f686e4a476c4f38aef122aa757b7681e0015f383b5b905259e0d00de9b0","cipherparams":{"iv":"bcdb0827ed566ff4d18b15ce37bb50bb"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"267b03b6bcebd1b4c10ed47d186f6e334ac38f7a96d7c077731a7abb833c585b"},"mac":"27bc016b2daa18f795d92cc44b7705b3ecc7c23c58aa54afda662ad4f0eaed24"},"id":"242e1d72-7075-4fc5-90d2-faff66088aec","version":3} -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-06T07-58-41.965010624Z--47010231863e4171c65aafd173f464a5397409e7: -------------------------------------------------------------------------------- 1 | {"address":"47010231863e4171c65aafd173f464a5397409e7","crypto":{"cipher":"aes-128-ctr","ciphertext":"503a34e7134ed6771b8b537fd45f57a274a4b930f88b29dd94cd48b41409fb5c","cipherparams":{"iv":"01bc2c9b8c9f425f88546fccc68c7f7e"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"9d8227de033a70f19cc672d35e88f235413a4eb577e203cfa76fa21c00acc714"},"mac":"bda75b48e8d042c524c616fc2aebf0a401ab0ee440cbb08b78868dfd8a0c436a"},"id":"bc978851-003e-4bf7-a654-d1f4cee35aa7","version":3} -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-06T07-59-10.698813967Z--da6a355dd311e18b35a23de174de9ac8638e532a: -------------------------------------------------------------------------------- 1 | {"address":"da6a355dd311e18b35a23de174de9ac8638e532a","crypto":{"cipher":"aes-128-ctr","ciphertext":"f436c9e75ebfe4d0e721c43ea2ca764f4f8be41da5e92b698fac57c61cdc0bf2","cipherparams":{"iv":"d377b958a9481ae170d9c975ffc1ef64"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"13a968c202f908f25f6d8c7ca8583b55705980d760c4a7ff70e1f16abc3bd64e"},"mac":"a5f000036712805ee176cccbe68c4ce804d4cfaaf7921be40bc60823b172cac5"},"id":"fd417db4-7c2e-4e97-8378-5c8d054e0103","version":3} -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-06T07-59-34.745859619Z--25842757534c2ed386f55226852e818a199b82a2: -------------------------------------------------------------------------------- 1 | {"address":"25842757534c2ed386f55226852e818a199b82a2","crypto":{"cipher":"aes-128-ctr","ciphertext":"a7f1894c9923493696e1a3da0e43c91a467f4f393e8f1748c7de3975fe3b50d0","cipherparams":{"iv":"8d350b9dc637314ef42752039786f6a3"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"fc6f2d9f33267eca63decfe01adf3b64af1421d9ddb74553a80f271ef6542f24"},"mac":"c2f5683d03ddbc4d74337792bce5b853820b2f51b6c05a45b00cc8c5c2c00a43"},"id":"d846cb5c-e4af-47fb-aab4-0b9d405f9155","version":3} -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-06T08-00-01.499176142Z--d9648e66fa7f8b16b3ec39ccca559b0fa334b853: -------------------------------------------------------------------------------- 1 | {"address":"d9648e66fa7f8b16b3ec39ccca559b0fa334b853","crypto":{"cipher":"aes-128-ctr","ciphertext":"3e98e7c7e83d4ded02a5dc1be93fd061102356b179d805e8c2857c85457ea3e1","cipherparams":{"iv":"a052af8cf9f765796368f2a068a69eac"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"eb486aee8a00b0929a21fb30bf7c154a0b4cd6ea61c8f7ccbac36be476d13425"},"mac":"8745432007d360a627f7eae4d292b3f33754a5656361ee5f83d529a2da3220c3"},"id":"9c34bd5e-d227-4ba5-b3f2-6d734478e1b6","version":3} -------------------------------------------------------------------------------- /private-chain-data/keystore/UTC--2018-06-19T21-54-35.832389126Z--331bded3ee57a580925befca2884c57e4472e4a0: -------------------------------------------------------------------------------- 1 | {"address":"331bded3ee57a580925befca2884c57e4472e4a0","crypto":{"cipher":"aes-128-ctr","ciphertext":"9935d00f8729346a254cb131c7188e239093735711961b040ad446da1563f50a","cipherparams":{"iv":"c682a0c3f4ad5137af727db2aec11043"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"701273400f652cc215ff112b8b607f52e35e3fd937cc147356292103b81bbd5c"},"mac":"73aa3ba6a5e3728e221e981d34afaeb1be840529adeb3a7acc5784a7c4ef425b"},"id":"55ab5d9e-2bd8-4da5-aae5-0ba246b7c870","version":3} -------------------------------------------------------------------------------- /scripts/ci-release.sh: -------------------------------------------------------------------------------- 1 | : "${SECRET_GITLAB_BOT_CI_TOKEN:?Environment variable SECRET_GITLAB_BOT_CI_TOKEN needs to be set as secret CI variable in GitLab before running this script}" 2 | 3 | # Make sure to check out the latest master branch 4 | echo "git checkout master && git pull" 5 | git checkout master && git pull 6 | 7 | # Act as gitlab-bot user who has push access to `master` 8 | echo "git config --global user.name \"gitlab-bot\"" 9 | git config --global user.name "gitlab-bot" 10 | echo "git config --global user.email \"git@brickblock.io\"" 11 | git config --global user.email "git@brickblock.io" 12 | echo "git config --global push.default current" 13 | git config --global push.default current 14 | 15 | # Compiles all JSON ABIs of our contracts into the `.gitignore`d folder `./deployed-contracts` 16 | echo "yarn build" 17 | yarn build 18 | 19 | # Auto-generate CHANGELOG.md and bump version number in package.json 20 | echo "yarn release" 21 | yarn release 22 | 23 | # Push the freshly generated CHANGELOG.md and updated package.json 24 | echo "git push https://gitlab-bot:\$SECRET_GITLAB_BOT_CI_TOKEN@git.brickblock.sh/${CI_PROJECT_PATH}.git/ master --follow-tags" 25 | git push https://gitlab-bot:"$SECRET_GITLAB_BOT_CI_TOKEN"@git.brickblock.sh/"$CI_PROJECT_PATH" master --follow-tags 26 | 27 | # Publish new version on npm 28 | echo "npm publish" 29 | npm publish 30 | -------------------------------------------------------------------------------- /scripts/geth/auto-mine.js: -------------------------------------------------------------------------------- 1 | /* global eth,miner,txpool */ 2 | /* eslint no-var:0 */ 3 | /* eslint no-console:0 */ 4 | 5 | function automine(milliSeconds) { 6 | setInterval(function() { 7 | if (!eth.mining && (txpool.status.pending || txpool.status.queued)) { 8 | console.log('miner start') 9 | miner.start() 10 | } else if (eth.mining) { 11 | console.log('miner stop') 12 | miner.stop() 13 | } 14 | }, milliSeconds) 15 | } 16 | 17 | automine(500) 18 | 19 | // * NOTE: This automine implementation would be better because it 20 | // * doesn't rely on setInterval. Unfortunately there seems 21 | // * to be a bug (race condition?) in it which causes some tests 22 | // * to time out. 23 | // * 24 | // * Try using this implementation with test/main-tests/AccessToken.js 25 | // * and it'll work fine, even faster than the above implementation. 26 | // * However, running test/main-tests/ContractRegistry.js will time 27 | // * out with this implementation. 28 | 29 | // function automine() { 30 | // if (eth.getBlock('pending').transactions.length > 0) { 31 | // if (eth.mining) return 32 | // 33 | // console.log('Pending transactions! Mining...') 34 | // miner.start(1) 35 | // } else { 36 | // miner.stop() 37 | // console.log('No transactions! Mining stopped.') 38 | // } 39 | // } 40 | // 41 | // eth.filter('latest', function() { 42 | // automine() 43 | // }) 44 | // eth.filter('pending', function() { 45 | // automine() 46 | // }) 47 | // 48 | // automine() 49 | -------------------------------------------------------------------------------- /scripts/geth/genesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "gasLimit": "0x9000000", 3 | "nonce": "0x0000000000000123", 4 | "timestamp": "0x00", 5 | "parentHash": 6 | "0x0000000000000000000000000000000000000000000000000000000000000000", 7 | "extraData": "0x00", 8 | "difficulty": "1", 9 | "mixhash": 10 | "0x0000000000000000000000000000000000000000000000000000000000000000", 11 | "alloc": { 12 | "0aff697345acfe4224ded0732dd4b8ad65ac5268": { 13 | "balance": "100000000000000000000" 14 | }, 15 | "b95e24b6b29d2d216ef35baf06093bdcb5d9ef9a": { 16 | "balance": "100000000000000000000" 17 | }, 18 | "df5255f6bbabb6e91e5b7c9177602cf6de7e1485": { 19 | "balance": "100000000000000000000" 20 | }, 21 | "7dcdf74cad9a90692a9d24121ff61a216f528ff5": { 22 | "balance": "100000000000000000000" 23 | }, 24 | "e2b3c5292ea4f6cf53ad822e7c2ff749c0e8dc23": { 25 | "balance": "100000000000000000000" 26 | }, 27 | "d4be220860d26bcce32a5e98826a61f10c95794c": { 28 | "balance": "100000000000000000000" 29 | }, 30 | "47010231863e4171c65aafd173f464a5397409e7": { 31 | "balance": "100000000000000000000" 32 | }, 33 | "da6a355dd311e18b35a23de174de9ac8638e532a": { 34 | "balance": "100000000000000000000" 35 | }, 36 | "25842757534c2ed386f55226852e818a199b82a2": { 37 | "balance": "100000000000000000000" 38 | }, 39 | "d9648e66fa7f8b16b3ec39ccca559b0fa334b853": { 40 | "balance": "100000000000000000000" 41 | }, 42 | "331bded3ee57a580925befca2884c57e4472e4a0": { 43 | "balance": "0" 44 | } 45 | }, 46 | "config": { 47 | "chainId": 4448, 48 | "homesteadBlock": 0, 49 | "eip150Block": 0, 50 | "eip150Hash": 51 | "0x0000000000000000000000000000000000000000000000000000000000000000", 52 | "eip155Block": 0, 53 | "eip158Block": 0, 54 | "byzantiumBlock": 0 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /scripts/geth/manage-accounts.js: -------------------------------------------------------------------------------- 1 | /* global personal */ 2 | /* eslint no-var:0 */ 3 | /* eslint no-console:0 */ 4 | 5 | var UNLOCK_DURATION_AS_SECONDS = 60 * 60 6 | 7 | for (var index = 0; index < personal.listAccounts.length; index++) { 8 | var account = personal.listAccounts[index] 9 | console.log(account) 10 | personal.unlockAccount(account, 'bb', UNLOCK_DURATION_AS_SECONDS) 11 | } 12 | 13 | console.log( 14 | personal.listAccounts.length + ' accounts unlocked for ', 15 | +UNLOCK_DURATION_AS_SECONDS / 60 + ' minutes !' 16 | ) 17 | -------------------------------------------------------------------------------- /scripts/geth/start-geth.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This script brings up a local geth blockchain to run tests against. 4 | # If old chain data exists in the data dir, it will delete it and 5 | # start with a clean state. 6 | 7 | GETH_DATA_DIR='./private-chain-data' 8 | 9 | if [ -d "$GETH_DATA_DIR/geth" ]; then 10 | printf '%s\n' "Removing old chain data from $GETH_DATA_DIR/geth" 11 | rm -rf "$GETH_DATA_DIR/geth" 12 | fi 13 | 14 | printf '%s\n' "init genesis block in $GETH_DATA_DIR/geth" 15 | geth --datadir "$GETH_DATA_DIR" init "./scripts/geth/genesis.json" 16 | printf '%s\n' "running geth..." 17 | geth --datadir "$GETH_DATA_DIR" \ 18 | --gasprice 0 \ 19 | --maxpeers 0 console \ 20 | --networkid 4447 \ 21 | --nodiscover \ 22 | --preload "scripts/geth/manage-accounts.js,scripts/geth/auto-mine.js" \ 23 | --rpc \ 24 | --rpcapi eth,net,web3,personal,miner,debug,txpool \ 25 | --rpccorsdomain "*" \ 26 | --targetgaslimit 94000000 \ 27 | --mine \ 28 | --minerthreads 1 \ 29 | --etherbase 10 30 | -------------------------------------------------------------------------------- /scripts/healthcheck.sh: -------------------------------------------------------------------------------- 1 | yarn lint 2 | printf " ✅\n" 3 | 4 | printf "1️⃣ Running tests…" 5 | yarn test 6 | printf " ✅\n" 7 | 8 | printf "2️⃣ Searching for open TODOs and FIXMEs in the code…" 9 | printf "6️⃣ Searching for open TODOs and FIXMEs in the code…" 10 | yarn todo -------------------------------------------------------------------------------- /scripts/inject-deployed-contracts.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const R = require('ramda') 4 | const sha3 = require('ethereumjs-util').sha3 5 | 6 | // all the deployed contracts that are singletons and need to be known by downstream consumers (like 7 | // platform web client) are stored here 8 | const deployedContractsByNetwork = require('../config/deployed-contracts.js') 9 | 10 | // truffle compiles and then saves artifacts here 11 | const contractBuildDirectory = path.resolve(__dirname, '../build/contracts') 12 | 13 | // here is what is included in the published npm module 14 | const deployedContractsDirectory = path.resolve( 15 | __dirname, 16 | '../deployed-contracts' 17 | ) 18 | 19 | // ensure this directory exists since we do not check this into git 20 | if (!fs.existsSync(deployedContractsDirectory)) 21 | fs.mkdirSync(deployedContractsDirectory) 22 | 23 | // we group by contract name; seems more natural in the JSON file to organize by network 24 | const deployedAddressesGroupedByContractName = R.toPairs( 25 | deployedContractsByNetwork 26 | ).reduce((acc, [networkId, contractsInNetwork]) => { 27 | R.toPairs(contractsInNetwork).forEach(([contractName, contractAddress]) => { 28 | // if accumulator does not have contract yet, set as empty hashmap 29 | if (acc[contractName] == null) acc[contractName] = {} 30 | 31 | acc[contractName][networkId] = { address: contractAddress.toLowerCase() } 32 | }) 33 | 34 | return acc 35 | }, {}) 36 | 37 | fs.readdirSync(contractBuildDirectory).forEach(contractArtifactFilename => { 38 | // this is the output from truffle compile 39 | const contractArtifact = JSON.parse( 40 | fs.readFileSync(path.join(contractBuildDirectory, contractArtifactFilename)) 41 | ) 42 | 43 | // this is the minimal data needed to work with the contract 44 | // if something wants to deploy an instance of the contract, should use truffle compiled output 45 | const deployedContractArtifact = R.pick(['abi'], contractArtifact) 46 | 47 | const contractName = contractArtifactFilename.replace('.json', '') 48 | 49 | // add the addresses into the build artifact 50 | if (!R.isNil(deployedAddressesGroupedByContractName[contractName])) { 51 | deployedContractArtifact.networks = 52 | deployedAddressesGroupedByContractName[contractName] 53 | 54 | // TODO: 55 | // we use `truffle-contract` in platform, and it must have all events in each network property... 56 | // ideally this would be read from the ABI but somehow this lib does its own weird thing 57 | const events = contractArtifact.abi 58 | .filter(item => item.type === 'event') 59 | .map(event => { 60 | const key = 61 | '0x' + 62 | sha3( 63 | `${event.name}(${event.inputs.map(input => input.type).join(',')})` 64 | ).hexSlice() 65 | return { [key]: event } 66 | }) 67 | 68 | deployedContractArtifact.networks = R.merge( 69 | deployedContractArtifact.networks, 70 | { events } 71 | ) 72 | // end TODO: if we ever stop using truffle-contract this block can go away 73 | } 74 | 75 | // write the deployed contract to be used by consuming applications 76 | fs.writeFileSync( 77 | path.join(deployedContractsDirectory, contractArtifactFilename), 78 | JSON.stringify(deployedContractArtifact) 79 | ) 80 | }) 81 | -------------------------------------------------------------------------------- /scripts/lib/logger.js: -------------------------------------------------------------------------------- 1 | const bunyan = require('bunyan') 2 | const bformat = require('bunyan-format') 3 | const formatOut = bformat({ outputMode: 'long' }) 4 | const logger = bunyan.createLogger({ 5 | name: 'smartContracts', 6 | //src: true, 7 | stream: formatOut, 8 | }) 9 | 10 | process.env.LOG_LEVEL 11 | ? logger.level(process.env.LOG_LEVEL) 12 | : logger.level('info') 13 | 14 | module.exports = logger 15 | -------------------------------------------------------------------------------- /scripts/post-merge-githook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # MIT © Sindre Sorhus - sindresorhus.com 3 | 4 | # git hook to run a command after `git pull` if a specified file was changed 5 | # Run `chmod +x post-merge` to make it executable then put it into `.git/hooks/`. 6 | 7 | changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)" 8 | 9 | check_run() { 10 | echo "$changed_files" | grep --quiet "$1" && eval "$2" 11 | } 12 | 13 | # Example usage 14 | # In this example it's used to run `yarn` if yarn.lock changed 15 | check_run yarn.lock "yarn install" || true 16 | -------------------------------------------------------------------------------- /scripts/test-frozen.js: -------------------------------------------------------------------------------- 1 | const exec = require('child_process').exec 2 | const path = require('path') 3 | 4 | // we use console to log out in this script, turning this rule off for the file 5 | /* eslint-disable no-console */ 6 | 7 | const directories = { 8 | cwd: __dirname, 9 | frozen: path.join(__dirname, '../frozen'), 10 | node_modules: path.join(__dirname, '../node_modules'), 11 | } 12 | 13 | const execPromise = (command, options) => 14 | new Promise((resolve, reject) => { 15 | const runner = exec(command, options) 16 | 17 | // we want to log output as it arrives 18 | runner.stdout.on('data', console.log) 19 | runner.stderr.on('data', console.log) 20 | 21 | // when the runner is done we can cleanup 22 | runner.on('close', exitCode => { 23 | exitCode === 0 ? resolve() : reject() 24 | }) 25 | }) 26 | 27 | const prepare = () => 28 | execPromise( 29 | ` 30 | if [[ ! -d ${directories.frozen}/node_modules ]]; then 31 | ln -s ${directories.node_modules} ${directories.frozen}/node_modules 32 | fi 33 | 34 | if [[ -d ${directories.frozen}/build ]]; then 35 | rm -rf ${directories.frozen}/build 36 | fi 37 | ` 38 | ) 39 | 40 | const runTruffleCommand = truffleCommand => () => 41 | execPromise( 42 | `node -e "require('frozen-truffle/build/cli.bundled.js');" -- noop ${truffleCommand}`, 43 | { cwd: directories.frozen } 44 | ).catch(() => { 45 | throw Error(`command "${truffleCommand}" did not exit successfully`) 46 | }) 47 | 48 | prepare() 49 | .then(runTruffleCommand('compile')) 50 | .then(runTruffleCommand('test test/main-tests/*')) 51 | .catch(error => { 52 | console.log('ERROR:', error.message) 53 | process.exit(1) 54 | }) 55 | -------------------------------------------------------------------------------- /stress-tests/POA/st-poa-eth-funding.js: -------------------------------------------------------------------------------- 1 | const logger = require('scripts/lib/logger') 2 | const BigNumber = require('bignumber.js') 3 | const { timeTravel } = require('helpers') 4 | const { getRandomBigInt, gasPrice } = require('test/helpers/general') 5 | 6 | const { 7 | determineNeededTimeTravel, 8 | setupPoaProxyAndEcosystem, 9 | testStartEthSale, 10 | issuer, 11 | custodian, 12 | testUpdateProofOfCustody, 13 | defaultIpfsHashArray32, 14 | testPayActivationFee, 15 | testActivate, 16 | testIssuerClaim, 17 | testClaimAllPayouts, 18 | testPayout, 19 | getRemainingAmountInWeiDuringEthFunding, 20 | defaultFiatCurrency, 21 | defaultFiatRate, 22 | } = require('test/helpers/poa') 23 | 24 | const { 25 | InvestmentRegistry, 26 | fundEthUntilRemainingTarget, 27 | getFundingGoal, 28 | displaySummary, 29 | } = require('../helpers/st-poa-helper') 30 | 31 | describe('PoaToken Stress Tests - test eth funding only', () => { 32 | contract('PoaToken', accounts => { 33 | const owner = accounts[0] 34 | const investmentRegistry = new InvestmentRegistry() 35 | const ethInvestors = accounts.slice(4, accounts.length) 36 | let fundingGoal 37 | let paidActivationFee 38 | 39 | let fmr 40 | let poa 41 | let exr 42 | let exp 43 | let totalPayout = new BigNumber(0) 44 | 45 | before('setup contracts', async () => { 46 | fundingGoal = await getFundingGoal({ 47 | defaultFiatRate, 48 | defaultfundingGoal: new BigNumber(1e8), // 1.000.000 EUR 49 | investors: ethInvestors, 50 | }) 51 | 52 | const contracts = await setupPoaProxyAndEcosystem({ 53 | _fundingGoal: fundingGoal, 54 | _whitelistedPoaBuyers: ethInvestors, 55 | }) 56 | 57 | fmr = contracts.fmr 58 | poa = contracts.poa 59 | exr = contracts.exr 60 | exp = contracts.exp 61 | 62 | web3.eth.sendTransaction({ 63 | from: owner, 64 | to: exp.address, 65 | value: web3.toWei(30, 'ether'), 66 | }) 67 | 68 | const neededTime = await determineNeededTimeTravel(poa) 69 | 70 | await timeTravel(neededTime) 71 | await testStartEthSale(poa, { gasPrice }) 72 | }) 73 | 74 | it('should fund with random amounts with many investors', async () => { 75 | const target = (await poa.fundingGoalInCents()).sub(10000) 76 | logger.info( 77 | `Funding with ETH investors until ${target 78 | .div(1e18) 79 | .toString()} ETH remains` 80 | ) 81 | await fundEthUntilRemainingTarget( 82 | poa, 83 | exr, 84 | exp, 85 | target, 86 | gasPrice, 87 | ethInvestors, 88 | investmentRegistry 89 | ) 90 | const remainingBuyableAmount = await getRemainingAmountInWeiDuringEthFunding( 91 | poa 92 | ) 93 | 94 | if (remainingBuyableAmount.isNegative()) { 95 | logger.info('buying remaining tokens with eth', 0) 96 | await poa.buy({ 97 | from: ethInvestors[0], 98 | value: 0, 99 | gasPrice, 100 | }) 101 | } else { 102 | logger.info( 103 | 'buying remaining tokens with eth', 104 | remainingBuyableAmount.div(1e18).toString() 105 | ) 106 | await poa.buy({ 107 | from: ethInvestors[0], 108 | value: remainingBuyableAmount, 109 | gasPrice, 110 | }) 111 | } 112 | }).timeout(1000 * 60 * 20) // set timeout to 20 minutes 113 | 114 | it('should activate', async () => { 115 | const res = await testPayActivationFee(poa, fmr) 116 | 117 | paidActivationFee = res.paidFeeAmount 118 | 119 | await testUpdateProofOfCustody(poa, defaultIpfsHashArray32, { 120 | from: custodian, 121 | }) 122 | 123 | await testActivate(poa, fmr, { 124 | from: custodian, 125 | }) 126 | }) 127 | 128 | it('should let issuer claim', async () => { 129 | await testIssuerClaim(poa) 130 | }) 131 | 132 | it('should payout many times', async () => { 133 | for (let i = 0; i < 10; i++) { 134 | const payout = getRandomBigInt(new BigNumber(1e18), new BigNumber(3e18)) 135 | 136 | await testPayout(poa, fmr, { 137 | from: issuer, 138 | value: payout, 139 | gasPrice, 140 | }) 141 | totalPayout = totalPayout.add(payout) 142 | } 143 | }) 144 | 145 | it('should let investors claim', async () => { 146 | await testClaimAllPayouts( 147 | poa, 148 | investmentRegistry.getAllInvestorAddresses() 149 | ) 150 | }) 151 | 152 | after('should display summary data', async () => { 153 | await displaySummary({ 154 | poa, 155 | fundingGoal, 156 | currency: defaultFiatCurrency, 157 | defaultFiatRate, 158 | investmentRegistry, 159 | totalPayout, 160 | paidActivationFee, 161 | }) 162 | }) 163 | }) 164 | }) 165 | -------------------------------------------------------------------------------- /stress-tests/POA/st-poa-fiat-funding.js: -------------------------------------------------------------------------------- 1 | const logger = require('scripts/lib/logger') 2 | const BigNumber = require('bignumber.js') 3 | const { 4 | getRandomBigInt, 5 | gasPrice, 6 | testWillThrow, 7 | } = require('test/helpers/general') 8 | const { timeTravel } = require('helpers') 9 | const { 10 | determineNeededTimeTravel, 11 | setupPoaProxyAndEcosystem, 12 | testStartFiatSale, 13 | issuer, 14 | custodian, 15 | testBuyTokensWithFiat, 16 | getRemainingAmountInCentsDuringFiatFunding, 17 | testUpdateProofOfCustody, 18 | defaultIpfsHashArray32, 19 | testPayActivationFee, 20 | testActivate, 21 | testIssuerClaim, 22 | testClaimAllPayouts, 23 | testPayout, 24 | defaultFiatCurrency, 25 | defaultFiatRate, 26 | } = require('test/helpers/poa') 27 | 28 | const { 29 | InvestmentRegistry, 30 | fundFiatUntilRemainingTarget, 31 | displaySummary, 32 | getFundingGoal, 33 | } = require('../helpers/st-poa-helper') 34 | 35 | describe('PoaToken Stress Tests - test fiat funding only', () => { 36 | contract('PoaToken', accounts => { 37 | const fiatInvestors = accounts.slice(4, accounts.length) 38 | const investmentRegistry = new InvestmentRegistry() 39 | let paidActivationFee 40 | let fundingGoal 41 | let fmr 42 | let poa 43 | let totalPayout = new BigNumber(0) 44 | 45 | before('setup contracts', async () => { 46 | fundingGoal = await getFundingGoal({ 47 | defaultFiatRate, 48 | defaultfundingGoal: new BigNumber(1e8), // 1.000.000 EUR 49 | investors: fiatInvestors, 50 | }) 51 | const contracts = await setupPoaProxyAndEcosystem({ 52 | _fundingGoal: fundingGoal, 53 | }) 54 | 55 | fmr = contracts.fmr 56 | poa = contracts.poa 57 | 58 | const neededTime = await determineNeededTimeTravel(poa) 59 | 60 | await timeTravel(neededTime) 61 | await testStartFiatSale(poa, { from: issuer, gasPrice }) 62 | }) 63 | 64 | it('should fund with random amounts with many investors', async () => { 65 | await fundFiatUntilRemainingTarget( 66 | poa, 67 | fundingGoal 68 | .div(100) 69 | .mul(2) 70 | .floor(), 71 | custodian, 72 | gasPrice, 73 | fiatInvestors, 74 | investmentRegistry 75 | ) 76 | 77 | const remainingFundableAmount = await getRemainingAmountInCentsDuringFiatFunding( 78 | poa 79 | ) 80 | 81 | logger.info( 82 | `Remaining fundable amount: ${remainingFundableAmount.toString()}`, 83 | { 84 | scope: 'Fiat funding', 85 | } 86 | ) 87 | 88 | await testBuyTokensWithFiat( 89 | poa, 90 | fiatInvestors[0], 91 | remainingFundableAmount, 92 | { 93 | from: custodian, 94 | gasPrice, 95 | expectedTokenDifferenceTolerance: 100000, 96 | } 97 | ) 98 | }) 99 | 100 | it('should activate', async () => { 101 | const res = await testPayActivationFee(poa, fmr) 102 | 103 | paidActivationFee = res.paidFeeAmount 104 | 105 | await testUpdateProofOfCustody(poa, defaultIpfsHashArray32, { 106 | from: custodian, 107 | }) 108 | 109 | await testActivate(poa, fmr, { 110 | from: custodian, 111 | }) 112 | }) 113 | 114 | it('should payout many times', async () => { 115 | for (let i = 0; i < 10; i++) { 116 | const payout = getRandomBigInt(new BigNumber(1e18), new BigNumber(3e18)) 117 | await testPayout(poa, fmr, { 118 | from: issuer, 119 | value: getRandomBigInt(new BigNumber(1e18), new BigNumber(3e18)), 120 | gasPrice, 121 | }) 122 | totalPayout = totalPayout.add(payout) 123 | } 124 | }) 125 | 126 | it('should NOT let issuer claim, because there are no ETH funders', async () => { 127 | await testWillThrow(testIssuerClaim, [poa]) 128 | }) 129 | 130 | it('should let investors claim', async () => { 131 | await testClaimAllPayouts( 132 | poa, 133 | investmentRegistry.getFiatInvestorAddresses() 134 | ) 135 | }) 136 | 137 | after('should display summary data', async () => { 138 | await displaySummary({ 139 | poa, 140 | fundingGoal, 141 | currency: defaultFiatCurrency, 142 | defaultFiatRate, 143 | investmentRegistry, 144 | totalPayout, 145 | paidActivationFee, 146 | }) 147 | }) 148 | }) 149 | }) 150 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | globals: { 3 | assert: true, 4 | contract: true, 5 | setTimeout: true, 6 | web3: true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/helpers/bbk.js: -------------------------------------------------------------------------------- 1 | const BrickblockToken = artifacts.require('./BrickblockToken') 2 | 3 | const distributeBbkToMany = async (bbk, accounts, amount, owner) => { 4 | return Promise.all( 5 | accounts.map(account => 6 | bbk.distributeTokens(account, amount, { from: owner }) 7 | ) 8 | ) 9 | } 10 | 11 | const finalizedBBK = async ( 12 | owner, 13 | bonusAddress, 14 | fountainAddress, 15 | contributors, 16 | tokenDistAmount 17 | ) => { 18 | const bbk = await BrickblockToken.new(bonusAddress, { from: owner }) 19 | await bbk.changeFountainContractAddress(fountainAddress, { from: owner }) 20 | await distributeBbkToMany(bbk, contributors, tokenDistAmount, owner) 21 | await bbk.finalizeTokenSale({ from: owner }) 22 | await bbk.unpause({ from: owner }) 23 | return bbk 24 | } 25 | 26 | module.exports = { 27 | distributeBbkToMany, 28 | finalizedBBK, 29 | } 30 | -------------------------------------------------------------------------------- /test/helpers/fmr.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const ContractRegistry = artifacts.require('ContractRegistry') 3 | const AccessToken = artifacts.require('AccessToken') 4 | const FeeManager = artifacts.require('FeeManager') 5 | const ExchangeRates = artifacts.require('ExchangeRates') 6 | const { getEtherBalance, getReceipt, gasPrice, bigZero } = require('./general') 7 | const { finalizedBBK } = require('./bbk') 8 | 9 | const setupContracts = async ( 10 | owner, 11 | bonusAddress, 12 | contributors, 13 | tokenDistAmount 14 | ) => { 15 | const reg = await ContractRegistry.new() 16 | const act = await AccessToken.new(reg.address) 17 | const bbk = await finalizedBBK( 18 | owner, 19 | bonusAddress, 20 | act.address, 21 | contributors, 22 | tokenDistAmount 23 | ) 24 | const exr = await ExchangeRates.new(reg.address) 25 | const fmr = await FeeManager.new(reg.address) 26 | 27 | await reg.updateContractAddress('BrickblockToken', bbk.address) 28 | await reg.updateContractAddress('AccessToken', act.address) 29 | await reg.updateContractAddress('ExchangeRates', exr.address) 30 | await reg.updateContractAddress('FeeManager', fmr.address) 31 | 32 | // check that at least one of the contributors got bbk 33 | const balanceCheck = await bbk.balanceOf(contributors[0]) 34 | const bbkPaused = await bbk.paused() 35 | assert(balanceCheck.greaterThan(0), 'the balance should be more than 0') 36 | assert(!bbkPaused, 'the contract should not be paused') 37 | return { 38 | reg, 39 | act, 40 | bbk, 41 | exr, 42 | fmr, 43 | } 44 | } 45 | 46 | const testPayFee = async (fmr, act, feePayer, feeAmount, actRate) => { 47 | const expectedActMinted = feeAmount.mul(actRate) 48 | const preActTotalSupply = await act.totalSupply() 49 | const preFeeManagerEthBalance = await getEtherBalance(fmr.address) 50 | const preFeePayerEthBalance = await getEtherBalance(feePayer) 51 | 52 | const txid = await fmr.payFee({ from: feePayer, value: feeAmount, gasPrice }) 53 | const tx = await getReceipt(txid) 54 | 55 | const { gasUsed } = tx 56 | const gasCost = gasPrice.mul(gasUsed) 57 | const postActTotalSupply = await act.totalSupply() 58 | const postFeeManagerEthBalance = await getEtherBalance(fmr.address) 59 | const postFeePayerEthBalance = await getEtherBalance(feePayer) 60 | const expectedFeePayerEthBalance = preFeePayerEthBalance 61 | .sub(feeAmount) 62 | .sub(gasCost) 63 | 64 | assert.equal( 65 | postActTotalSupply.sub(preActTotalSupply).toString(), 66 | expectedActMinted.toString(), 67 | 'minted act should matched expected amount' 68 | ) 69 | assert.equal( 70 | postFeeManagerEthBalance.sub(preFeeManagerEthBalance).toString(), 71 | feeAmount.toString(), 72 | 'the fee manager eth balance should be incremented by the feeAmount sent' 73 | ) 74 | 75 | assert.equal( 76 | expectedFeePayerEthBalance.toString(), 77 | postFeePayerEthBalance.toString(), 78 | 'the fee payer eth balance should match the expected balance' 79 | ) 80 | 81 | return postFeePayerEthBalance 82 | } 83 | 84 | const testPartialClaimFee = async (fmr, act, claimer, claimAmount, actRate) => { 85 | const expectedWeiToEth = claimAmount.div(actRate) 86 | const preFeeManagerEthBalance = await getEtherBalance(fmr.address) 87 | const preFeeClaimerEthBalance = await getEtherBalance(claimer) 88 | const preFeeManagerActBalance = await act.balanceOf(fmr.address) 89 | const preFeeClaimerActBalance = await act.balanceOf(claimer) 90 | const preActTotalSupply = await act.totalSupply() 91 | 92 | const txid = await fmr.claimFee(claimAmount, { from: claimer, gasPrice }) 93 | const tx = await getReceipt(txid) 94 | 95 | const { gasUsed } = tx 96 | const gasCost = gasPrice.mul(gasUsed) 97 | 98 | const postFeeManagerEthBalance = await getEtherBalance(fmr.address) 99 | const postFeeClaimerEthBalance = await getEtherBalance(claimer) 100 | const expectedFeeClaimerEthBalance = preFeeClaimerEthBalance 101 | .add(expectedWeiToEth) 102 | .sub(gasCost) 103 | const postFeeManagerActBalance = await act.balanceOf(fmr.address) 104 | const postFeeClaimerActBalance = await act.balanceOf(claimer) 105 | const postActTotalSupply = await act.totalSupply() 106 | 107 | assert( 108 | claimAmount.greaterThan(0), 109 | 'the act to use to claim should be greater than 0' 110 | ) 111 | assert.equal( 112 | preFeeManagerEthBalance.sub(postFeeManagerEthBalance).toString(), 113 | expectedWeiToEth.toString(), 114 | 'the fee manager eth balance should be decremented by the claimAmount sent' 115 | ) 116 | 117 | assert.equal( 118 | expectedFeeClaimerEthBalance.toString(), 119 | postFeeClaimerEthBalance.toString(), 120 | 'the fee payer eth balance should match the expected balance' 121 | ) 122 | assert.equal( 123 | preFeeManagerActBalance.toString(), 124 | bigZero.toString(), 125 | 'the fee manager ACT balance should always be 0' 126 | ) 127 | assert.equal( 128 | postFeeManagerActBalance.toString(), 129 | bigZero.toString(), 130 | 'the fee manager ACT balance should always be 0' 131 | ) 132 | assert.equal( 133 | preFeeClaimerActBalance.sub(postFeeClaimerActBalance).toString(), 134 | claimAmount.toString(), 135 | 'the ACT balance of the fee claimer should be decremented by the amount' 136 | ) 137 | assert.equal( 138 | preActTotalSupply.sub(postActTotalSupply).toString(), 139 | claimAmount.toString(), 140 | 'the ACT total supply should be decremented by the amount burned for ETH' 141 | ) 142 | 143 | return postFeeClaimerActBalance 144 | } 145 | 146 | module.exports = { 147 | testPayFee, 148 | testPartialClaimFee, 149 | setupContracts, 150 | } 151 | -------------------------------------------------------------------------------- /test/helpers/pmr.js: -------------------------------------------------------------------------------- 1 | const PoaManager = artifacts.require('PoaManager.sol') 2 | const PoaToken = artifacts.require('PoaToken.sol') 3 | const PoaCrowdsale = artifacts.require('PoaCrowdsale') 4 | 5 | const { 6 | issuer, 7 | defaultActivationDuration, 8 | defaultFiatCurrency, 9 | defaultFiatCurrency32, 10 | defaultFiatRate, 11 | defaultFiatRatePenalty, 12 | defaultFundingGoal, 13 | defaultFiatFundingDuration, 14 | defaultEthFundingDuration, 15 | defaultIpfsHashArray32, 16 | defaultName32, 17 | defaultSymbol32, 18 | defaultTotalSupply, 19 | getDefaultStartTimeForFundingPeriod, 20 | setupEcosystem, 21 | stages, 22 | testActivate, 23 | testBuyRemainingTokens, 24 | testPayActivationFee, 25 | testSetCurrencyRate, 26 | testStartPreFunding, 27 | testStartEthSale, 28 | testUpdateProofOfCustody, 29 | timeTravelToEthFundingPeriod, 30 | whitelistedEthInvestors, 31 | } = require('./poa') 32 | const { gasPrice } = require('./general') 33 | 34 | const accounts = web3.eth.accounts 35 | const owner = accounts[0] 36 | // must be accounts 2 in order to work with poa test helpers 37 | const custodian = accounts[2] 38 | 39 | const setupPoaManager = async () => { 40 | const poatm = await PoaToken.new() 41 | const poacm = await PoaCrowdsale.new() 42 | const { reg, exr, exp, fmr } = await setupEcosystem() 43 | const pmr = await PoaManager.new(reg.address) 44 | 45 | await reg.updateContractAddress('PoaManager', pmr.address) 46 | await reg.updateContractAddress('PoaTokenMaster', poatm.address) 47 | await reg.updateContractAddress('PoaCrowdsaleMaster', poacm.address) 48 | 49 | await testSetCurrencyRate( 50 | exr, 51 | exp, 52 | defaultFiatCurrency, 53 | defaultFiatRate, 54 | defaultFiatRatePenalty, 55 | { 56 | from: owner, 57 | value: 1e18, 58 | } 59 | ) 60 | 61 | return { 62 | poatm, 63 | reg, 64 | pmr, 65 | fmr, 66 | } 67 | } 68 | 69 | const addNewToken = async (pmr, config) => { 70 | const txReceipt = await pmr.addNewToken( 71 | defaultName32, 72 | defaultSymbol32, 73 | defaultFiatCurrency32, 74 | custodian, 75 | defaultTotalSupply, 76 | await getDefaultStartTimeForFundingPeriod(), 77 | defaultFiatFundingDuration, 78 | defaultEthFundingDuration, 79 | defaultActivationDuration, 80 | defaultFundingGoal, 81 | config 82 | ) 83 | 84 | const tokenAddress = txReceipt.logs[0].args.token 85 | 86 | return { 87 | tokenAddress, 88 | txReceipt, 89 | } 90 | } 91 | 92 | const moveTokenToActive = async (poa, fmr) => { 93 | // move from `Preview` to `PreFunding` stage 94 | await testStartPreFunding(poa, { from: issuer, gasPrice }) 95 | 96 | await timeTravelToEthFundingPeriod(poa) 97 | 98 | // move from `PreFunding` to `EthFunding` stage 99 | await testStartEthSale(poa) 100 | 101 | await testBuyRemainingTokens(poa, { 102 | from: whitelistedEthInvestors[whitelistedEthInvestors.length - 1], 103 | gasPrice, 104 | }) 105 | 106 | await testUpdateProofOfCustody(poa, defaultIpfsHashArray32, { 107 | from: custodian, 108 | }) 109 | 110 | await testPayActivationFee(poa, fmr) 111 | 112 | await testActivate(poa, fmr, { 113 | from: custodian, 114 | }) 115 | } 116 | 117 | const testPauseToken = async (pmr, poa, config) => { 118 | await pmr.pauseToken(poa.address, config) 119 | } 120 | 121 | const testUnpauseToken = async (pmr, poa, config) => { 122 | await pmr.unpauseToken(poa.address, config) 123 | } 124 | 125 | const testTerminateToken = async (pmr, poa, config) => { 126 | const preStage = await poa.stage() 127 | assert.equal( 128 | preStage.toString(), 129 | stages.Active, 130 | 'poa should start in stage Active' 131 | ) 132 | 133 | await pmr.terminateToken(poa.address, config) 134 | 135 | const postStage = await poa.stage() 136 | assert.equal( 137 | postStage.toString(), 138 | stages.Terminated, 139 | 'poa should be in stage Terminated after terminate' 140 | ) 141 | } 142 | 143 | module.exports = { 144 | setupPoaManager, 145 | addNewToken, 146 | moveTokenToActive, 147 | testPauseToken, 148 | testUnpauseToken, 149 | testTerminateToken, 150 | } 151 | -------------------------------------------------------------------------------- /test/helpers/storage.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js') 2 | const leftPad = require('left-pad') 3 | 4 | const getAllSequentialStorage = async addr => { 5 | let slot = 0 6 | let zeroCounter = 0 7 | const sequentialStorage = [] 8 | // eslint-disable-next-line no-constant-condition 9 | while (true) { 10 | const data = await web3.eth.getStorageAt(addr, slot) 11 | if (new BigNumber(data).equals(0)) { 12 | zeroCounter++ 13 | } 14 | 15 | sequentialStorage.push({ 16 | slot, 17 | data, 18 | }) 19 | slot++ 20 | 21 | if (zeroCounter > 20) { 22 | break 23 | } 24 | } 25 | 26 | return sequentialStorage 27 | } 28 | 29 | const findMappingStorage = async (address, key, startSlot, endSlot) => { 30 | const bigStart = startSlot.add ? startSlot : new BigNumber(startSlot) 31 | const bigEnd = endSlot.add ? endSlot : new BigNumber(endSlot) 32 | 33 | for ( 34 | let mappingSlot = bigStart; 35 | mappingSlot.lt(bigEnd); 36 | mappingSlot = mappingSlot.add(1) 37 | ) { 38 | const mappingValueSlot = getMappingSlot(mappingSlot.toString(), key) 39 | const mappingValueStorage = await web3.eth.getStorageAt( 40 | address, 41 | mappingValueSlot 42 | ) 43 | if (mappingValueStorage != '0x00') { 44 | return { 45 | mappingValueStorage, 46 | mappingValueSlot, 47 | mappingSlot, 48 | } 49 | } 50 | } 51 | 52 | return null 53 | } 54 | 55 | const standardizeInput = input => 56 | leftPad(web3.toHex(input).replace('0x', ''), 64, '0') 57 | 58 | const getMappingSlot = (mappingSlot, key) => { 59 | const mappingSlotPadded = standardizeInput(mappingSlot) 60 | const keyPadded = standardizeInput(key) 61 | const slot = web3.sha3(keyPadded.concat(mappingSlotPadded), { 62 | encoding: 'hex', 63 | }) 64 | 65 | return slot 66 | } 67 | 68 | const getMappingStorage = async (address, mappingSlot, key) => { 69 | const mappingKeySlot = getMappingSlot(mappingSlot.toString(), key) 70 | const complexStorage = await web3.eth.getStorageAt(address, mappingKeySlot) 71 | return complexStorage 72 | } 73 | 74 | const getNestedMappingStorage = async (address, mappingSlot, key, key2) => { 75 | const nestedMappingSlot = getMappingSlot(mappingSlot.toString(), key) 76 | 77 | const nestedMappingValueSlot = getMappingSlot(nestedMappingSlot, key2) 78 | 79 | const nestedMappingValueStorage = await web3.eth.getStorageAt( 80 | address, 81 | nestedMappingValueSlot 82 | ) 83 | 84 | return { 85 | nestedMappingSlot, 86 | nestedMappingValueSlot, 87 | nestedMappingValueStorage, 88 | } 89 | } 90 | 91 | const findNestedMappingStorage = async ( 92 | address, 93 | key, 94 | key2, 95 | slotStart, 96 | slotEnd 97 | ) => { 98 | const bigStart = new BigNumber(slotStart) 99 | const bigEnd = new BigNumber(slotEnd) 100 | 101 | for ( 102 | let mappingSlot = bigStart; 103 | mappingSlot.lt(bigEnd); 104 | mappingSlot = mappingSlot.add(1) 105 | ) { 106 | const nestedMappingSlot = getMappingSlot(mappingSlot.toString(), key) 107 | const nestedMappingValueSlot = getMappingSlot(nestedMappingSlot, key2) 108 | 109 | const nestedMappingValueStorage = await web3.eth.getStorageAt( 110 | address, 111 | nestedMappingValueSlot 112 | ) 113 | 114 | if (nestedMappingValueStorage != '0x00') { 115 | return { 116 | nestedMappingValueStorage, 117 | mappingSlot, 118 | nestedMappingSlot, 119 | nestedMappingValueSlot, 120 | } 121 | } 122 | } 123 | 124 | return null 125 | } 126 | 127 | // must be small enough to fit string length value in same slot as string 128 | const shortHexStringStorageToAscii = hex => 129 | web3.toAscii(hex.slice(0, parseInt('0x' + hex[hex.length - 1], 16) + 2)) 130 | 131 | const hasZeroBytesAfter = (bytesBuffer, offset) => { 132 | let hasZeroBytes = true 133 | 134 | if (offset === bytesBuffer.length) return false 135 | 136 | let index = 0 137 | for (const byte of bytesBuffer.values()) { 138 | if (index > offset) { 139 | hasZeroBytes = hasZeroBytes && byte === 0 140 | } 141 | 142 | index++ 143 | } 144 | 145 | return hasZeroBytes 146 | } 147 | 148 | const trimRightBytes = hex => { 149 | const bytesBuffer = Buffer.from(hex.replace('0x', ''), 'hex') 150 | const bytesArray = [] 151 | let counter = 0 152 | 153 | for (const byte of bytesBuffer.values()) { 154 | if ( 155 | byte !== 0 || 156 | (byte === 0 && !hasZeroBytesAfter(bytesBuffer, counter)) 157 | ) { 158 | bytesArray.push(byte) 159 | } 160 | 161 | counter++ 162 | } 163 | 164 | return '0x' + Buffer.from(bytesArray, 'hex').toString('hex') 165 | } 166 | 167 | const bytes32StorageToAscii = hex => { 168 | const trimmedBytes = trimRightBytes(hex) 169 | // because solidity also uses ascii NOT utf8 170 | return Buffer.from(trimmedBytes.replace('0x', ''), 'hex').toString('ascii') 171 | } 172 | 173 | module.exports = { 174 | getAllSequentialStorage, 175 | findMappingStorage, 176 | getMappingSlot, 177 | getMappingStorage, 178 | getNestedMappingStorage, 179 | findNestedMappingStorage, 180 | shortHexStringStorageToAscii, 181 | bytes32StorageToAscii, 182 | trimRightBytes, 183 | } 184 | -------------------------------------------------------------------------------- /test/helpers/whitelist.js: -------------------------------------------------------------------------------- 1 | const addAddress = async ({ 2 | addressToWhitelist, 3 | ownerAddress, 4 | whitelistContract, 5 | isPaused, 6 | }) => { 7 | assert.equal( 8 | await whitelistContract.whitelisted(addressToWhitelist), 9 | false, 10 | 'should start false' 11 | ) 12 | 13 | await whitelistContract.addAddress(addressToWhitelist, { 14 | from: ownerAddress, 15 | }) 16 | 17 | assert.equal( 18 | await whitelistContract.whitelisted(addressToWhitelist), 19 | isPaused ? false : true, 20 | 'should be changed to true' 21 | ) 22 | } 23 | 24 | const removeAddress = async ({ 25 | addressToWhitelist, 26 | ownerAddress, 27 | whitelistContract, 28 | isPaused, 29 | }) => { 30 | assert.equal( 31 | await whitelistContract.whitelisted(addressToWhitelist), 32 | isPaused ? false : true, 33 | 'should start true' 34 | ) 35 | 36 | await whitelistContract.removeAddress(addressToWhitelist, { 37 | from: ownerAddress, 38 | }) 39 | 40 | assert.equal( 41 | await whitelistContract.whitelisted(addressToWhitelist), 42 | false, 43 | 'should be changed to false' 44 | ) 45 | } 46 | 47 | module.exports = { addAddress, removeAddress } 48 | -------------------------------------------------------------------------------- /test/main-tests/ContractRegistry.js: -------------------------------------------------------------------------------- 1 | const ContractRegistry = artifacts.require('ContractRegistry') 2 | const RemoteContractStub = artifacts.require('stubs/RemoteContractStub') 3 | const BrokenRemoteContractStub = artifacts.require( 4 | 'stubs/BrokenRemoteContractStub' 5 | ) 6 | const RemoteContractUserStub = artifacts.require('stubs/RemoteContractUserStub') 7 | const { testWillThrow } = require('../helpers/general') 8 | 9 | const assert = require('assert') 10 | const BigNumber = require('bignumber.js') 11 | 12 | describe('when using the contract registry', () => { 13 | contract('ContractRegistry', accounts => { 14 | const initialTestNumber = new BigNumber(123) 15 | const notOwner = accounts[1] 16 | const contractNameInRegistry = 'TestName' 17 | let reg 18 | let brokenGrc 19 | let fixedGrc 20 | let grcu 21 | 22 | before('setup Registry', async () => { 23 | reg = await ContractRegistry.new() 24 | brokenGrc = await BrokenRemoteContractStub.new(initialTestNumber) 25 | grcu = await RemoteContractUserStub.new(reg.address) 26 | }) 27 | 28 | it('should error when no address for string key', async () => { 29 | await testWillThrow(reg.getContractAddress, [contractNameInRegistry]) 30 | }) 31 | 32 | it('should NOT set an address if NOT owner', async () => { 33 | await testWillThrow(reg.updateContractAddress, [ 34 | contractNameInRegistry, 35 | brokenGrc.address, 36 | { from: notOwner }, 37 | ]) 38 | }) 39 | 40 | it('should set an address', async () => { 41 | await reg.updateContractAddress(contractNameInRegistry, brokenGrc.address) 42 | const postValue = await reg.getContractAddress(contractNameInRegistry) 43 | assert.equal( 44 | postValue, 45 | brokenGrc.address, 46 | 'the address should be set to the grc address' 47 | ) 48 | }) 49 | 50 | describe('when using the remote contract through the registry', () => { 51 | it('should get the testNumber from remote contract', async () => { 52 | const testNumber = await grcu.remoteTestNumber() 53 | 54 | assert.equal(testNumber.toString(), initialTestNumber.toString()) 55 | }) 56 | 57 | it('should set the testNumber on remote contract', async () => { 58 | const newNumber = new BigNumber(321) 59 | const preNumber = await grcu.remoteTestNumber() 60 | 61 | await grcu.remoteSetNumber(newNumber) 62 | 63 | const postNumber = await grcu.remoteTestNumber() 64 | 65 | assert( 66 | preNumber.toString() != postNumber.toString(), 67 | 'the number should be different' 68 | ) 69 | assert.equal(newNumber.toString(), postNumber.toString()) 70 | }) 71 | 72 | it('should return an incorrect value when adding on broken contract', async () => { 73 | const brokenValue = await grcu.remoteAdd(1, 1) 74 | assert.equal( 75 | brokenValue.toString(), 76 | new BigNumber(2).add(3).toString(), 77 | 'the broken value should be the two numbers added plus 3' 78 | ) 79 | }) 80 | }) 81 | 82 | describe('when updating the broken contract in the registry', () => { 83 | it('should change the contract address in registry', async () => { 84 | fixedGrc = await RemoteContractStub.new(initialTestNumber) 85 | await reg.updateContractAddress( 86 | contractNameInRegistry, 87 | fixedGrc.address 88 | ) 89 | const updatedAddress = await reg.getContractAddress( 90 | contractNameInRegistry 91 | ) 92 | 93 | assert.equal( 94 | updatedAddress, 95 | fixedGrc.address, 96 | 'the updated address should match the new contract address' 97 | ) 98 | }) 99 | 100 | it('should now return the correct value', async () => { 101 | const value = await grcu.remoteAdd(1, 1) 102 | assert.equal( 103 | value.toString(), 104 | new BigNumber(2).toString(), 105 | 'the value should match the expected value' 106 | ) 107 | }) 108 | 109 | it('should not allow updating with the same address', async () => { 110 | await testWillThrow(reg.updateContractAddress, [ 111 | contractNameInRegistry, 112 | fixedGrc.address, 113 | ]) 114 | }) 115 | 116 | it('should not allow updating when the address is not a contract', async () => { 117 | await testWillThrow(reg.updateContractAddress, [ 118 | contractNameInRegistry, 119 | '0x00000000000000000000000000000000', 120 | ]) 121 | }) 122 | }) 123 | }) 124 | }) 125 | -------------------------------------------------------------------------------- /test/main-tests/EmployeeTokenSalaryPayout.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js') 2 | const { gasPrice } = require('../helpers/general') 3 | const { 4 | testAddEmployee, 5 | testAddManyEmployees, 6 | testRemoveEmployee, 7 | testPayout, 8 | } = require('../helpers/employeeTokenSalaryPayoutHelper') 9 | 10 | const EmployeeTokenSalaryPayoutArtifact = artifacts.require( 11 | 'EmployeeTokenSalaryPayout' 12 | ) 13 | const DummyContractArtifact = artifacts.require('./stubs/RemoteContractStub') 14 | const { finalizedBBK } = require('../helpers/bbk') 15 | 16 | describe('when distributing BBK bonus payouts', () => { 17 | contract('EmployeeTokenSalaryPayout', accounts => { 18 | const owner = accounts[0] 19 | const bbkHolder = accounts[1] 20 | const employees = accounts.slice(2) 21 | const defaultBbkSalaryAmount = 1000 22 | const defaultStartingBalance = 3234 23 | const defaultEndingBalance = 34552 24 | let employeeTokenSalaryPayoutContract 25 | let bbk 26 | 27 | beforeEach('setup contracts', async () => { 28 | const dummy = await DummyContractArtifact.new(1000, { from: owner }) 29 | 30 | bbk = await finalizedBBK( 31 | owner, 32 | bbkHolder, 33 | dummy.address, 34 | [bbkHolder], 35 | new BigNumber(1e24) 36 | ) 37 | employeeTokenSalaryPayoutContract = await EmployeeTokenSalaryPayoutArtifact.new( 38 | bbk.address 39 | ) 40 | await bbk.transfer( 41 | employeeTokenSalaryPayoutContract.address, 42 | new BigNumber('1e24'), 43 | { 44 | from: bbkHolder, 45 | } 46 | ) 47 | }) 48 | 49 | it('should add & remove employee', async () => { 50 | await testAddEmployee( 51 | employeeTokenSalaryPayoutContract, 52 | employees[0], 53 | defaultBbkSalaryAmount, 54 | defaultStartingBalance, 55 | { 56 | from: owner, 57 | } 58 | ) 59 | await testRemoveEmployee( 60 | bbk, 61 | employeeTokenSalaryPayoutContract, 62 | employees[0], 63 | defaultEndingBalance, 64 | { 65 | from: owner, 66 | } 67 | ) 68 | }) 69 | 70 | it('should distribute bbk to all registered employees', async () => { 71 | await testAddManyEmployees( 72 | employeeTokenSalaryPayoutContract, 73 | employees, 74 | new BigNumber(defaultBbkSalaryAmount), 75 | defaultStartingBalance, 76 | { 77 | from: owner, 78 | } 79 | ) 80 | await testPayout(bbk, employeeTokenSalaryPayoutContract, employees, { 81 | from: owner, 82 | gasPrice, 83 | }) 84 | }) 85 | 86 | it('should get correct total payout amount', async () => { 87 | await testAddManyEmployees( 88 | employeeTokenSalaryPayoutContract, 89 | employees, 90 | new BigNumber(defaultBbkSalaryAmount), 91 | defaultStartingBalance, 92 | { 93 | from: owner, 94 | } 95 | ) 96 | const expectedPayout = await employeeTokenSalaryPayoutContract.getTotalPayoutAmount() 97 | 98 | const realPayoutResult = await testPayout( 99 | bbk, 100 | employeeTokenSalaryPayoutContract, 101 | employees, 102 | { 103 | from: owner, 104 | gasPrice, 105 | } 106 | ) 107 | 108 | assert.equal( 109 | realPayoutResult.payoutAmount.toString(), 110 | expectedPayout.toString(), 111 | 'Expected payout should match' 112 | ) 113 | 114 | // eslint-disable-next-line no-console 115 | console.log( 116 | `Used gas amount for ${employees.length} accounts`, 117 | realPayoutResult.gasUsed 118 | ) 119 | }) 120 | }) 121 | }) 122 | -------------------------------------------------------------------------------- /test/main-tests/FeeManager.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js') 2 | 3 | const { testWillThrow, sendTransaction } = require('../helpers/general') 4 | const { testApproveAndLockMany } = require('../helpers/act') 5 | const { setupContracts } = require('../helpers/fmr') 6 | const { testPayFee, testPartialClaimFee } = require('../helpers/fmr') 7 | 8 | describe('when using utility functions', () => { 9 | contract('FeeManager', accounts => { 10 | const owner = accounts[0] 11 | const bonusAddress = accounts[1] 12 | const contributors = accounts.slice(3) 13 | const tokenDistAmount = new BigNumber(1e24) 14 | const actRate = new BigNumber(1e3) 15 | let fmr 16 | 17 | before('setup contracts', async () => { 18 | const contracts = await setupContracts( 19 | owner, 20 | bonusAddress, 21 | contributors, 22 | tokenDistAmount, 23 | new BigNumber(0) 24 | ) 25 | fmr = contracts.fmr 26 | }) 27 | 28 | it('should return the correct weiToAct value', async () => { 29 | const weiValue = new BigNumber(1e15) 30 | const expectedAct = weiValue.mul(actRate) 31 | const actualAct = await fmr.weiToAct(weiValue) 32 | 33 | assert.equal( 34 | expectedAct.toString(), 35 | actualAct.toString(), 36 | 'wei converted to act should match actual value' 37 | ) 38 | }) 39 | 40 | it('should return the correct actToWei value', async () => { 41 | const actValue = new BigNumber(1e21) 42 | const expectedWei = actValue.div(actRate) 43 | const actualWei = await fmr.actToWei(actValue) 44 | 45 | assert.equal( 46 | expectedWei.toString(), 47 | actualWei.toString(), 48 | 'act converted to wei should match expected value' 49 | ) 50 | }) 51 | }) 52 | }) 53 | 54 | describe('when interacting with FeeManager', () => { 55 | contract('FeeManager', accounts => { 56 | const owner = accounts[0] 57 | const bonusAddress = accounts[1] 58 | const feePayer = accounts[2] 59 | const contributors = accounts.slice(3) 60 | const claimer = contributors[0] 61 | const tokenDistAmount = new BigNumber(1e24) 62 | const tokenLockAmount = new BigNumber(1e24) 63 | const feeAmount = new BigNumber(1e19) 64 | const actRate = new BigNumber(1e3) 65 | let bbk 66 | let act 67 | let fmr 68 | 69 | before('setup contracts', async () => { 70 | const contracts = await setupContracts( 71 | owner, 72 | bonusAddress, 73 | contributors, 74 | tokenDistAmount, 75 | actRate 76 | ) 77 | bbk = contracts.bbk 78 | act = contracts.act 79 | fmr = contracts.fmr 80 | await testApproveAndLockMany(bbk, act, contributors, tokenLockAmount) 81 | }) 82 | 83 | it('should increment ether balance correctly for FeeManager', async () => { 84 | await testPayFee(fmr, act, feePayer, feeAmount, actRate) 85 | }) 86 | 87 | it('should decrement ether balance correctly for FeeManager', async () => { 88 | const actBalance = await act.balanceOf(claimer) 89 | const claimAmount = actBalance.div(2) 90 | await testPartialClaimFee(fmr, act, claimer, claimAmount, actRate) 91 | }) 92 | 93 | it('should NOT allow fallback function payments', async () => { 94 | await testWillThrow(sendTransaction, [ 95 | web3, 96 | { from: feePayer, value: feeAmount, to: fmr.address }, 97 | ]) 98 | }) 99 | 100 | it('should NOT allow claiming more ACT than balance', async () => { 101 | const actBalance = await act.balanceOf(claimer) 102 | const claimAmount = actBalance.add(1) 103 | await testWillThrow(fmr.claimFee, [claimAmount, { from: claimer }]) 104 | }) 105 | }) 106 | }) 107 | -------------------------------------------------------------------------------- /test/main-tests/PoaLogger.js: -------------------------------------------------------------------------------- 1 | const { 2 | testPreviewToPreFundingEvent, 3 | testPreFundingToFiatFundingEvent, 4 | testFiatFundingToEthFundingEvent, 5 | testBuyTokensEvents, 6 | testBuyRemainingTokensEvents, 7 | testActivateEvents, 8 | testPayoutEvents, 9 | testClaimEvents, 10 | testTerminateEvents, 11 | testChangeCustodianEvents, 12 | testReclaimEvents, 13 | } = require('../helpers/log') 14 | const { setupPoaProxyAndEcosystem } = require('../helpers/poa') 15 | 16 | describe('when using PoaLogger to log PoaToken events', () => { 17 | contract('PoaLogger', () => { 18 | let poa 19 | let log 20 | let pmr 21 | let reg 22 | let fmr 23 | 24 | before('setup contracts', async () => { 25 | const contracts = await setupPoaProxyAndEcosystem() 26 | poa = contracts.poa 27 | log = contracts.log 28 | pmr = contracts.pmr 29 | reg = contracts.reg 30 | fmr = contracts.fmr 31 | 32 | await pmr.listToken(poa.address) 33 | }) 34 | 35 | it('should log transitition from Preview to PreFunding stage', async () => { 36 | await testPreviewToPreFundingEvent(poa, reg, pmr, log) 37 | }) 38 | 39 | it('should log transitition from PreFunding to FiatFunding stage', async () => { 40 | await testPreFundingToFiatFundingEvent(poa, reg, pmr, log) 41 | }) 42 | 43 | it('should log transitition from FiatFunding to EthFunding stage', async () => { 44 | await testFiatFundingToEthFundingEvent(poa, reg, pmr, log) 45 | }) 46 | 47 | it('should log buy events', async () => { 48 | await testBuyTokensEvents(poa, reg, pmr, log) 49 | await testBuyRemainingTokensEvents(poa, reg, pmr, log) 50 | }) 51 | 52 | it('should log proof of custody updated events', async () => { 53 | await testActivateEvents(poa, reg, pmr, fmr, log) 54 | }) 55 | 56 | it('should log payout events', async () => { 57 | await testPayoutEvents(poa, reg, pmr, fmr, log) 58 | }) 59 | 60 | it('should log claim events', async () => { 61 | await testClaimEvents(poa, reg, pmr, log) 62 | }) 63 | 64 | it('should log terminated events', async () => { 65 | await testTerminateEvents(poa, reg, pmr, log) 66 | }) 67 | 68 | it('should log custodian changed events', async () => { 69 | await testChangeCustodianEvents(poa, reg, pmr, log) 70 | }) 71 | 72 | it('should log reclaim events', async () => { 73 | await testReclaimEvents() 74 | }) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /test/main-tests/PoaProxy.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const PoaProxy = artifacts.require('PoaProxy') 3 | const PoaToken = artifacts.require('PoaToken') 4 | const PoaCrowdsale = artifacts.require('PoaCrowdsale') 5 | const IPoaTokenCrowdsale = artifacts.require('IPoaTokenCrowdsale') 6 | const UpgradedPoa = artifacts.require('UpgradedPoa') 7 | 8 | const { testWillThrow } = require('../helpers/general') 9 | const { 10 | checkPreInitializedStorage, 11 | initializeContract, 12 | checkPostInitializedStorage, 13 | enterActiveStage, 14 | checkPostActiveStorage, 15 | checkPostIsUpgradedStorage, 16 | } = require('../helpers/pxy') 17 | const { 18 | testApprove, 19 | whitelistedEthInvestors, 20 | setupEcosystem, 21 | testSetCurrencyRate, 22 | defaultFiatCurrency, 23 | defaultFiatRate, 24 | defaultFiatRatePenalty, 25 | owner, 26 | } = require('../helpers/poa') 27 | 28 | describe('when using PoaProxy contract to proxy a PoaToken', () => { 29 | contract('PoaProxy/PoaToken', accounts => { 30 | let poatm 31 | let poacm 32 | let upoam 33 | let pxy 34 | let poa 35 | let reg 36 | let fmr 37 | let pmr 38 | 39 | before('setup contracts', async () => { 40 | // this sets PoaManager contract as owner in registry... storage will reflect that 41 | const contracts = await setupEcosystem() 42 | reg = contracts.reg 43 | fmr = contracts.fmr 44 | pmr = contracts.pmr 45 | const { exr, exp } = contracts 46 | poatm = await PoaToken.new() 47 | poacm = await PoaCrowdsale.new() 48 | upoam = await UpgradedPoa.new() 49 | pxy = await PoaProxy.new(poatm.address, poacm.address, reg.address) 50 | poa = await IPoaTokenCrowdsale.at(pxy.address) 51 | 52 | // set registry entries for ease of testing addresses 53 | await reg.updateContractAddress('PoaTokenMaster', poatm.address) 54 | await reg.updateContractAddress('PoaCrowdsaleMaster', poacm.address) 55 | 56 | // setup currency so that initialization will pass 57 | await testSetCurrencyRate( 58 | exr, 59 | exp, 60 | defaultFiatCurrency, 61 | defaultFiatRate, 62 | defaultFiatRatePenalty, 63 | { 64 | from: owner, 65 | value: 1e18, 66 | } 67 | ) 68 | 69 | assert.equal( 70 | poa.address, 71 | pxy.address, 72 | 'poa and pxy should have the same address' 73 | ) 74 | }) 75 | 76 | it('should have proxy addresses in storage before initialization', async () => { 77 | await checkPreInitializedStorage(poa, reg) 78 | }) 79 | 80 | it('should initializeContract', async () => { 81 | await initializeContract(poa, reg) 82 | }) 83 | 84 | it('should have new storage after initializing token and crowdsale', async () => { 85 | await checkPostInitializedStorage(poa, reg, pmr) 86 | }) 87 | 88 | it('should move to active poa stage', async () => { 89 | await enterActiveStage(poa, fmr) 90 | }) 91 | 92 | it('should approve', async () => { 93 | await testApprove(poa, whitelistedEthInvestors[1], 3e18, { 94 | from: whitelistedEthInvestors[0], 95 | }) 96 | }) 97 | 98 | it('should have correct storage after entering active', async () => { 99 | await checkPostActiveStorage(poa, reg, pmr) 100 | }) 101 | 102 | it('should NOT upgrade to new master if NOT PoaManager (accounts[0] for test)', async () => { 103 | await testWillThrow(pxy.proxyChangeTokenMaster, [ 104 | upoam.address, 105 | { from: accounts[1] }, 106 | ]) 107 | }) 108 | 109 | it('should upgrade to new master with additional functionality and storage', async () => { 110 | const preTokenMaster = await pxy.poaTokenMaster() 111 | 112 | // first must update the contract registry 113 | await reg.updateContractAddress('PoaTokenMaster', upoam.address) 114 | // then can upgrade the proxy 115 | await pmr.upgradeToken(pxy.address) 116 | 117 | const postTokenMaster = await pxy.poaTokenMaster() 118 | 119 | assert.equal( 120 | preTokenMaster, 121 | poatm.address, 122 | 'old master should be equal to poatm.address' 123 | ) 124 | assert.equal( 125 | postTokenMaster, 126 | upoam.address, 127 | 'new master should be equal to upoam.address' 128 | ) 129 | }) 130 | 131 | it('should have the same storage as before', async () => { 132 | await checkPostActiveStorage(poa, reg, pmr) 133 | poa = UpgradedPoa.at(pxy.address) 134 | }) 135 | 136 | it('should use added functionality to change isUpgraded', async () => { 137 | const preIsUpgraded = await poa.isUpgraded() 138 | 139 | await poa.setUpgrade() 140 | 141 | const postIsUpgraded = await poa.isUpgraded() 142 | 143 | assert(!preIsUpgraded, 'preIsUpgraded should be false') 144 | assert(postIsUpgraded, 'postIsUpgraded should be true') 145 | }) 146 | 147 | it('should have new storage for new bool isUpgraded after being set', async () => { 148 | await checkPostIsUpgradedStorage(poa, reg, pmr) 149 | }) 150 | }) 151 | }) 152 | -------------------------------------------------------------------------------- /test/main-tests/WarpTool.js: -------------------------------------------------------------------------------- 1 | const { warpBlocks } = require('../helpers/general') 2 | 3 | describe('when warping', () => { 4 | contract('WarpTool', () => { 5 | it('should warp to set block', async () => { 6 | const preBlock = web3.eth.blockNumber 7 | const warpAmount = 10 8 | await warpBlocks(warpAmount) 9 | const postBlock = web3.eth.blockNumber 10 | assert.equal(postBlock - preBlock, 10, 'should warp the right amount') 11 | }) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /test/main-tests/Whitelist.js: -------------------------------------------------------------------------------- 1 | // Utils 2 | const { testWillThrow } = require('../helpers/general') 3 | const { addAddress, removeAddress } = require('../helpers/whitelist') 4 | 5 | // contract under test 6 | const Whitelist = artifacts.require('./Whitelist.sol') 7 | 8 | describe('when deployed', () => { 9 | contract('Whitelist', accounts => { 10 | const [ownerAddress, addressToWhitelist, anyAddress] = accounts 11 | 12 | let whitelistContract 13 | before('setup contract', async () => { 14 | whitelistContract = await Whitelist.new({ from: ownerAddress }) 15 | }) 16 | 17 | describe('initial state', () => { 18 | it('should start with owner set as contract creator', async () => { 19 | assert.equal( 20 | await whitelistContract.owner(), 21 | ownerAddress, 22 | 'should be the contract creator' 23 | ) 24 | 25 | assert.equal( 26 | await whitelistContract.owner({ from: anyAddress }), 27 | ownerAddress, 28 | 'should be public state' 29 | ) 30 | }) 31 | 32 | it('should start unpaused', async () => { 33 | assert.equal(await whitelistContract.paused(), false, 'should be false') 34 | 35 | assert.equal( 36 | await whitelistContract.paused({ from: anyAddress }), 37 | false, 38 | 'should be public state' 39 | ) 40 | }) 41 | }) 42 | 43 | describe('when contract is unpaused', () => { 44 | describe('when ownerAddress is sending transactions', () => { 45 | it('should add an address', async () => { 46 | await addAddress({ 47 | addressToWhitelist, 48 | ownerAddress, 49 | whitelistContract, 50 | }) 51 | }) 52 | 53 | it('should not add an already whitelisted address', async () => { 54 | assert.equal( 55 | await whitelistContract.whitelisted(addressToWhitelist), 56 | true, 57 | 'address should be whitelisted' 58 | ) 59 | 60 | await testWillThrow(whitelistContract.addAddress, [ 61 | addressToWhitelist, 62 | { from: ownerAddress }, 63 | ]) 64 | }) 65 | 66 | it('should allow anyone to see whitelisted status of an address', async () => { 67 | assert.equal( 68 | await whitelistContract.whitelisted(addressToWhitelist, { 69 | from: anyAddress, 70 | }), 71 | true, 72 | 'should be public state' 73 | ) 74 | }) 75 | 76 | it('should remove an address', async () => { 77 | await removeAddress({ 78 | addressToWhitelist, 79 | ownerAddress, 80 | whitelistContract, 81 | }) 82 | }) 83 | 84 | it('should not remove an already non-whitelisted address', async () => { 85 | await testWillThrow(whitelistContract.removeAddress, [ 86 | addressToWhitelist, 87 | { from: ownerAddress }, 88 | ]) 89 | }) 90 | 91 | it('should allow anyone to see non-whitelisted status of an address', async () => { 92 | assert.equal( 93 | await whitelistContract.whitelisted(addressToWhitelist, { 94 | from: anyAddress, 95 | }), 96 | false, 97 | 'should be public state' 98 | ) 99 | }) 100 | }) 101 | 102 | describe('when a non-owner address is sending transactions', () => { 103 | it('should fail to add an address', async () => { 104 | await testWillThrow(whitelistContract.addAddress, [ 105 | addressToWhitelist, 106 | { from: anyAddress }, 107 | ]) 108 | }) 109 | 110 | it('should fail to remove an address', async () => { 111 | await testWillThrow(whitelistContract.removeAddress, [ 112 | addressToWhitelist, 113 | { from: anyAddress }, 114 | ]) 115 | }) 116 | }) 117 | }) 118 | 119 | describe('when contract is paused', () => { 120 | before(async () => { 121 | await whitelistContract.pause({ from: ownerAddress }) 122 | 123 | assert.equal(await whitelistContract.paused(), true, 'should be true') 124 | 125 | assert.equal( 126 | await whitelistContract.paused({ from: anyAddress }), 127 | true, 128 | 'should be public state' 129 | ) 130 | }) 131 | 132 | it('should add an address', async () => { 133 | await addAddress({ 134 | addressToWhitelist, 135 | ownerAddress, 136 | whitelistContract, 137 | isPaused: true, 138 | }) 139 | }) 140 | 141 | it('should return false for a whitelisted address', async () => { 142 | assert.equal( 143 | await whitelistContract.whitelisted(addressToWhitelist), 144 | false, 145 | 'always returns false when paused' 146 | ) 147 | }) 148 | 149 | it('should remove an address', async () => { 150 | await removeAddress({ 151 | addressToWhitelist, 152 | ownerAddress, 153 | whitelistContract, 154 | isPaused: true, 155 | }) 156 | }) 157 | 158 | it('should return false for a non-whitelisted address', async () => { 159 | assert.equal( 160 | await whitelistContract.whitelisted(addressToWhitelist), 161 | false, 162 | 'always returns false when paused' 163 | ) 164 | }) 165 | }) 166 | }) 167 | }) 168 | -------------------------------------------------------------------------------- /test/main-tests/poaTokenProxy/poaNoStage.js: -------------------------------------------------------------------------------- 1 | const { 2 | custodian, 3 | setupPoaProxyAndEcosystem, 4 | testCalculateFee, 5 | testChangeCustodianAddress, 6 | testFallback, 7 | testFiatCentsToWei, 8 | testWeiToFiatCents, 9 | whitelistedEthInvestors, 10 | } = require('../../helpers/poa') 11 | const { testWillThrow } = require('../../helpers/general.js') 12 | const BigNumber = require('bignumber.js') 13 | 14 | describe('when testing stage independent functions', () => { 15 | contract('PoaTokenProxy', () => { 16 | let poa 17 | 18 | before('setup contracts', async () => { 19 | const contracts = await setupPoaProxyAndEcosystem() 20 | poa = contracts.poa 21 | }) 22 | 23 | it('should use weiToFiatCents to return correct value', async () => { 24 | await testWeiToFiatCents(poa, new BigNumber('1e18')) 25 | }) 26 | 27 | it('should use fiatCentsToWei to return correct value', async () => { 28 | await testFiatCentsToWei(poa, new BigNumber('3e4')) 29 | }) 30 | 31 | it('should calculate correct fee', async () => { 32 | await testCalculateFee(poa, new BigNumber('1e18')) 33 | }) 34 | 35 | it('should NOT changeCustodianAddress when NOT custodian', async () => { 36 | await testWillThrow(testChangeCustodianAddress, [ 37 | poa, 38 | whitelistedEthInvestors[1], 39 | { from: whitelistedEthInvestors[2] }, 40 | ]) 41 | }) 42 | 43 | it('should change changeCustodianAddress', async () => { 44 | await testChangeCustodianAddress(poa, whitelistedEthInvestors[2], { 45 | from: custodian, 46 | }) 47 | }) 48 | 49 | it('should NOT allow payable fallback to run', async () => { 50 | await testFallback({ 51 | from: whitelistedEthInvestors[0], 52 | value: 3e17, 53 | to: poa.address, 54 | }) 55 | }) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /test/main-tests/poaTokenProxy/poaNormalFlow.js: -------------------------------------------------------------------------------- 1 | const { 2 | issuer, 3 | custodian, 4 | defaultIpfsHashArray32, 5 | setupPoaProxyAndEcosystem, 6 | testActivate, 7 | testIssuerClaim, 8 | testBuyRemainingTokens, 9 | testBuyTokens, 10 | testClaimAllPayouts, 11 | testPayActivationFee, 12 | testPayout, 13 | testStartPreFunding, 14 | testStartEthSale, 15 | testUpdateProofOfCustody, 16 | timeTravelToEthFundingPeriod, 17 | whitelistedEthInvestors, 18 | } = require('../../helpers/poa') 19 | const { gasPrice } = require('../../helpers/general.js') 20 | 21 | describe("when going through Poa's normal flow", async () => { 22 | contract('PoaTokenProxy', () => { 23 | let fmr 24 | let poa 25 | 26 | before('setup contracts', async () => { 27 | const contracts = await setupPoaProxyAndEcosystem() 28 | poa = contracts.poa 29 | fmr = contracts.fmr 30 | }) 31 | 32 | it("should move from 'Preview' to 'PreFunding' stage", async () => { 33 | await testStartPreFunding(poa, { from: issuer, gasPrice }) 34 | }) 35 | 36 | it('should move from PreFunding to EthFunding after startTimeForEthFundingPeriod', async () => { 37 | await timeTravelToEthFundingPeriod(poa) 38 | 39 | // move from `PreFunding` to `EthFunding` stage 40 | await testStartEthSale(poa) 41 | }) 42 | 43 | it('should allow buying', async () => { 44 | await testBuyTokens(poa, { 45 | from: whitelistedEthInvestors[0], 46 | value: 5e17, 47 | gasPrice, 48 | }) 49 | }) 50 | 51 | it("should buy all remaining tokens, moving to 'FundingSuccessful' stage", async () => { 52 | await testBuyRemainingTokens(poa, { 53 | from: whitelistedEthInvestors[1], 54 | gasPrice, 55 | }) 56 | }) 57 | 58 | it('should update proof of custody', async () => { 59 | await testUpdateProofOfCustody(poa, defaultIpfsHashArray32, { 60 | from: custodian, 61 | }) 62 | }) 63 | 64 | it('issuer should pay initial fee', async () => { 65 | await testPayActivationFee(poa, fmr) 66 | }) 67 | 68 | it('should activate with ipfs hash from custodian', async () => { 69 | await testActivate(poa, fmr, { 70 | from: custodian, 71 | }) 72 | }) 73 | 74 | it('should claim contract funding as issuer', async () => { 75 | await testIssuerClaim(poa) 76 | }) 77 | 78 | it('should allow payouts by issuer', async () => { 79 | await testPayout(poa, fmr, { 80 | from: issuer, 81 | value: 2e18, 82 | gasPrice, 83 | }) 84 | }) 85 | 86 | it('should allow all token holders to claim', async () => { 87 | await testClaimAllPayouts(poa, whitelistedEthInvestors) 88 | }) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /test/misc/ExchangeRateGasCosts.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js') 2 | const { setupContracts } = require('../helpers/exr') 3 | const { table } = require('table') 4 | 5 | describe('when analyzing gas costs', () => { 6 | contract('ExchangeRates/ExchangeRatesProviderStub', accounts => { 7 | const owner = accounts[0] 8 | const callInterval = new BigNumber(60) 9 | const callbackGasLimit = new BigNumber(20e9) 10 | const queryString = 'https://domain.com/api/?base=ETH&to=USD' 11 | const queryType = 'USD' 12 | const ratePenalty = 20 // in permille => 20/1000 = 2% 13 | let exr 14 | 15 | before('setup contracts', async () => { 16 | const contracts = await setupContracts() 17 | exr = contracts.exr 18 | }) 19 | 20 | it('should output to console gas costs', async () => { 21 | const data = [['Function', 'Gas Used']] 22 | 23 | //setCurrencySettings 24 | const txSetCurrencySettings = await exr.setCurrencySettings( 25 | queryType, 26 | queryString, 27 | callInterval, 28 | callbackGasLimit, 29 | ratePenalty, 30 | { from: owner } 31 | ) 32 | data.push(['setCurrencySettings', txSetCurrencySettings.receipt.gasUsed]) 33 | 34 | //getCurrencySettings 35 | const txFetchRate = await exr.fetchRate(queryType, { 36 | from: owner, 37 | value: 1e18, 38 | }) 39 | 40 | data.push(['txFetchRate', txFetchRate.receipt.gasUsed]) 41 | 42 | // eslint-disable-next-line 43 | console.log(table(data)) 44 | }) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | const HDWalletProvider = require('truffle-hdwallet-provider') 2 | const infuraApiKey = process.env.INFURA_API_KEY 3 | const testnetMnemonic = process.env.TESTNET_MNEMONIC 4 | const mainnetMnemonic = process.env.MAINNET_MNEMONIC 5 | const providerUrl = process.env.PROVIDER_URL 6 | 7 | module.exports = { 8 | networks: { 9 | dev: { 10 | host: 'localhost', 11 | port: 8545, 12 | network_id: 4447, 13 | gasPrice: 1e9, 14 | }, 15 | devGeth: { 16 | host: 'localhost', 17 | port: 8545, 18 | network_id: '*', 19 | // Values below 6000000 fail often because of the required minimum block gas amount. 20 | gas: 6300000, 21 | }, 22 | ropsten: { 23 | network_id: 3, 24 | gas: 4.6e6, 25 | gasPrice: 5e9, 26 | provider: () => 27 | new HDWalletProvider( 28 | testnetMnemonic, 29 | providerUrl || 'https://ropsten.infura.io/v3/' + infuraApiKey, 30 | 0, 31 | 10 32 | ), 33 | }, 34 | kovan: { 35 | network_id: 42, 36 | gas: 6.5e6, 37 | gasPrice: 5e9, 38 | provider: () => 39 | new HDWalletProvider( 40 | testnetMnemonic, 41 | providerUrl || 'https://kovan.infura.io/v3/' + infuraApiKey, 42 | 0, 43 | 10 44 | ), 45 | }, 46 | rinkeby: { 47 | network_id: 4, 48 | gas: 6.5e6, 49 | gasPrice: 5e9, 50 | provider: () => 51 | new HDWalletProvider( 52 | testnetMnemonic, 53 | providerUrl || 'https://rinkeby.infura.io/v3/' + infuraApiKey, 54 | 0, 55 | 10 56 | ), 57 | }, 58 | mainnet: { 59 | network_id: 1, 60 | gas: 6.5e6, 61 | gasPrice: 5e9, 62 | provider: () => 63 | new HDWalletProvider( 64 | mainnetMnemonic, 65 | providerUrl || 'https://mainnet.infura.io/v3/' + infuraApiKey, 66 | 0, 67 | 10 68 | ), 69 | }, 70 | // to be used when we want to interact in a local truffle console session 71 | hdwallet: { 72 | provider: () => { 73 | return new HDWalletProvider( 74 | // NOTE: this can be any valid mnemonic, as long as you are making `calls` and not `transactions` 75 | process.env.HDWALLET_MNEMONIC, 76 | // NOTE: this is the network you want to connect to; if you are running a local network you 77 | // can connect to that instead of infura but then you probably want to use: 78 | // `truffle console --network dev` 79 | providerUrl, 80 | 0, 81 | 10 82 | ) 83 | }, 84 | network_id: '*', 85 | }, 86 | }, 87 | compilers: { 88 | solc: { 89 | version: '0.4.24', 90 | }, 91 | }, 92 | mocha: { 93 | /* 94 | * Default reporter: 'spec' 95 | * + Prints out test duration 96 | * + Faster 97 | * - Doesn't display gas costs 98 | * 99 | * Gas cost reporter: 'eth-gas-reporter' 100 | * + Can analyze gas costs 101 | * - Slow 102 | * - Doesn't display test duration 103 | */ 104 | reporter: process.env.GAS_REPORTER ? 'eth-gas-reporter' : 'spec', 105 | }, 106 | } 107 | --------------------------------------------------------------------------------