├── .circleci └── config.yml ├── .gitignore ├── .soliumignore ├── .soliumrc.json ├── AUDIT.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── artifacts ├── README.md ├── compiled │ └── v1 │ │ ├── AddressUtils.json │ │ ├── CvcEscrow.json │ │ ├── CvcEscrowInterface.json │ │ ├── CvcMigrator.json │ │ ├── CvcOntology.json │ │ ├── CvcOntologyInterface.json │ │ ├── CvcPricing.json │ │ ├── CvcPricingInterface.json │ │ ├── CvcProxy.json │ │ ├── CvcToken.json │ │ ├── CvcValidatorRegistry.json │ │ ├── CvcValidatorRegistryInterface.json │ │ ├── ERC20.json │ │ ├── ERC20Basic.json │ │ ├── EternalStorage.json │ │ ├── ImplementationStorage.json │ │ ├── Initializable.json │ │ ├── Migrations.json │ │ ├── Ownable.json │ │ ├── Pausable.json │ │ ├── SafeMath.json │ │ ├── StandardToken.json │ │ ├── UpgradeAgent.json │ │ └── UpgradeableToken.json └── deployed │ └── .gitkeep ├── audit-ci.json ├── constants.js ├── contracts ├── Migrations.sol ├── README.md ├── escrow │ ├── CvcEscrow.sol │ └── CvcEscrowInterface.sol ├── idv │ ├── CvcValidatorRegistry.sol │ └── CvcValidatorRegistryInterface.sol ├── ontology │ ├── CvcOntology.sol │ └── CvcOntologyInterface.sol ├── pricing │ ├── CvcPricing.sol │ └── CvcPricingInterface.sol └── upgradeability │ ├── CvcMigrator.sol │ ├── CvcProxy.sol │ ├── EternalStorage.sol │ ├── ImplementationStorage.sol │ ├── Initializable.sol │ ├── Ownable.sol │ └── Pausable.sol ├── docker-compose.yml ├── migrations ├── 1_initial_migration.js ├── 2_deploy_contracts_v1.js ├── 3_mint_cvc_tokens.js ├── 4_default_ontology_records.js ├── 5_default_prices.js ├── README.md └── utils │ ├── compiledContractHelper.js │ ├── deployedContractHelper.js │ └── index.js ├── package-lock.json ├── package.json ├── scripts ├── downloadArtifacts.sh ├── uploadArtifacts.sh └── withTunnel.sh ├── test ├── TestCvcEscrow.js ├── TestCvcMigrator.js ├── TestCvcOntology.js ├── TestCvcPricing.js ├── TestCvcProxy.js ├── TestCvcValidatorRegistry.js ├── helpers │ └── encodeCall.js └── stubs │ ├── ClashingImplementation.sol │ ├── DummyImplementation.sol │ ├── OntologyAccessor.sol │ ├── Ownable.sol │ ├── PricingAccessor.sol │ ├── TestProxyImplementationV0.sol │ └── TestProxyImplementationV1.sol └── truffle.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | defaults: 2 | - &cache_restore 3 | restore_cache: 4 | keys: 5 | - v1-dependencies-{{ checksum "package.json" }}-{{checksum "package-lock.json" }} 6 | - &cache_save 7 | save_cache: 8 | paths: 9 | - node_modules 10 | key: v1-dependencies-{{ checksum "package.json" }}-{{checksum "package-lock.json" }} 11 | 12 | version: 2 13 | workflows: 14 | version: 2 15 | build-and-deploy-to-dev: 16 | jobs: 17 | - build 18 | - deploy-contracts-to-dev: 19 | context: Development 20 | requires: 21 | - build 22 | filters: 23 | branches: 24 | only: master 25 | - build-marketplace-tx-server-latest: 26 | context: Development 27 | requires: 28 | - deploy-contracts-to-dev 29 | filters: 30 | branches: 31 | only: master 32 | 33 | deploy-to-test: 34 | jobs: 35 | - deploy-contracts-to-test: 36 | context: Development 37 | filters: 38 | branches: 39 | only: release 40 | - build-marketplace-tx-server-test: 41 | context: Development 42 | requires: 43 | - deploy-contracts-to-test 44 | filters: 45 | branches: 46 | only: release 47 | 48 | jobs: 49 | build: 50 | docker: 51 | - image: circleci/node:8.15 52 | - image: trufflesuite/ganache-cli 53 | command: [ganache, -m, 'drum muscle maximum umbrella night initial prevent auction pink old fault media', -h, '0.0.0.0', -l, '7992181', -g, '1000000000', -e, '1000'] 54 | 55 | working_directory: ~/repo 56 | 57 | steps: 58 | - checkout 59 | - *cache_restore 60 | - run: npm ci 61 | - run: npm run audit-ci 62 | - *cache_save 63 | - run: npm run check-ci 64 | 65 | deploy-contracts-to-dev: 66 | docker: 67 | # image extended from circleci/node:8.9 and includes AWS CLI 68 | - image: civicteam/circle-aws-node 69 | 70 | working_directory: ~/repo 71 | 72 | steps: 73 | - checkout 74 | - *cache_restore 75 | - run: npm ci 76 | - *cache_save 77 | 78 | - run: scripts/downloadArtifacts.sh dev 79 | - run: NETWORK=tunnel STAGE=dev scripts/withTunnel.sh npm run deploy-contracts 80 | - run: scripts/uploadArtifacts.sh dev 81 | 82 | deploy-contracts-to-test: 83 | docker: 84 | # image extended from circleci/node:8.9 and includes AWS CLI 85 | - image: civicteam/circle-aws-node 86 | 87 | working_directory: ~/repo 88 | 89 | steps: 90 | - checkout 91 | - *cache_restore 92 | - run: npm ci 93 | - *cache_save 94 | 95 | - run: scripts/downloadArtifacts.sh test 96 | - run: NETWORK=tunnel STAGE=test scripts/withTunnel.sh npm run deploy-contracts 97 | - run: scripts/uploadArtifacts.sh test 98 | 99 | build-marketplace-tx-server-latest: 100 | docker: 101 | - image: circleci/node:8.15 102 | 103 | steps: 104 | - run: curl -d "build_parameters[CIRCLE_JOB]=build-docker-latest" "https://circleci.com/api/v1.1/project/github/civicteam/civic_js_node_server/tree/dev?circle-token=${CIRCLE_TOKEN}" 105 | 106 | build-marketplace-tx-server-test: 107 | docker: 108 | - image: circleci/node:8.15 109 | 110 | steps: 111 | - run: curl -d "build_parameters[CIRCLE_JOB]=build-docker-test" "https://circleci.com/api/v1.1/project/github/civicteam/civic_js_node_server/tree/master?circle-token=${CIRCLE_TOKEN}" 112 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | 4 | # Integration test output from geth node 5 | /geth.log 6 | /.nyc_output/ 7 | 8 | # Logs 9 | logs 10 | *.log 11 | 12 | # Directories used by tools like mocha & istanbul 13 | coverage 14 | shippable 15 | 16 | # Generated files 17 | config/development/contracts.json 18 | config/development/*.sol.js 19 | config/test/contracts.json 20 | 21 | # Shared artifacts 22 | /artifacts/deployed/*.json 23 | -------------------------------------------------------------------------------- /.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": [ 4 | "security" 5 | ], 6 | "rules": { 7 | "quotes": [ 8 | "error", 9 | "double" 10 | ], 11 | "indentation": [ 12 | "error", 13 | 4 14 | ], 15 | "function-order": [ 16 | "warning" 17 | ], 18 | "no-experimental": [ 19 | "off" 20 | ], 21 | "security/no-inline-assembly": [ 22 | "off" 23 | ], 24 | "zeppelin/constant-candidates": [ 25 | "warning" 26 | ], 27 | "zeppelin/highlight-comments": [ 28 | "warning" 29 | ], 30 | "zeppelin/missing-natspec-comments": [ 31 | "off" 32 | ], 33 | "zeppelin/no-arithmetic-operations": [ 34 | "warning" 35 | ], 36 | "zeppelin/no-state-variable-shadowing": [ 37 | "warning" 38 | ], 39 | "zeppelin/no-unchecked-send": [ 40 | "warning" 41 | ], 42 | "zeppelin/no-unused-imports": [ 43 | "warning" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /AUDIT.md: -------------------------------------------------------------------------------- 1 | This project uses [npm audit](https://docs.npmjs.com/cli/audit) to scan dependencies for vulnerabilities 2 | and automatically install any compatible updates to vulnerable dependencies. 3 | The security audit is also integrated into the project's CI pipeline via [audit-ci](https://github.com/IBM/audit-ci) command 4 | which fails the build if there is any vulnerability found. 5 | It is possible to ignore specific errors by whitelisting them in [audit-ci config.](./audit-ci.json). 6 | 7 | ## NPM audit whitelist 8 | Whenever you whitelist a specific advisory it is required to refer it to here and justify the whitelisting. 9 | 10 | ### Advisories 11 | 12 | | # | Level | Module | Title | Explanation | 13 | |------|-------|---------|------|-------------| 14 | | 782 | Moderate | lodash | Prototype Pollution | truffle-artifactor dependency (requires major version update) | 15 | | 786 | Low | braces | Regular Expression Denial of Service | Solium dev dependency | 16 | 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Open Source Code of Conduct 2 | This code of conduct outlines our expectations for participants within the Identity.com Open Source community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community. 3 | 4 | 5 | # Our open source community strives to: 6 | 7 | 8 | * Be friendly and patient. 9 | * Be welcoming: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, color, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. 10 | * Be considerate: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we’re a world-wide community, so you might not be communicating in someone else’s primary language. 11 | * Be respectful: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. 12 | * Be careful in the words that you choose: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren’t acceptable. This includes, but is not limited to: 13 | * Violent threats or language directed against another person. 14 | * Discriminatory jokes and language. 15 | * Posting sexually explicit or violent material. 16 | * Posting (or threatening to post) other people’s personally identifying information (“doxing”). 17 | * Personal insults, especially those using racist or sexist terms. 18 | * Unwelcome sexual attention. 19 | * Advocating for, or encouraging, any of the above behavior. 20 | * Repeated harassment of others. In general, if someone asks you to stop, then stop. 21 | * When we disagree, try to understand why: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. 22 | * Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. 23 | 24 | 25 | This code is not exhaustive or complete. It serves to distill our common understanding of a collaborative, shared environment, and goals. We expect it to be followed in spirit as much as in the letter. 26 | 27 | 28 | # Diversity Statement 29 | We encourage everyone to participate and are committed to building a community for all. Although we may not be able to satisfy everyone, we all agree that everyone is equal. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong. 30 | 31 | 32 | Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected characteristics above, including participants with disabilities. 33 | 34 | 35 | # Reporting Issues 36 | If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via https://www.identity.com/about/contact-us/. All reports will be handled with discretion. In your report please include: 37 | 38 | * Your contact information. 39 | * Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link. 40 | * Any additional information that may be helpful. 41 | 42 | 43 | After filing a report, a representative will contact you personally. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. A representative will then review the incident, follow up with any additional questions, and make a decision as to how to respond. We will respect confidentiality requests for the purpose of protecting victims of abuse. 44 | 45 | 46 | Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the representative may take any action they deem appropriate, up to and including a permanent ban from our community without warning. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | We’re still working out the kinks to make contributing to this project as easy and transparent as possible, but we’re not quite there yet. Hopefully this document makes the process for contributing clear and answers some questions that you may have. 3 | 4 | # Code of Conduct 5 | Identity.com has adopted a [Code of Conduct](CODE_OF_CONDUCT.md) that we expect project participants to adhere to. Please read the full text so that you can understand what actions will and will not be tolerated. 6 | 7 | # Contributors & Team Members Development 8 | Most internal changes made by Civic & Identity.com engineers will be synced to GitHub. Civic & Identity.com use this code in production. The team is likely to have an enterprise version of the code containing specific environment details and that part is not synced with the code here. 9 | Changes from the community are handled through GitHub pull requests which go through our review process. Once a change made on GitHub is approved, it will be imported to the Civic & Identity.com internal repositories. 10 | 11 | # Branch Organization 12 | We will do our best to keep the master branch in good shape, with tests passing at all times. But in order to move fast, we will make API changes that your application might not be compatible with. We recommend that you use the latest stable and published version. 13 | If you send a pull request, please do it against the master branch. We maintain stable branches for major versions separately.We accept pull requests against latest major branch directly if it is a bugfix related to its version. This fix will be also applied to the master branch by the Core team. 14 | 15 | # Semantic Versioning 16 | This software follows semantic versioning. We release patch versions for bug fixes, minor versions for new features, and major versions for any breaking changes. When we make breaking changes, we also introduce deprecation warnings in a minor version so that our users learn about the upcoming changes and migrate their code in advance. 17 | Every significant change is documented in the changelog file. 18 | 19 | # Bugs 20 | ## Where to Find Known Issues 21 | We are using GitHub Issues for our public bugs. Core team will keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist. 22 | ## Reporting New Issues 23 | The best way to get your bug fixed is to provide a reduced test case. 24 | How to Get in Touch 25 | 26 | GitHub Issues: Create a ticket with the specific tag: [question] ; [feature request]; [suggestion] ; [discussion] 27 | Identity.com website contact form 28 | 29 | # Proposing a Change 30 | If you intend to change the public API, or make any non-trivial changes to the implementation, we recommend filing an issue. This lets us reach an agreement on your proposal before you put significant effort into it. 31 | If you’re only fixing a bug, it’s fine to submit a pull request right away but we still recommend to file an issue detailing what you’re fixing. This is helpful in case we don’t accept that specific fix but want to keep track of the issue. 32 | 33 | # Your First Pull Request 34 | Working on your first Pull Request? You can learn how from this free video series: 35 | [How to Contribute](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) to an Open Source Project on GitHub 36 | If you decide to fix an issue, please be sure to check the comment thread in case somebody is already working on a fix. If nobody is working on it at the moment, please leave a comment stating that you intend to work on it so other people don’t accidentally duplicate your effort. 37 | If somebody claims an issue but doesn’t follow up for more than two weeks, it’s fine to take it over but you should still leave a comment. 38 | 39 | # Sending a Pull Request 40 | The Identity.com team is monitoring for pull requests. We will review your pull request and either merge it, request changes to it, or close it with an explanation. 41 | Before submitting a pull request, please make sure the following is done: 42 | Fork the repository and create your branch from master. 43 | Run `npm install` in the repository root. 44 | If you’ve fixed a bug or added code that should be tested, add tests! 45 | Ensure the test suite passes (`npm test`). 46 | Format your code with eslint (`npm run lint`). 47 | If you haven’t already, complete the CLA. 48 | 49 | # Contributor License Agreement (CLA) 50 | By contributing to Identity.com projects, you agree that your contributions will be licensed under its MIT license. 51 | Contribution Prerequisites 52 | 53 | # Prerequisites 54 | Please follow [README](README.md) instructions. 55 | 56 | # Development Workflow 57 | Please follow [README](README.md) instructions. 58 | 59 | # Style Guide 60 | We use an automatic code formatter called ESLint. Run `npm run lint` after making any changes to the code. 61 | Then, our linter will catch most issues that may exist in your code. 62 | However, there are still some styles that the linter cannot pick up. If you are unsure about something, looking at Airbnb’s Style Guide will guide you in the right direction. 63 | 64 | # License 65 | By contributing to Identity.com projects, you agree that your contributions will be licensed under its MIT license. 66 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Identity.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Identity.com Smart Contracts 2 | 3 | ## Summary 4 | 5 | The smart contracts of the [Identity.com](https://www.identity.com/) marketplace. 6 | 7 | Identity.com grants users, requesters, and validators around the world entry to accessible, reusable identity verification powered by CVC tokens. 8 | 9 | ## Contracts 10 | 11 | ### Functional Contracts 12 | 13 | #### CvcOntology: 14 | 15 | Holds the list of all recognized Credential Items available for sale in the ecosystem. 16 | 17 | #### CvcPricing: 18 | 19 | Stores actual prices for Credential Items available for sale. 20 | It allows registered Identity Validators to set or delete prices for specific Credential Items. 21 | 22 | #### CvcEscrow: 23 | 24 | Provides an escrow service for the Identity.com marketplace. 25 | It controls an escrow placement's lifecycle which involves transferring a pre-approved amount funds 26 | 27 | #### CvcValidatorRegistry: 28 | 29 | A registry for Identity Validators (IDV). It is part of the marketplace access control mechanism. 30 | 31 | ### Support Contracts 32 | 33 | - CvcMigrator 34 | - CvcProxy 35 | - EternalStorage 36 | - ImplementationStorage 37 | - Initializable 38 | - Ownable 39 | - Pausable 40 | - Migrations 41 | 42 | For details on the migration and proxy patterns used in Identity.com, see [migrations/README.md](migrations/README.md) 43 | 44 | ## Project structure 45 | 46 | The project follows the Truffle framework basic structure: 47 | - `migrations` folder for the migration files 48 | - `contracts` folder for smart contract source code files 49 | - `test` for smart contracts unit tests 50 | - `truffle.js` file at the top level for network management. 51 | 52 | # Testing 53 | 54 | All tests are running against Truffle's Ganache. 55 | We have the `docker-compose.yml` file with default setup for ganache to speed up environment setup. 56 | See `package.json` for available testing commands. 57 | 58 | # Running migrations 59 | 60 | Setup connection to your ethereum node (ganache/privatenet/testnet/mainnet). 61 | Run `deploy-contracts` npm command. 62 | Resulting JSON artifacts with ABIs and network addresses will be in `artifacts/deployed` folder. 63 | -------------------------------------------------------------------------------- /artifacts/README.md: -------------------------------------------------------------------------------- 1 | # Artifact folder 2 | 3 | - Compiled: compiled artifacts with ABI and bytecode. Used for versioning. 4 | - Deployed: the results of the latest migration run. 5 | -------------------------------------------------------------------------------- /artifacts/compiled/v1/AddressUtils.json: -------------------------------------------------------------------------------- 1 | { 2 | "contractName": "AddressUtils", 3 | "abi": [], 4 | "bytecode": "0x604c602c600b82828239805160001a60731460008114601c57601e565bfe5b5030600052607381538281f30073000000000000000000000000000000000000000030146080604052600080fd00a165627a7a72305820529e5325d2ee8ea89d689631535ae827cae58edd1709562e8c269861ad7277060029", 5 | "deployedBytecode": "0x73000000000000000000000000000000000000000030146080604052600080fd00a165627a7a72305820529e5325d2ee8ea89d689631535ae827cae58edd1709562e8c269861ad7277060029", 6 | "sourceMap": "87:930:16:-;;132:2:-1;166:7;155:9;146:7;137:37;252:7;246:14;243:1;238:23;232:4;229:33;270:1;265:20;;;;222:63;;265:20;274:9;222:63;;298:9;295:1;288:20;328:4;319:7;311:22;352:7;343;336:24", 7 | "deployedSourceMap": "87:930:16:-;;;;;;;;", 8 | "source": "pragma solidity ^0.4.23;\n\n\n/**\n * Utility library of inline functions on addresses\n */\nlibrary AddressUtils {\n\n /**\n * Returns whether the target address is a contract\n * @dev This function will return false if invoked during the constructor of a contract,\n * as the code is not actually created until after the constructor finishes.\n * @param addr address to check\n * @return whether the target address is a contract\n */\n function isContract(address addr) internal view returns (bool) {\n uint256 size;\n // XXX Currently there is no better way to check if there is a contract in an address\n // than to check the size of the code at that address.\n // See https://ethereum.stackexchange.com/a/14016/36603\n // for more details about how this works.\n // TODO Check this again before the Serenity release, because all addresses will be\n // contracts then.\n // solium-disable-next-line security/no-inline-assembly\n assembly { size := extcodesize(addr) }\n return size > 0;\n }\n\n}\n", 9 | "sourcePath": "openzeppelin-solidity/contracts/AddressUtils.sol", 10 | "ast": { 11 | "absolutePath": "openzeppelin-solidity/contracts/AddressUtils.sol", 12 | "exportedSymbols": { 13 | "AddressUtils": [ 14 | 4977 15 | ] 16 | }, 17 | "id": 4978, 18 | "nodeType": "SourceUnit", 19 | "nodes": [ 20 | { 21 | "id": 4960, 22 | "literals": [ 23 | "solidity", 24 | "^", 25 | "0.4", 26 | ".23" 27 | ], 28 | "nodeType": "PragmaDirective", 29 | "src": "0:24:16" 30 | }, 31 | { 32 | "baseContracts": [], 33 | "contractDependencies": [], 34 | "contractKind": "library", 35 | "documentation": "Utility library of inline functions on addresses", 36 | "fullyImplemented": true, 37 | "id": 4977, 38 | "linearizedBaseContracts": [ 39 | 4977 40 | ], 41 | "name": "AddressUtils", 42 | "nodeType": "ContractDefinition", 43 | "nodes": [ 44 | { 45 | "body": { 46 | "id": 4975, 47 | "nodeType": "Block", 48 | "src": "501:513:16", 49 | "statements": [ 50 | { 51 | "assignments": [], 52 | "declarations": [ 53 | { 54 | "constant": false, 55 | "id": 4968, 56 | "name": "size", 57 | "nodeType": "VariableDeclaration", 58 | "scope": 4976, 59 | "src": "507:12:16", 60 | "stateVariable": false, 61 | "storageLocation": "default", 62 | "typeDescriptions": { 63 | "typeIdentifier": "t_uint256", 64 | "typeString": "uint256" 65 | }, 66 | "typeName": { 67 | "id": 4967, 68 | "name": "uint256", 69 | "nodeType": "ElementaryTypeName", 70 | "src": "507:7:16", 71 | "typeDescriptions": { 72 | "typeIdentifier": "t_uint256", 73 | "typeString": "uint256" 74 | } 75 | }, 76 | "value": null, 77 | "visibility": "internal" 78 | } 79 | ], 80 | "id": 4969, 81 | "initialValue": null, 82 | "nodeType": "VariableDeclarationStatement", 83 | "src": "507:12:16" 84 | }, 85 | { 86 | "externalReferences": [ 87 | { 88 | "size": { 89 | "declaration": 4968, 90 | "isOffset": false, 91 | "isSlot": false, 92 | "src": "962:4:16", 93 | "valueSize": 1 94 | } 95 | }, 96 | { 97 | "addr": { 98 | "declaration": 4962, 99 | "isOffset": false, 100 | "isSlot": false, 101 | "src": "982:4:16", 102 | "valueSize": 1 103 | } 104 | } 105 | ], 106 | "id": 4970, 107 | "nodeType": "InlineAssembly", 108 | "operations": "{\n size := extcodesize(addr)\n}", 109 | "src": "951:49:16" 110 | }, 111 | { 112 | "expression": { 113 | "argumentTypes": null, 114 | "commonType": { 115 | "typeIdentifier": "t_uint256", 116 | "typeString": "uint256" 117 | }, 118 | "id": 4973, 119 | "isConstant": false, 120 | "isLValue": false, 121 | "isPure": false, 122 | "lValueRequested": false, 123 | "leftExpression": { 124 | "argumentTypes": null, 125 | "id": 4971, 126 | "name": "size", 127 | "nodeType": "Identifier", 128 | "overloadedDeclarations": [], 129 | "referencedDeclaration": 4968, 130 | "src": "1001:4:16", 131 | "typeDescriptions": { 132 | "typeIdentifier": "t_uint256", 133 | "typeString": "uint256" 134 | } 135 | }, 136 | "nodeType": "BinaryOperation", 137 | "operator": ">", 138 | "rightExpression": { 139 | "argumentTypes": null, 140 | "hexValue": "30", 141 | "id": 4972, 142 | "isConstant": false, 143 | "isLValue": false, 144 | "isPure": true, 145 | "kind": "number", 146 | "lValueRequested": false, 147 | "nodeType": "Literal", 148 | "src": "1008:1:16", 149 | "subdenomination": null, 150 | "typeDescriptions": { 151 | "typeIdentifier": "t_rational_0_by_1", 152 | "typeString": "int_const 0" 153 | }, 154 | "value": "0" 155 | }, 156 | "src": "1001:8:16", 157 | "typeDescriptions": { 158 | "typeIdentifier": "t_bool", 159 | "typeString": "bool" 160 | } 161 | }, 162 | "functionReturnParameters": 4966, 163 | "id": 4974, 164 | "nodeType": "Return", 165 | "src": "994:15:16" 166 | } 167 | ] 168 | }, 169 | "documentation": "Returns whether the target address is a contract\n@dev This function will return false if invoked during the constructor of a contract,\n as the code is not actually created until after the constructor finishes.\n@param addr address to check\n@return whether the target address is a contract", 170 | "id": 4976, 171 | "implemented": true, 172 | "isConstructor": false, 173 | "isDeclaredConst": true, 174 | "modifiers": [], 175 | "name": "isContract", 176 | "nodeType": "FunctionDefinition", 177 | "parameters": { 178 | "id": 4963, 179 | "nodeType": "ParameterList", 180 | "parameters": [ 181 | { 182 | "constant": false, 183 | "id": 4962, 184 | "name": "addr", 185 | "nodeType": "VariableDeclaration", 186 | "scope": 4976, 187 | "src": "458:12:16", 188 | "stateVariable": false, 189 | "storageLocation": "default", 190 | "typeDescriptions": { 191 | "typeIdentifier": "t_address", 192 | "typeString": "address" 193 | }, 194 | "typeName": { 195 | "id": 4961, 196 | "name": "address", 197 | "nodeType": "ElementaryTypeName", 198 | "src": "458:7:16", 199 | "typeDescriptions": { 200 | "typeIdentifier": "t_address", 201 | "typeString": "address" 202 | } 203 | }, 204 | "value": null, 205 | "visibility": "internal" 206 | } 207 | ], 208 | "src": "457:14:16" 209 | }, 210 | "payable": false, 211 | "returnParameters": { 212 | "id": 4966, 213 | "nodeType": "ParameterList", 214 | "parameters": [ 215 | { 216 | "constant": false, 217 | "id": 4965, 218 | "name": "", 219 | "nodeType": "VariableDeclaration", 220 | "scope": 4976, 221 | "src": "495:4:16", 222 | "stateVariable": false, 223 | "storageLocation": "default", 224 | "typeDescriptions": { 225 | "typeIdentifier": "t_bool", 226 | "typeString": "bool" 227 | }, 228 | "typeName": { 229 | "id": 4964, 230 | "name": "bool", 231 | "nodeType": "ElementaryTypeName", 232 | "src": "495:4:16", 233 | "typeDescriptions": { 234 | "typeIdentifier": "t_bool", 235 | "typeString": "bool" 236 | } 237 | }, 238 | "value": null, 239 | "visibility": "internal" 240 | } 241 | ], 242 | "src": "494:6:16" 243 | }, 244 | "scope": 4977, 245 | "src": "438:576:16", 246 | "stateMutability": "view", 247 | "superFunction": null, 248 | "visibility": "internal" 249 | } 250 | ], 251 | "scope": 4978, 252 | "src": "87:930:16" 253 | } 254 | ], 255 | "src": "0:1018:16" 256 | }, 257 | "legacyAST": { 258 | "absolutePath": "openzeppelin-solidity/contracts/AddressUtils.sol", 259 | "exportedSymbols": { 260 | "AddressUtils": [ 261 | 4977 262 | ] 263 | }, 264 | "id": 4978, 265 | "nodeType": "SourceUnit", 266 | "nodes": [ 267 | { 268 | "id": 4960, 269 | "literals": [ 270 | "solidity", 271 | "^", 272 | "0.4", 273 | ".23" 274 | ], 275 | "nodeType": "PragmaDirective", 276 | "src": "0:24:16" 277 | }, 278 | { 279 | "baseContracts": [], 280 | "contractDependencies": [], 281 | "contractKind": "library", 282 | "documentation": "Utility library of inline functions on addresses", 283 | "fullyImplemented": true, 284 | "id": 4977, 285 | "linearizedBaseContracts": [ 286 | 4977 287 | ], 288 | "name": "AddressUtils", 289 | "nodeType": "ContractDefinition", 290 | "nodes": [ 291 | { 292 | "body": { 293 | "id": 4975, 294 | "nodeType": "Block", 295 | "src": "501:513:16", 296 | "statements": [ 297 | { 298 | "assignments": [], 299 | "declarations": [ 300 | { 301 | "constant": false, 302 | "id": 4968, 303 | "name": "size", 304 | "nodeType": "VariableDeclaration", 305 | "scope": 4976, 306 | "src": "507:12:16", 307 | "stateVariable": false, 308 | "storageLocation": "default", 309 | "typeDescriptions": { 310 | "typeIdentifier": "t_uint256", 311 | "typeString": "uint256" 312 | }, 313 | "typeName": { 314 | "id": 4967, 315 | "name": "uint256", 316 | "nodeType": "ElementaryTypeName", 317 | "src": "507:7:16", 318 | "typeDescriptions": { 319 | "typeIdentifier": "t_uint256", 320 | "typeString": "uint256" 321 | } 322 | }, 323 | "value": null, 324 | "visibility": "internal" 325 | } 326 | ], 327 | "id": 4969, 328 | "initialValue": null, 329 | "nodeType": "VariableDeclarationStatement", 330 | "src": "507:12:16" 331 | }, 332 | { 333 | "externalReferences": [ 334 | { 335 | "size": { 336 | "declaration": 4968, 337 | "isOffset": false, 338 | "isSlot": false, 339 | "src": "962:4:16", 340 | "valueSize": 1 341 | } 342 | }, 343 | { 344 | "addr": { 345 | "declaration": 4962, 346 | "isOffset": false, 347 | "isSlot": false, 348 | "src": "982:4:16", 349 | "valueSize": 1 350 | } 351 | } 352 | ], 353 | "id": 4970, 354 | "nodeType": "InlineAssembly", 355 | "operations": "{\n size := extcodesize(addr)\n}", 356 | "src": "951:49:16" 357 | }, 358 | { 359 | "expression": { 360 | "argumentTypes": null, 361 | "commonType": { 362 | "typeIdentifier": "t_uint256", 363 | "typeString": "uint256" 364 | }, 365 | "id": 4973, 366 | "isConstant": false, 367 | "isLValue": false, 368 | "isPure": false, 369 | "lValueRequested": false, 370 | "leftExpression": { 371 | "argumentTypes": null, 372 | "id": 4971, 373 | "name": "size", 374 | "nodeType": "Identifier", 375 | "overloadedDeclarations": [], 376 | "referencedDeclaration": 4968, 377 | "src": "1001:4:16", 378 | "typeDescriptions": { 379 | "typeIdentifier": "t_uint256", 380 | "typeString": "uint256" 381 | } 382 | }, 383 | "nodeType": "BinaryOperation", 384 | "operator": ">", 385 | "rightExpression": { 386 | "argumentTypes": null, 387 | "hexValue": "30", 388 | "id": 4972, 389 | "isConstant": false, 390 | "isLValue": false, 391 | "isPure": true, 392 | "kind": "number", 393 | "lValueRequested": false, 394 | "nodeType": "Literal", 395 | "src": "1008:1:16", 396 | "subdenomination": null, 397 | "typeDescriptions": { 398 | "typeIdentifier": "t_rational_0_by_1", 399 | "typeString": "int_const 0" 400 | }, 401 | "value": "0" 402 | }, 403 | "src": "1001:8:16", 404 | "typeDescriptions": { 405 | "typeIdentifier": "t_bool", 406 | "typeString": "bool" 407 | } 408 | }, 409 | "functionReturnParameters": 4966, 410 | "id": 4974, 411 | "nodeType": "Return", 412 | "src": "994:15:16" 413 | } 414 | ] 415 | }, 416 | "documentation": "Returns whether the target address is a contract\n@dev This function will return false if invoked during the constructor of a contract,\n as the code is not actually created until after the constructor finishes.\n@param addr address to check\n@return whether the target address is a contract", 417 | "id": 4976, 418 | "implemented": true, 419 | "isConstructor": false, 420 | "isDeclaredConst": true, 421 | "modifiers": [], 422 | "name": "isContract", 423 | "nodeType": "FunctionDefinition", 424 | "parameters": { 425 | "id": 4963, 426 | "nodeType": "ParameterList", 427 | "parameters": [ 428 | { 429 | "constant": false, 430 | "id": 4962, 431 | "name": "addr", 432 | "nodeType": "VariableDeclaration", 433 | "scope": 4976, 434 | "src": "458:12:16", 435 | "stateVariable": false, 436 | "storageLocation": "default", 437 | "typeDescriptions": { 438 | "typeIdentifier": "t_address", 439 | "typeString": "address" 440 | }, 441 | "typeName": { 442 | "id": 4961, 443 | "name": "address", 444 | "nodeType": "ElementaryTypeName", 445 | "src": "458:7:16", 446 | "typeDescriptions": { 447 | "typeIdentifier": "t_address", 448 | "typeString": "address" 449 | } 450 | }, 451 | "value": null, 452 | "visibility": "internal" 453 | } 454 | ], 455 | "src": "457:14:16" 456 | }, 457 | "payable": false, 458 | "returnParameters": { 459 | "id": 4966, 460 | "nodeType": "ParameterList", 461 | "parameters": [ 462 | { 463 | "constant": false, 464 | "id": 4965, 465 | "name": "", 466 | "nodeType": "VariableDeclaration", 467 | "scope": 4976, 468 | "src": "495:4:16", 469 | "stateVariable": false, 470 | "storageLocation": "default", 471 | "typeDescriptions": { 472 | "typeIdentifier": "t_bool", 473 | "typeString": "bool" 474 | }, 475 | "typeName": { 476 | "id": 4964, 477 | "name": "bool", 478 | "nodeType": "ElementaryTypeName", 479 | "src": "495:4:16", 480 | "typeDescriptions": { 481 | "typeIdentifier": "t_bool", 482 | "typeString": "bool" 483 | } 484 | }, 485 | "value": null, 486 | "visibility": "internal" 487 | } 488 | ], 489 | "src": "494:6:16" 490 | }, 491 | "scope": 4977, 492 | "src": "438:576:16", 493 | "stateMutability": "view", 494 | "superFunction": null, 495 | "visibility": "internal" 496 | } 497 | ], 498 | "scope": 4978, 499 | "src": "87:930:16" 500 | } 501 | ], 502 | "src": "0:1018:16" 503 | }, 504 | "compiler": { 505 | "name": "solc", 506 | "version": "0.4.24+commit.e67f0147.Emscripten.clang" 507 | }, 508 | "networks": {}, 509 | "schemaVersion": "2.0.1", 510 | "updatedAt": "2018-10-09T19:38:41.285Z" 511 | } -------------------------------------------------------------------------------- /artifacts/deployed/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/identity-com/smart-contracts/54b3b1565ed4265373207923a5603cfc8ca2a9bd/artifacts/deployed/.gitkeep -------------------------------------------------------------------------------- /audit-ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "low": true, 3 | "package-manager": "auto", 4 | "report": true, 5 | "advisories": [782, 786], 6 | "whitelist": [] 7 | } 8 | -------------------------------------------------------------------------------- /constants.js: -------------------------------------------------------------------------------- 1 | const CVC_DECIMALS = 8; 2 | const ONE_CVC = 10 ** CVC_DECIMALS; 3 | const TOTAL_SUPPLY = 1e9 * ONE_CVC; 4 | const EVENT_ESCROW_PLACED = 'EscrowPlaced'; 5 | const EVENT_ESCROW_MOVED = 'EscrowMoved'; 6 | const EVENT_ESCROW_RELEASED = 'EscrowReleased'; 7 | const EVENT_ESCROW_REFUNDED = 'EscrowCanceled'; 8 | const EVENT_CREDENTIAL_ITEM_PRICE_SET = 'CredentialItemPriceSet'; 9 | const EVENT_CREDENTIAL_ITEM_PRICE_DELETED = 'CredentialItemPriceDeleted'; 10 | 11 | module.exports = { 12 | CVC_DECIMALS, 13 | ONE_CVC, 14 | TOTAL_SUPPLY, 15 | EVENT_ESCROW_PLACED, 16 | EVENT_ESCROW_MOVED, 17 | EVENT_ESCROW_RELEASED, 18 | EVENT_ESCROW_REFUNDED, 19 | EVENT_CREDENTIAL_ITEM_PRICE_SET, 20 | EVENT_CREDENTIAL_ITEM_PRICE_DELETED, 21 | }; 22 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # Contracts 2 | 3 | This folder holds smart contract sorce code. 4 | 5 | - Escrow: escrow service functionality for Identity.com marketplace. It controls an escrow placement's lifecycle which involves transferring a pre-approved amount funds from the Identity Requester account to its own account and keeping them until the marketplace deal is complete. 6 | - Pricing: pricing stores actual prices for Credential Items available for sale. It allows registered Identity Validators to set or delete price for specific Credential Item. 7 | - Ontology: ontology holds the list of all recognized Credential Items available for sale. 8 | - Validator registry: is a registry for Identity Validators (IDV). It is part of the marketplace access control mechanism. 9 | - Upgradeability: proxy functionality and all related contracts. 10 | -------------------------------------------------------------------------------- /contracts/escrow/CvcEscrowInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "../pricing/CvcPricingInterface.sol"; 4 | 5 | 6 | /** 7 | * @title CvcEscrowInterface 8 | * @dev This contract defines the escrow service interface. 9 | */ 10 | contract CvcEscrowInterface { 11 | 12 | /// Describes all possible states of the escrow placement: 13 | /// Empty - the placement with specific ID is unknown to the contract. i.e. was never placed. 14 | /// Placed - the placement is active, pending to be released or canceled. 15 | /// Released - the placement has been released and Identity Validator received payment. 16 | /// Canceled - the placement has been canceled and Identity Requester got refund, no payment was made to Identity Validator. 17 | enum PlacementState {Empty, Placed, Released, Canceled} 18 | 19 | /** 20 | * @dev The EscrowPlaced event is emitted when the placement is made and the corresponding amount of tokens 21 | * transferred from Identity Requester account to the escrow account. 22 | * The event is emitted individually for each placement item with unique scope request ID. 23 | * 24 | * @param idr ID Requester address. 25 | * @param idv Identity Validator address. 26 | * @param scopeRequestId Scope request identifier. 27 | * @param amount CVC token amount in creds (CVC x 10e-8). 28 | * @param credentialItemIds Array of credential item IDs. 29 | * @param placementId Escrow Placement Identifier 30 | */ 31 | event EscrowPlaced( 32 | address indexed idr, 33 | address indexed idv, 34 | bytes32 indexed scopeRequestId, 35 | uint256 amount, 36 | bytes32[] credentialItemIds, 37 | bytes32 placementId 38 | ); 39 | 40 | /** 41 | * @dev The EscrowMoved event is emitted for each placement item with unique scope request ID kept in escrow upon 42 | * placement partial release. It contains the old and new placement ID to track placement item lifecycle. 43 | * The placement ID from the latest EscrowMoved event effectively points to the active placement containing 44 | * specific scope request ID. 45 | * 46 | * @param idr ID Requester address. 47 | * @param idv Identity Validator address. 48 | * @param scopeRequestId Scope request identifier. 49 | * @param amount CVC token amount in creds (CVC x 10e-8). 50 | * @param credentialItemIds Array of credential item IDs. 51 | * @param oldPlacementId Escrow Placement Identifier of the partially released placement. 52 | * @param placementId Escrow Placement Identifier of new placement. 53 | */ 54 | event EscrowMoved( 55 | address indexed idr, 56 | address indexed idv, 57 | bytes32 indexed scopeRequestId, 58 | uint256 amount, 59 | bytes32[] credentialItemIds, 60 | bytes32 oldPlacementId, 61 | bytes32 placementId 62 | ); 63 | 64 | /** 65 | * @dev The EscrowReleased event is emitted when the placement is released and the corresponding amount of tokens 66 | * transferred from escrow account to Identity Validator account. 67 | * The event is emitted individually for each placement item with unique scope request ID. 68 | * 69 | * @param idr ID Requester address. 70 | * @param idv Identity Validator address. 71 | * @param scopeRequestId Scope request identifier. 72 | * @param platformFee CVC token amount transferred to the marketplace maintainer account as service fee. 73 | * @param idvFee CVC token amount transferred to the Identity Validator account. 74 | * @param credentialItemIds Array of credential item IDs. 75 | * @param placementId Escrow Placement Identifier 76 | */ 77 | event EscrowReleased( 78 | address indexed idr, 79 | address indexed idv, 80 | bytes32 indexed scopeRequestId, 81 | uint256 platformFee, 82 | uint256 idvFee, 83 | bytes32[] credentialItemIds, 84 | bytes32 placementId 85 | ); 86 | 87 | 88 | /** 89 | * @dev The EscrowCanceled event is emitted when the placement is canceled and the corresponding amount of tokens 90 | * refunded from escrow account to Identity Requester account. 91 | * The event is emitted individually for each placement item with unique scope request ID. 92 | * 93 | * @param idr ID Requester address. 94 | * @param idv Identity Validator address. 95 | * @param scopeRequestId Scope request identifier. 96 | * @param amount CVC token amount in creds (CVC x 10e-8) refunded to Identity Requester. 97 | * @param credentialItemIds Array of credential item IDs. 98 | * @param placementId Escrow Placement Identifier 99 | */ 100 | event EscrowCanceled( 101 | address indexed idr, 102 | address indexed idv, 103 | bytes32 indexed scopeRequestId, 104 | uint256 amount, 105 | bytes32[] credentialItemIds, 106 | bytes32 placementId 107 | ); 108 | 109 | /** 110 | * @dev Handles escrow placement for a single scope request. 111 | * @param _idv Address of Identity Validator 112 | * @param _scopeRequestId Scope request identifier 113 | * @param _amount CVC token amount in creds (CVC x 10e-8) 114 | * @param _credentialItemIds Array of credential item IDs 115 | * @return bytes32 New Placement ID 116 | */ 117 | function place(address _idv, bytes32 _scopeRequestId, uint256 _amount, bytes32[] _credentialItemIds) 118 | external 119 | returns (bytes32); 120 | 121 | /** 122 | * @dev Handles escrow placement for multiple scope requests grouped by credential item IDs. 123 | * @param _idv Address of Identity Validator 124 | * @param _scopeRequestIds Array of scope request IDs 125 | * @param _amount CVC token amount in creds (CVC x 10e-8) 126 | * @param _credentialItemIds Array of credential item IDs 127 | * @return bytes32 New Placement ID 128 | */ 129 | function placeBatch(address _idv, bytes32[] _scopeRequestIds, uint256 _amount, bytes32[] _credentialItemIds) 130 | external 131 | returns (bytes32); 132 | 133 | /** 134 | * @dev Releases escrow placement for a single scope request and distributes funds. 135 | * @param _idr Address of Identity Requester 136 | * @param _idv Address of Identity Validator 137 | * @param _scopeRequestId Scope request identifier 138 | */ 139 | function release(address _idr, address _idv, bytes32 _scopeRequestId) external; 140 | 141 | /** 142 | * @dev Releases escrow placement for multiple scope requests and distributes funds. 143 | * @param _idr Address of Identity Requester 144 | * @param _idv Address of Identity Validator 145 | * @param _scopeRequestIdsToRelease Array of scope request IDs which will be released 146 | * @param _scopeRequestIdsToKeep Array of scope request IDs which will be kept in escrow 147 | * @return bytes32 Placement ID of remaining part of the batch. Empty when the placement was fully released 148 | */ 149 | function releaseBatch( 150 | address _idr, 151 | address _idv, 152 | bytes32[] _scopeRequestIdsToRelease, 153 | bytes32[] _scopeRequestIdsToKeep 154 | ) 155 | external 156 | returns (bytes32); 157 | 158 | /** 159 | * @dev Refunds escrowed tokens for a single scope request back to Identity Requester. 160 | * @param _idr Address of Identity Requester 161 | * @param _idv Address of Identity Validator 162 | * @param _scopeRequestId Scope request identifier 163 | */ 164 | function refund(address _idr, address _idv, bytes32 _scopeRequestId) external; 165 | 166 | 167 | /** 168 | * @dev Refunds escrowed tokens for multiple scope requests back to Identity Requester. 169 | * @param _idr Address of Identity Requester 170 | * @param _idv Address of Identity Validator 171 | * @param _scopeRequestIds Array of scope request IDs 172 | */ 173 | function refundBatch(address _idr, address _idv, bytes32[] _scopeRequestIds) external; 174 | 175 | /** 176 | * @dev Returns placement details. 177 | * @param _idr Address of Identity Requester 178 | * @param _idv Address of Identity Validator 179 | * @param _scopeRequestId Scope request identifier 180 | * @return uint256 CVC token amount in creds (CVC x 10e-8) 181 | * @return PlacementState One of the CvcEscrowInterface.PlacementState values. 182 | * @return bytes32[] Array of credential item IDs. 183 | * @return uint256 Block confirmations since escrow was placed. 184 | * @return bool True if placement can be refunded otherwise false 185 | */ 186 | function verify(address _idr, address _idv, bytes32 _scopeRequestId) 187 | external 188 | view 189 | returns ( 190 | uint256 placementAmount, 191 | PlacementState placementState, 192 | bytes32[] credentialItemIds, 193 | uint256 confirmations, 194 | bool refundable 195 | ); 196 | 197 | /** 198 | * @dev Returns placement details. 199 | * @param _idr Address of Identity Requester 200 | * @param _idv Address of Identity Validator 201 | * @param _scopeRequestIds Array of scope request IDs 202 | * @return uint256 CVC token amount in creds (CVC x 10e-8) 203 | * @return PlacementState One of the CvcEscrowInterface.PlacementState values. 204 | * @return bytes32[] Array of credential item IDs. 205 | * @return uint256 Block confirmations since escrow was placed. 206 | * @return bool True if placement can be refunded otherwise false 207 | */ 208 | function verifyBatch(address _idr, address _idv, bytes32[] _scopeRequestIds) 209 | external 210 | view 211 | returns ( 212 | uint256 placementAmount, 213 | PlacementState placementState, 214 | bytes32[] credentialItemIds, 215 | uint256 confirmations, 216 | bool refundable 217 | ); 218 | 219 | 220 | /** 221 | * @dev Returns placement details. 222 | * @param _placementId Escrow Placement identifier. 223 | * @return uint256 CVC token amount in creds (CVC x 10e-8) 224 | * @return PlacementState One of the CvcEscrowInterface.PlacementState values. 225 | * @return bytes32[] Array of credential item IDs. 226 | * @return uint256 Block confirmations since escrow was placed. 227 | * @return bool True if placement can be refunded otherwise false 228 | */ 229 | function verifyPlacement(bytes32 _placementId) 230 | external 231 | view 232 | returns ( 233 | uint256 placementAmount, 234 | PlacementState placementState, 235 | bytes32[] credentialItemIds, 236 | uint256 confirmations, 237 | bool refundable 238 | ); 239 | 240 | 241 | /** 242 | * @dev Calculates escrow placement identifier. 243 | * @param _idr Address of Identity Requester 244 | * @param _idv Address of Identity Validator 245 | * @param _scopeRequestIds An array of scope request identifiers 246 | * @return bytes32 Placement ID 247 | */ 248 | function calculatePlacementId(address _idr, address _idv, bytes32[] _scopeRequestIds) 249 | public 250 | pure 251 | returns (bytes32); 252 | 253 | } 254 | -------------------------------------------------------------------------------- /contracts/idv/CvcValidatorRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./CvcValidatorRegistryInterface.sol"; 4 | import "../upgradeability/Initializable.sol"; 5 | import "../upgradeability/EternalStorage.sol"; 6 | import "../upgradeability/Ownable.sol"; 7 | 8 | 9 | /** 10 | * @title CvcValidatorRegistry 11 | * @dev This contract is a registry for Identity Validators (IDV). It is part of the marketplace access control mechanism. 12 | * Only registered and authorized Identity Validators can perform certain actions on marketplace. 13 | */ 14 | contract CvcValidatorRegistry is EternalStorage, Initializable, Ownable, CvcValidatorRegistryInterface { 15 | 16 | /** 17 | Data structures and storage layout: 18 | struct Validator { 19 | string name; 20 | string description; 21 | } 22 | mapping(address => Validator) validators; 23 | **/ 24 | 25 | /** 26 | * @dev Constructor: invokes initialization function 27 | */ 28 | constructor() public { 29 | initialize(msg.sender); 30 | } 31 | 32 | /** 33 | * @dev Registers a new Validator or updates the existing one. 34 | * @param _idv Validator address. 35 | * @param _name Validator name. 36 | * @param _description Validator description. 37 | */ 38 | function set(address _idv, string _name, string _description) external onlyInitialized onlyOwner { 39 | require(_idv != address(0), "Cannot register IDV with zero address"); 40 | require(bytes(_name).length > 0, "Cannot register IDV with empty name"); 41 | 42 | setValidatorName(_idv, _name); 43 | setValidatorDescription(_idv, _description); 44 | } 45 | 46 | /** 47 | * @dev Returns Validator data. 48 | * @param _idv Validator address. 49 | * @return name Validator name. 50 | * @return description Validator description. 51 | */ 52 | function get(address _idv) external view onlyInitialized returns (string name, string description) { 53 | name = getValidatorName(_idv); 54 | description = getValidatorDescription(_idv); 55 | } 56 | 57 | /** 58 | * @dev Verifies whether Validator is registered. 59 | * @param _idv Validator address. 60 | * @return bool 61 | */ 62 | function exists(address _idv) external view onlyInitialized returns (bool) { 63 | return bytes(getValidatorName(_idv)).length > 0; 64 | } 65 | 66 | /** 67 | * @dev Contract initialization method. 68 | * @param _owner Owner address 69 | */ 70 | function initialize(address _owner) public initializes { 71 | setOwner(_owner); 72 | } 73 | 74 | /** 75 | * @dev Returns Validator name. 76 | * @param _idv Validator address. 77 | * @return string 78 | */ 79 | function getValidatorName(address _idv) private view returns (string) { 80 | // return validators[_idv].name; 81 | return stringStorage[keccak256(abi.encodePacked("validators.", _idv, ".name"))]; 82 | } 83 | 84 | /** 85 | * @dev Saves Validator name. 86 | * @param _idv Validator address. 87 | * @param _name Validator name. 88 | */ 89 | function setValidatorName(address _idv, string _name) private { 90 | // validators[_idv].name = _name; 91 | stringStorage[keccak256(abi.encodePacked("validators.", _idv, ".name"))] = _name; 92 | } 93 | 94 | /** 95 | * @dev Returns Validator description. 96 | * @param _idv Validator address. 97 | * @return string 98 | */ 99 | function getValidatorDescription(address _idv) private view returns (string) { 100 | // return validators[_idv].description; 101 | return stringStorage[keccak256(abi.encodePacked("validators.", _idv, ".description"))]; 102 | } 103 | 104 | /** 105 | * @dev Saves Validator description. 106 | * @param _idv Validator address. 107 | * @param _description Validator description. 108 | */ 109 | function setValidatorDescription(address _idv, string _description) private { 110 | // validators[_idv].description = _description; 111 | stringStorage[keccak256(abi.encodePacked("validators.", _idv, ".description"))] = _description; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /contracts/idv/CvcValidatorRegistryInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | /** 5 | * @title CvcValidatorRegistryInterface 6 | * @dev This contract defines Validator Registry interface. 7 | */ 8 | contract CvcValidatorRegistryInterface { 9 | 10 | /** 11 | * @dev Adds a new Validator record or updates the existing one. 12 | * @param _name Validator name. 13 | * @param _description Validator description. 14 | */ 15 | function set(address _idv, string _name, string _description) external; 16 | 17 | /** 18 | * @dev Returns Validator entry. 19 | * @param _idv Validator address. 20 | * @return name Validator name. 21 | * @return description Validator description. 22 | */ 23 | function get(address _idv) external view returns (string name, string description); 24 | 25 | /** 26 | * @dev Verifies whether Validator is registered. 27 | * @param _idv Validator address. 28 | * @return bool 29 | */ 30 | function exists(address _idv) external view returns (bool); 31 | } 32 | -------------------------------------------------------------------------------- /contracts/ontology/CvcOntology.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./CvcOntologyInterface.sol"; 5 | import "../upgradeability/Initializable.sol"; 6 | import "../upgradeability/Ownable.sol"; 7 | import "../upgradeability/EternalStorage.sol"; 8 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 9 | 10 | /** 11 | * @title CvcOntology 12 | * @dev This contract holds the list of all recognized Credential Items available for sale. 13 | */ 14 | contract CvcOntology is EternalStorage, Initializable, Ownable, CvcOntologyInterface { 15 | 16 | using SafeMath for uint256; 17 | 18 | /** 19 | Data structures and storage layout: 20 | struct CredentialItem { 21 | string type; // "claim" or "credential" 22 | string name; // e.g. "proofOfIdentity" 23 | string version; // e.g. "v1.2" 24 | string reference; // e.g. "https://example.com/credential-proofOfIdentity-v1_2.json" 25 | string referenceType; // e.g. "JSON-LD-Context" 26 | bytes32 referenceHash; // e.g. "0x2cd9bf92c5e20b1b410f5ace94d963a96e89156fbe65b70365e8596b37f1f165" 27 | bool deprecated; // e.g. false 28 | } 29 | uint256 recordsCount; 30 | bytes32[] recordsIds; 31 | mapping(bytes32 => CredentialItem) records; 32 | **/ 33 | 34 | /** 35 | * Constructor to initialize with some default values 36 | */ 37 | constructor() public { 38 | initialize(msg.sender); 39 | } 40 | 41 | /** 42 | * @dev Adds new Credential Item to the registry. 43 | * @param _recordType Credential Item type 44 | * @param _recordName Credential Item name 45 | * @param _recordVersion Credential Item version 46 | * @param _reference Credential Item reference URL 47 | * @param _referenceType Credential Item reference type 48 | * @param _referenceHash Credential Item reference hash 49 | */ 50 | function add( 51 | string _recordType, 52 | string _recordName, 53 | string _recordVersion, 54 | string _reference, 55 | string _referenceType, 56 | bytes32 _referenceHash 57 | ) external onlyInitialized onlyOwner { 58 | require(bytes(_recordType).length > 0, "Empty credential item type"); 59 | require(bytes(_recordName).length > 0, "Empty credential item name"); 60 | require(bytes(_recordVersion).length > 0, "Empty credential item version"); 61 | require(bytes(_reference).length > 0, "Empty credential item reference"); 62 | require(bytes(_referenceType).length > 0, "Empty credential item type"); 63 | require(_referenceHash != 0x0, "Empty credential item reference hash"); 64 | 65 | bytes32 id = calculateId(_recordType, _recordName, _recordVersion); 66 | 67 | require(getReferenceHash(id) == 0x0, "Credential item record already exists"); 68 | 69 | setType(id, _recordType); 70 | setName(id, _recordName); 71 | setVersion(id, _recordVersion); 72 | setReference(id, _reference); 73 | setReferenceType(id, _referenceType); 74 | setReferenceHash(id, _referenceHash); 75 | setRecordId(getCount(), id); 76 | incrementCount(); 77 | } 78 | 79 | /** 80 | * @dev Contract initialization method. 81 | * @param _owner Contract owner address 82 | */ 83 | function initialize(address _owner) public initializes { 84 | setOwner(_owner); 85 | } 86 | 87 | /** 88 | * @dev Deprecates single Credential Item of specific type, name and version. 89 | * @param _type Record type to deprecate 90 | * @param _name Record name to deprecate 91 | * @param _version Record version to deprecate 92 | */ 93 | function deprecate(string _type, string _name, string _version) public onlyInitialized onlyOwner { 94 | deprecateById(calculateId(_type, _name, _version)); 95 | } 96 | 97 | /** 98 | * @dev Deprecates single Credential Item by ontology record ID. 99 | * @param _id Ontology record ID 100 | */ 101 | function deprecateById(bytes32 _id) public onlyInitialized onlyOwner { 102 | require(getReferenceHash(_id) != 0x0, "Cannot deprecate unknown credential item"); 103 | require(getDeprecated(_id) == false, "Credential item is already deprecated"); 104 | setDeprecated(_id); 105 | } 106 | 107 | /** 108 | * @dev Returns single Credential Item data up by ontology record ID. 109 | * @param _id Ontology record ID to search by 110 | * @return id Ontology record ID 111 | * @return recordType Credential Item type 112 | * @return recordName Credential Item name 113 | * @return recordVersion Credential Item version 114 | * @return reference Credential Item reference URL 115 | * @return referenceType Credential Item reference type 116 | * @return referenceHash Credential Item reference hash 117 | * @return deprecated Credential Item type deprecation flag 118 | */ 119 | function getById( 120 | bytes32 _id 121 | ) public view onlyInitialized returns ( 122 | bytes32 id, 123 | string recordType, 124 | string recordName, 125 | string recordVersion, 126 | string reference, 127 | string referenceType, 128 | bytes32 referenceHash, 129 | bool deprecated 130 | ) { 131 | referenceHash = getReferenceHash(_id); 132 | if (referenceHash != 0x0) { 133 | recordType = getType(_id); 134 | recordName = getName(_id); 135 | recordVersion = getVersion(_id); 136 | reference = getReference(_id); 137 | referenceType = getReferenceType(_id); 138 | deprecated = getDeprecated(_id); 139 | id = _id; 140 | } 141 | } 142 | 143 | /** 144 | * @dev Returns single Credential Item of specific type, name and version. 145 | * @param _type Credential Item type 146 | * @param _name Credential Item name 147 | * @param _version Credential Item version 148 | * @return id Ontology record ID 149 | * @return recordType Credential Item type 150 | * @return recordName Credential Item name 151 | * @return recordVersion Credential Item version 152 | * @return reference Credential Item reference URL 153 | * @return referenceType Credential Item reference type 154 | * @return referenceHash Credential Item reference hash 155 | * @return deprecated Credential Item type deprecation flag 156 | */ 157 | function getByTypeNameVersion( 158 | string _type, 159 | string _name, 160 | string _version 161 | ) public view onlyInitialized returns ( 162 | bytes32 id, 163 | string recordType, 164 | string recordName, 165 | string recordVersion, 166 | string reference, 167 | string referenceType, 168 | bytes32 referenceHash, 169 | bool deprecated 170 | ) { 171 | return getById(calculateId(_type, _name, _version)); 172 | } 173 | 174 | /** 175 | * @dev Returns all records. Currently is supported only from internal calls. 176 | * @return CredentialItem[] 177 | */ 178 | function getAll() public view onlyInitialized returns (CredentialItem[]) { 179 | uint256 count = getCount(); 180 | bytes32 id; 181 | CredentialItem[] memory records = new CredentialItem[](count); 182 | for (uint256 i = 0; i < count; i++) { 183 | id = getRecordId(i); 184 | records[i] = CredentialItem( 185 | id, 186 | getType(id), 187 | getName(id), 188 | getVersion(id), 189 | getReference(id), 190 | getReferenceType(id), 191 | getReferenceHash(id) 192 | ); 193 | } 194 | 195 | return records; 196 | } 197 | 198 | /** 199 | * @dev Returns all ontology record IDs. 200 | * Could be used from web3.js to retrieve the list of all records. 201 | * @return bytes32[] 202 | */ 203 | function getAllIds() public view onlyInitialized returns(bytes32[]) { 204 | uint256 count = getCount(); 205 | bytes32[] memory ids = new bytes32[](count); 206 | for (uint256 i = 0; i < count; i++) { 207 | ids[i] = getRecordId(i); 208 | } 209 | 210 | return ids; 211 | } 212 | 213 | /** 214 | * @dev Returns the number of registered ontology records. 215 | * @return uint256 216 | */ 217 | function getCount() internal view returns (uint256) { 218 | // return recordsCount; 219 | return uintStorage[keccak256("records.count")]; 220 | } 221 | 222 | /** 223 | * @dev Increments total record count. 224 | */ 225 | function incrementCount() internal { 226 | // recordsCount = getCount().add(1); 227 | uintStorage[keccak256("records.count")] = getCount().add(1); 228 | } 229 | 230 | /** 231 | * @dev Returns the ontology record ID by numeric index. 232 | * @return bytes32 233 | */ 234 | function getRecordId(uint256 _index) internal view returns (bytes32) { 235 | // return recordsIds[_index]; 236 | return bytes32Storage[keccak256(abi.encodePacked("records.ids.", _index))]; 237 | } 238 | 239 | /** 240 | * @dev Saves ontology record ID against the index. 241 | * @param _index Numeric index. 242 | * @param _id Ontology record ID. 243 | */ 244 | function setRecordId(uint256 _index, bytes32 _id) internal { 245 | // recordsIds[_index] = _id; 246 | bytes32Storage[keccak256(abi.encodePacked("records.ids.", _index))] = _id; 247 | } 248 | 249 | /** 250 | * @dev Returns the Credential Item type. 251 | * @return string 252 | */ 253 | function getType(bytes32 _id) internal view returns (string) { 254 | // return records[_id].type; 255 | return stringStorage[keccak256(abi.encodePacked("records.", _id, ".type"))]; 256 | } 257 | 258 | /** 259 | * @dev Saves Credential Item type. 260 | * @param _id Ontology record ID. 261 | * @param _type Credential Item type. 262 | */ 263 | function setType(bytes32 _id, string _type) internal { 264 | // records[_id].type = _type; 265 | stringStorage[keccak256(abi.encodePacked("records.", _id, ".type"))] = _type; 266 | } 267 | 268 | /** 269 | * @dev Returns the Credential Item name. 270 | * @return string 271 | */ 272 | function getName(bytes32 _id) internal view returns (string) { 273 | // records[_id].name; 274 | return stringStorage[keccak256(abi.encodePacked("records.", _id, ".name"))]; 275 | } 276 | 277 | /** 278 | * @dev Saves Credential Item name. 279 | * @param _id Ontology record ID. 280 | * @param _name Credential Item name. 281 | */ 282 | function setName(bytes32 _id, string _name) internal { 283 | // records[_id].name = _name; 284 | stringStorage[keccak256(abi.encodePacked("records.", _id, ".name"))] = _name; 285 | } 286 | 287 | /** 288 | * @dev Returns the Credential Item version. 289 | * @return string 290 | */ 291 | function getVersion(bytes32 _id) internal view returns (string) { 292 | // return records[_id].version; 293 | return stringStorage[keccak256(abi.encodePacked("records.", _id, ".version"))]; 294 | } 295 | 296 | /** 297 | * @dev Saves Credential Item version. 298 | * @param _id Ontology record ID. 299 | * @param _version Credential Item version. 300 | */ 301 | function setVersion(bytes32 _id, string _version) internal { 302 | // records[_id].version = _version; 303 | stringStorage[keccak256(abi.encodePacked("records.", _id, ".version"))] = _version; 304 | } 305 | 306 | /** 307 | * @dev Returns the Credential Item reference URL. 308 | * @return string 309 | */ 310 | function getReference(bytes32 _id) internal view returns (string) { 311 | // return records[_id].reference; 312 | return stringStorage[keccak256(abi.encodePacked("records.", _id, ".reference"))]; 313 | } 314 | 315 | /** 316 | * @dev Saves Credential Item reference URL. 317 | * @param _id Ontology record ID. 318 | * @param _reference Reference value. 319 | */ 320 | function setReference(bytes32 _id, string _reference) internal { 321 | // records[_id].reference = _reference; 322 | stringStorage[keccak256(abi.encodePacked("records.", _id, ".reference"))] = _reference; 323 | } 324 | 325 | /** 326 | * @dev Returns the Credential Item reference type value. 327 | * @return string 328 | */ 329 | function getReferenceType(bytes32 _id) internal view returns (string) { 330 | // return records[_id].referenceType; 331 | return stringStorage[keccak256(abi.encodePacked("records.", _id, ".referenceType"))]; 332 | } 333 | 334 | /** 335 | * @dev Saves Credential Item reference type. 336 | * @param _id Ontology record ID. 337 | * @param _referenceType Reference type. 338 | */ 339 | function setReferenceType(bytes32 _id, string _referenceType) internal { 340 | // records[_id].referenceType = _referenceType; 341 | stringStorage[keccak256(abi.encodePacked("records.", _id, ".referenceType"))] = _referenceType; 342 | } 343 | 344 | /** 345 | * @dev Returns the Credential Item reference hash value. 346 | * @return bytes32 347 | */ 348 | function getReferenceHash(bytes32 _id) internal view returns (bytes32) { 349 | // return records[_id].referenceHash; 350 | return bytes32Storage[keccak256(abi.encodePacked("records.", _id, ".referenceHash"))]; 351 | } 352 | 353 | /** 354 | * @dev Saves Credential Item reference hash. 355 | * @param _id Ontology record ID. 356 | * @param _referenceHash Reference hash. 357 | */ 358 | function setReferenceHash(bytes32 _id, bytes32 _referenceHash) internal { 359 | // records[_id].referenceHash = _referenceHash; 360 | bytes32Storage[keccak256(abi.encodePacked("records.", _id, ".referenceHash"))] = _referenceHash; 361 | } 362 | 363 | /** 364 | * @dev Returns the Credential Item deprecation flag value. 365 | * @return bool 366 | */ 367 | function getDeprecated(bytes32 _id) internal view returns (bool) { 368 | // return records[_id].deprecated; 369 | return boolStorage[keccak256(abi.encodePacked("records.", _id, ".deprecated"))]; 370 | } 371 | 372 | /** 373 | * @dev Sets Credential Item deprecation flag value. 374 | * @param _id Ontology record ID. 375 | */ 376 | function setDeprecated(bytes32 _id) internal { 377 | // records[_id].deprecated = true; 378 | boolStorage[keccak256(abi.encodePacked("records.", _id, ".deprecated"))] = true; 379 | } 380 | 381 | /** 382 | * @dev Calculates ontology record ID. 383 | * @param _type Credential Item type. 384 | * @param _name Credential Item name. 385 | * @param _version Credential Item version. 386 | */ 387 | function calculateId(string _type, string _name, string _version) internal pure returns (bytes32) { 388 | return keccak256(abi.encodePacked(_type, ".", _name, ".", _version)); 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /contracts/ontology/CvcOntologyInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | pragma experimental ABIEncoderV2; 3 | 4 | /** 5 | * @title CvcOntologyInterface 6 | * @dev This contract defines marketplace ontology registry interface. 7 | */ 8 | contract CvcOntologyInterface { 9 | 10 | struct CredentialItem { 11 | bytes32 id; 12 | string recordType; 13 | string recordName; 14 | string recordVersion; 15 | string reference; 16 | string referenceType; 17 | bytes32 referenceHash; 18 | } 19 | 20 | /** 21 | * @dev Adds new Credential Item to the registry. 22 | * @param _recordType Credential Item type 23 | * @param _recordName Credential Item name 24 | * @param _recordVersion Credential Item version 25 | * @param _reference Credential Item reference URL 26 | * @param _referenceType Credential Item reference type 27 | * @param _referenceHash Credential Item reference hash 28 | */ 29 | function add( 30 | string _recordType, 31 | string _recordName, 32 | string _recordVersion, 33 | string _reference, 34 | string _referenceType, 35 | bytes32 _referenceHash 36 | ) external; 37 | 38 | /** 39 | * @dev Deprecates single Credential Item by external ID (type, name and version). 40 | * @param _type Record type to deprecate 41 | * @param _name Record name to deprecate 42 | * @param _version Record version to deprecate 43 | */ 44 | function deprecate(string _type, string _name, string _version) public; 45 | 46 | /** 47 | * @dev Deprecates single Credential Item by ID. 48 | * @param _id Record ID to deprecate 49 | */ 50 | function deprecateById(bytes32 _id) public; 51 | 52 | /** 53 | * @dev Returns single Credential Item data up by ontology record ID. 54 | * @param _id Ontology record ID to search by 55 | * @return id Ontology record ID 56 | * @return recordType Credential Item type 57 | * @return recordName Credential Item name 58 | * @return recordVersion Credential Item version 59 | * @return reference Credential Item reference URL 60 | * @return referenceType Credential Item reference type 61 | * @return referenceHash Credential Item reference hash 62 | * @return deprecated Credential Item type deprecation flag 63 | */ 64 | function getById(bytes32 _id) public view returns ( 65 | bytes32 id, 66 | string recordType, 67 | string recordName, 68 | string recordVersion, 69 | string reference, 70 | string referenceType, 71 | bytes32 referenceHash, 72 | bool deprecated 73 | ); 74 | 75 | /** 76 | * @dev Returns single Credential Item of specific type, name and version. 77 | * @param _type Credential Item type 78 | * @param _name Credential Item name 79 | * @param _version Credential Item version 80 | * @return id Ontology record ID 81 | * @return recordType Credential Item type 82 | * @return recordName Credential Item name 83 | * @return recordVersion Credential Item version 84 | * @return reference Credential Item reference URL 85 | * @return referenceType Credential Item reference type 86 | * @return referenceHash Credential Item reference hash 87 | * @return deprecated Credential Item type deprecation flag 88 | */ 89 | function getByTypeNameVersion( 90 | string _type, 91 | string _name, 92 | string _version 93 | ) public view returns ( 94 | bytes32 id, 95 | string recordType, 96 | string recordName, 97 | string recordVersion, 98 | string reference, 99 | string referenceType, 100 | bytes32 referenceHash, 101 | bool deprecated 102 | ); 103 | 104 | /** 105 | * @dev Returns all IDs of registered Credential Items. 106 | * @return bytes32[] 107 | */ 108 | function getAllIds() public view returns (bytes32[]); 109 | 110 | /** 111 | * @dev Returns all registered Credential Items. 112 | * @return bytes32[] 113 | */ 114 | function getAll() public view returns (CredentialItem[]); 115 | } 116 | -------------------------------------------------------------------------------- /contracts/pricing/CvcPricing.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "openzeppelin-solidity/contracts/AddressUtils.sol"; 5 | import "./CvcPricingInterface.sol"; 6 | import "../idv/CvcValidatorRegistryInterface.sol"; 7 | import "../ontology/CvcOntologyInterface.sol"; 8 | import "../upgradeability/Initializable.sol"; 9 | import "../upgradeability/EternalStorage.sol"; 10 | import "../upgradeability/Pausable.sol"; 11 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 12 | 13 | 14 | /** 15 | * @title CvcPricing 16 | * @dev This contract stores actual prices for Credential Items available for sale. 17 | * It allows registered Identity Validators to set or delete prices for specific Credential Items. 18 | * 19 | * The pricing contract depends on other marketplace contracts, such as: 20 | * CvcOntology - to verify that Credential Item is available on the market and can be offered for sale. 21 | * CvcValidatorRegistry - to ensure that only registered Identity Validators can use pricing services. 22 | * Transactions from unknown accounts will be rejected. 23 | */ 24 | contract CvcPricing is EternalStorage, Initializable, Pausable, CvcPricingInterface { 25 | 26 | using SafeMath for uint256; 27 | 28 | /** 29 | Data structures and storage layout: 30 | struct Price { 31 | uint256 value; 32 | bytes32 credentialItemId; 33 | address idv; 34 | } 35 | 36 | address cvcOntology; 37 | address idvRegistry; 38 | uint256 pricesCount; 39 | bytes32[] pricesIds; 40 | mapping(bytes32 => uint256) pricesIndices; 41 | mapping(bytes32 => Price) prices; 42 | **/ 43 | 44 | 45 | /// Total supply of CVC tokens. 46 | uint256 constant private CVC_TOTAL_SUPPLY = 1e17; 47 | 48 | /// The fallback price introduced to be returned when credential price is undefined. 49 | /// The number is greater than CVC total supply, so it makes it impossible to transact with (e.g. place to escrow). 50 | uint256 constant private FALLBACK_PRICE = CVC_TOTAL_SUPPLY + 1; // solium-disable-line zeppelin/no-arithmetic-operations 51 | 52 | /// As zero price and undefined price are virtually indistinguishable, 53 | /// a special value is introduced to represent zero price. 54 | /// It equals to max unsigned integer which makes it impossible to transact with, hence should never be returned. 55 | uint256 constant private ZERO_PRICE = ~uint256(0); 56 | 57 | /** 58 | * @dev Constructor 59 | * @param _ontology CvcOntology contract address. 60 | * @param _idvRegistry CvcValidatorRegistry contract address. 61 | */ 62 | constructor(address _ontology, address _idvRegistry) public { 63 | initialize(_ontology, _idvRegistry, msg.sender); 64 | } 65 | 66 | /** 67 | * @dev Throws if called by unregistered IDV. 68 | */ 69 | modifier onlyRegisteredValidator() { 70 | require(idvRegistry().exists(msg.sender), "Identity Validator is not registered"); 71 | _; 72 | } 73 | 74 | /** 75 | * @dev Sets the price for Credential Item of specific type, name and version. 76 | * The price is associated with IDV address (sender). 77 | * @param _credentialItemType Credential Item type. 78 | * @param _credentialItemName Credential Item name. 79 | * @param _credentialItemVersion Credential Item version. 80 | * @param _price Credential Item price. 81 | */ 82 | function setPrice( 83 | string _credentialItemType, 84 | string _credentialItemName, 85 | string _credentialItemVersion, 86 | uint256 _price 87 | ) 88 | external 89 | onlyRegisteredValidator 90 | whenNotPaused 91 | { 92 | // Check price value upper bound. 93 | require(_price <= CVC_TOTAL_SUPPLY, "Price value cannot be more than token total supply"); 94 | 95 | // Check Credential Item ID to verify existence. 96 | bytes32 credentialItemId; 97 | bool deprecated; 98 | (credentialItemId, , , , , , , deprecated) = ontology().getByTypeNameVersion( 99 | _credentialItemType, 100 | _credentialItemName, 101 | _credentialItemVersion 102 | ); 103 | // Prevent setting price for unknown credential items. 104 | require(credentialItemId != 0x0, "Cannot set price for unknown credential item"); 105 | require(deprecated == false, "Cannot set price for deprecated credential item"); 106 | 107 | // Calculate price ID. 108 | bytes32 id = calculateId(msg.sender, credentialItemId); 109 | 110 | // Register new record (when price record has no associated Credential Item ID). 111 | if (getPriceCredentialItemId(id) == 0x0) { 112 | registerNewRecord(id); 113 | } 114 | 115 | // Save the price. 116 | setPriceIdv(id, msg.sender); 117 | setPriceCredentialItemId(id, credentialItemId); 118 | setPriceValue(id, _price); 119 | 120 | emit CredentialItemPriceSet( 121 | id, 122 | _price, 123 | msg.sender, 124 | _credentialItemType, 125 | _credentialItemName, 126 | _credentialItemVersion, 127 | credentialItemId 128 | ); 129 | } 130 | 131 | /** 132 | * @dev Deletes the price for Credential Item of specific type, name and version. 133 | * @param _credentialItemType Credential Item type. 134 | * @param _credentialItemName Credential Item name. 135 | * @param _credentialItemVersion Credential Item version. 136 | */ 137 | function deletePrice( 138 | string _credentialItemType, 139 | string _credentialItemName, 140 | string _credentialItemVersion 141 | ) 142 | external 143 | whenNotPaused 144 | { 145 | // Lookup Credential Item. 146 | bytes32 credentialItemId; 147 | (credentialItemId, , , , , , ,) = ontology().getByTypeNameVersion( 148 | _credentialItemType, 149 | _credentialItemName, 150 | _credentialItemVersion 151 | ); 152 | 153 | // Calculate Price ID to address individual data items. 154 | bytes32 id = calculateId(msg.sender, credentialItemId); 155 | 156 | // Ensure the price existence. Check whether Credential Item is associated. 157 | credentialItemId = getPriceCredentialItemId(id); 158 | require(credentialItemId != 0x0, "Cannot delete unknown price record"); 159 | 160 | // Delete the price data. 161 | deletePriceIdv(id); 162 | deletePriceCredentialItemId(id); 163 | deletePriceValue(id); 164 | 165 | unregisterRecord(id); 166 | 167 | emit CredentialItemPriceDeleted( 168 | id, 169 | msg.sender, 170 | _credentialItemType, 171 | _credentialItemName, 172 | _credentialItemVersion, 173 | credentialItemId 174 | ); 175 | } 176 | 177 | /** 178 | * @dev Returns the price set by IDV for Credential Item of specific type, name and version. 179 | * @param _idv IDV address. 180 | * @param _credentialItemType Credential Item type. 181 | * @param _credentialItemName Credential Item name. 182 | * @param _credentialItemVersion Credential Item version. 183 | * @return bytes32 Price ID. 184 | * @return uint256 Price value. 185 | * @return address IDV address. 186 | * @return string Credential Item type. 187 | * @return string Credential Item name. 188 | * @return string Credential Item version. 189 | */ 190 | function getPrice( 191 | address _idv, 192 | string _credentialItemType, 193 | string _credentialItemName, 194 | string _credentialItemVersion 195 | ) 196 | external 197 | view 198 | onlyInitialized 199 | returns ( 200 | bytes32 id, 201 | uint256 price, 202 | address idv, 203 | string credentialItemType, 204 | string credentialItemName, 205 | string credentialItemVersion, 206 | bool deprecated 207 | ) 208 | { 209 | // Lookup Credential Item. 210 | bytes32 credentialItemId; 211 | (credentialItemId, credentialItemType, credentialItemName, credentialItemVersion, , , , deprecated) = ontology().getByTypeNameVersion( 212 | _credentialItemType, 213 | _credentialItemName, 214 | _credentialItemVersion 215 | ); 216 | idv = _idv; 217 | id = calculateId(idv, credentialItemId); 218 | price = getPriceValue(id); 219 | if (price == FALLBACK_PRICE) { 220 | return (0x0, price, 0x0, "", "", "", false); 221 | } 222 | } 223 | 224 | /** 225 | * @dev Returns the price by Credential Item ID. 226 | * @param _idv IDV address. 227 | * @param _credentialItemId Credential Item ID. 228 | * @return bytes32 Price ID. 229 | * @return uint256 Price value. 230 | * @return address IDV address. 231 | * @return string Credential Item type. 232 | * @return string Credential Item name. 233 | * @return string Credential Item version. 234 | */ 235 | function getPriceByCredentialItemId(address _idv, bytes32 _credentialItemId) external view returns ( 236 | bytes32 id, 237 | uint256 price, 238 | address idv, 239 | string credentialItemType, 240 | string credentialItemName, 241 | string credentialItemVersion, 242 | bool deprecated 243 | ) { 244 | return getPriceById(calculateId(_idv, _credentialItemId)); 245 | } 246 | 247 | /** 248 | * @dev Returns all Credential Item prices. 249 | * @return CredentialItemPrice[] 250 | */ 251 | function getAllPrices() external view onlyInitialized returns (CredentialItemPrice[]) { 252 | uint256 count = getCount(); 253 | CredentialItemPrice[] memory prices = new CredentialItemPrice[](count); 254 | for (uint256 i = 0; i < count; i++) { 255 | bytes32 id = getRecordId(i); 256 | bytes32 credentialItemId = getPriceCredentialItemId(id); 257 | string memory credentialItemType; 258 | string memory credentialItemName; 259 | string memory credentialItemVersion; 260 | bool deprecated; 261 | 262 | (, credentialItemType, credentialItemName, credentialItemVersion, , , , deprecated) = ontology().getById(credentialItemId); 263 | 264 | prices[i] = CredentialItemPrice( 265 | id, 266 | getPriceValue(id), 267 | getPriceIdv(id), 268 | credentialItemType, 269 | credentialItemName, 270 | credentialItemVersion, 271 | deprecated 272 | ); 273 | } 274 | 275 | return prices; 276 | } 277 | 278 | /** 279 | * @dev Returns all IDs of registered Credential Item prices. 280 | * @return bytes32[] 281 | */ 282 | function getAllIds() external view onlyInitialized returns(bytes32[]) { 283 | uint256 count = getCount(); 284 | bytes32[] memory ids = new bytes32[](count); 285 | for (uint256 i = 0; i < count; i++) { 286 | ids[i] = getRecordId(i); 287 | } 288 | 289 | return ids; 290 | } 291 | 292 | /** 293 | * @dev Contract initialization method. 294 | * @param _ontology CvcOntology contract address. 295 | * @param _idvRegistry CvcValidatorRegistry contract address. 296 | * @param _owner Owner address 297 | */ 298 | function initialize(address _ontology, address _idvRegistry, address _owner) public initializes { 299 | require(AddressUtils.isContract(_ontology), "Initialization error: no contract code at ontology contract address"); 300 | require(AddressUtils.isContract(_idvRegistry), "Initialization error: no contract code at IDV registry contract address"); 301 | // cvcOntology = _ontology; 302 | addressStorage[keccak256("cvc.ontology")] = _ontology; 303 | // idvRegistry = _idvRegistry; 304 | addressStorage[keccak256("cvc.idv.registry")] = _idvRegistry; 305 | // Initialize current implementation owner address. 306 | setOwner(_owner); 307 | } 308 | 309 | /** 310 | * @dev Returns the price by ID. 311 | * @param _id Price ID 312 | * @return bytes32 Price ID. 313 | * @return uint256 Price value. 314 | * @return address IDV address. 315 | * @return string Credential Item type. 316 | * @return string Credential Item name. 317 | * @return string Credential Item version. 318 | */ 319 | function getPriceById(bytes32 _id) public view onlyInitialized returns ( 320 | bytes32 id, 321 | uint256 price, 322 | address idv, 323 | string credentialItemType, 324 | string credentialItemName, 325 | string credentialItemVersion, 326 | bool deprecated 327 | ) { 328 | // Always return price (could be a fallback price when not set). 329 | price = getPriceValue(_id); 330 | // Check whether Credential Item is associated. This is mandatory requirement for all existing prices. 331 | bytes32 credentialItemId = getPriceCredentialItemId(_id); 332 | if (credentialItemId != 0x0) { 333 | // Return ID and IDV address for existing entry only. 334 | id = _id; 335 | idv = getPriceIdv(_id); 336 | 337 | (, credentialItemType, credentialItemName, credentialItemVersion, , , , deprecated) = ontology().getById(credentialItemId); 338 | } 339 | } 340 | 341 | /** 342 | * @dev Returns instance of CvcOntologyInterface. 343 | * @return CvcOntologyInterface 344 | */ 345 | function ontology() public view returns (CvcOntologyInterface) { 346 | // return CvcOntologyInterface(cvcOntology); 347 | return CvcOntologyInterface(addressStorage[keccak256("cvc.ontology")]); 348 | } 349 | 350 | /** 351 | * @dev Returns instance of CvcValidatorRegistryInterface. 352 | * @return CvcValidatorRegistryInterface 353 | */ 354 | function idvRegistry() public view returns (CvcValidatorRegistryInterface) { 355 | // return CvcValidatorRegistryInterface(idvRegistry); 356 | return CvcValidatorRegistryInterface(addressStorage[keccak256("cvc.idv.registry")]); 357 | } 358 | 359 | /** 360 | * @dev Returns price record count. 361 | * @return uint256 362 | */ 363 | function getCount() internal view returns (uint256) { 364 | // return pricesCount; 365 | return uintStorage[keccak256("prices.count")]; 366 | } 367 | 368 | /** 369 | * @dev Increments price record counter. 370 | */ 371 | function incrementCount() internal { 372 | // pricesCount = getCount().add(1); 373 | uintStorage[keccak256("prices.count")] = getCount().add(1); 374 | } 375 | 376 | /** 377 | * @dev Decrements price record counter. 378 | */ 379 | function decrementCount() internal { 380 | // pricesCount = getCount().sub(1); 381 | uintStorage[keccak256("prices.count")] = getCount().sub(1); 382 | } 383 | 384 | /** 385 | * @dev Returns price ID by index. 386 | * @param _index Price record index. 387 | * @return bytes32 388 | */ 389 | function getRecordId(uint256 _index) internal view returns (bytes32) { 390 | // return pricesIds[_index]; 391 | return bytes32Storage[keccak256(abi.encodePacked("prices.ids.", _index))]; 392 | } 393 | 394 | /** 395 | * @dev Index new price record. 396 | * @param _id The price ID. 397 | */ 398 | function registerNewRecord(bytes32 _id) internal { 399 | bytes32 indexSlot = keccak256(abi.encodePacked("prices.indices.", _id)); 400 | // Prevent from registering same ID twice. 401 | // require(pricesIndices[_id] == 0); 402 | require(uintStorage[indexSlot] == 0, "Integrity error: price with the same ID is already registered"); 403 | 404 | uint256 index = getCount(); 405 | // Store record ID against index. 406 | // pricesIds[index] = _id; 407 | bytes32Storage[keccak256(abi.encodePacked("prices.ids.", index))] = _id; 408 | // Maintain reversed index to ID mapping to ensure O(1) deletion. 409 | // Store n+1 value and reserve zero value for not indexed records. 410 | uintStorage[indexSlot] = index.add(1); 411 | incrementCount(); 412 | } 413 | 414 | /** 415 | * @dev Deletes price record from index. 416 | * @param _id The price ID. 417 | */ 418 | function unregisterRecord(bytes32 _id) internal { 419 | // Since the order of price records is not guaranteed, we can make deletion more efficient 420 | // by replacing record we want to delete with the last record, hence avoid reindex. 421 | 422 | // Calculate deletion record ID slot. 423 | bytes32 deletionIndexSlot = keccak256(abi.encodePacked("prices.indices.", _id)); 424 | // uint256 deletionIndex = pricesIndices[_id].sub(1); 425 | uint256 deletionIndex = uintStorage[deletionIndexSlot].sub(1); 426 | bytes32 deletionIdSlot = keccak256(abi.encodePacked("prices.ids.", deletionIndex)); 427 | 428 | // Calculate last record ID slot. 429 | uint256 lastIndex = getCount().sub(1); 430 | bytes32 lastIdSlot = keccak256(abi.encodePacked("prices.ids.", lastIndex)); 431 | 432 | // Calculate last record index slot. 433 | bytes32 lastIndexSlot = keccak256(abi.encodePacked("prices.indices.", bytes32Storage[lastIdSlot])); 434 | 435 | // Copy last record ID into the empty slot. 436 | // pricesIds[deletionIdSlot] = pricesIds[lastIdSlot]; 437 | bytes32Storage[deletionIdSlot] = bytes32Storage[lastIdSlot]; 438 | // Make moved ID index point to the the correct record. 439 | // pricesIndices[lastIndexSlot] = pricesIndices[deletionIndexSlot]; 440 | uintStorage[lastIndexSlot] = uintStorage[deletionIndexSlot]; 441 | // Delete last record ID. 442 | // delete pricesIds[lastIndex]; 443 | delete bytes32Storage[lastIdSlot]; 444 | // Delete reversed index. 445 | // delete pricesIndices[_id]; 446 | delete uintStorage[deletionIndexSlot]; 447 | decrementCount(); 448 | } 449 | /** 450 | * @dev Returns price value. 451 | * @param _id The price ID. 452 | * @return uint256 453 | */ 454 | function getPriceValue(bytes32 _id) internal view returns (uint256) { 455 | // uint256 value = prices[_id].value; 456 | uint256 value = uintStorage[keccak256(abi.encodePacked("prices.", _id, ".value"))]; 457 | // Return fallback price if price is not set for existing Credential Item. 458 | // Since we use special (non-zero) value for zero price, actual '0' means the price was never set. 459 | if (value == 0) { 460 | return FALLBACK_PRICE; 461 | } 462 | // Convert from special zero representation value. 463 | if (value == ZERO_PRICE) { 464 | return 0; 465 | } 466 | 467 | return value; 468 | } 469 | 470 | /** 471 | * @dev Saves price value. 472 | * @param _id The price ID. 473 | * @param _value The price value. 474 | */ 475 | function setPriceValue(bytes32 _id, uint256 _value) internal { 476 | // Save the price (convert to special zero representation value if necessary). 477 | // prices[_id].value = (_value == 0) ? ZERO_PRICE : _value; 478 | uintStorage[keccak256(abi.encodePacked("prices.", _id, ".value"))] = (_value == 0) ? ZERO_PRICE : _value; 479 | } 480 | 481 | /** 482 | * @dev Deletes price value. 483 | * @param _id The price ID. 484 | */ 485 | function deletePriceValue(bytes32 _id) internal { 486 | // delete prices[_id].value; 487 | delete uintStorage[keccak256(abi.encodePacked("prices.", _id, ".value"))]; 488 | } 489 | 490 | /** 491 | * @dev Returns Credential Item ID the price is set for. 492 | * @param _id The price ID. 493 | * @return bytes32 494 | */ 495 | function getPriceCredentialItemId(bytes32 _id) internal view returns (bytes32) { 496 | // return prices[_id].credentialItemId; 497 | return bytes32Storage[keccak256(abi.encodePacked("prices.", _id, ".credentialItemId"))]; 498 | } 499 | 500 | /** 501 | * @dev Saves price Credential Item ID 502 | * @param _id The price ID. 503 | * @param _credentialItemId Associated Credential Item ID. 504 | */ 505 | function setPriceCredentialItemId(bytes32 _id, bytes32 _credentialItemId) internal { 506 | // prices[_id].credentialItemId = _credentialItemId; 507 | bytes32Storage[keccak256(abi.encodePacked("prices.", _id, ".credentialItemId"))] = _credentialItemId; 508 | } 509 | 510 | /** 511 | * @dev Deletes price Credential Item ID. 512 | * @param _id The price ID. 513 | */ 514 | function deletePriceCredentialItemId(bytes32 _id) internal { 515 | // delete prices[_id].credentialItemId; 516 | delete bytes32Storage[keccak256(abi.encodePacked("prices.", _id, ".credentialItemId"))]; 517 | } 518 | 519 | /** 520 | * @dev Returns price IDV address. 521 | * @param _id The price ID. 522 | * @return address 523 | */ 524 | function getPriceIdv(bytes32 _id) internal view returns (address) { 525 | // return prices[_id].idv; 526 | return addressStorage[keccak256(abi.encodePacked("prices.", _id, ".idv"))]; 527 | } 528 | 529 | /** 530 | * @dev Saves price IDV address. 531 | * @param _id The price ID. 532 | * @param _idv IDV address. 533 | */ 534 | function setPriceIdv(bytes32 _id, address _idv) internal { 535 | // prices[_id].idv = _idv; 536 | addressStorage[keccak256(abi.encodePacked("prices.", _id, ".idv"))] = _idv; 537 | } 538 | 539 | /** 540 | * @dev Deletes price IDV address. 541 | * @param _id The price ID. 542 | */ 543 | function deletePriceIdv(bytes32 _id) internal { 544 | // delete prices[_id].idv; 545 | delete addressStorage[keccak256(abi.encodePacked("prices.", _id, ".idv"))]; 546 | } 547 | 548 | /** 549 | * @dev Calculates price ID. 550 | * @param _idv IDV address. 551 | * @param _credentialItemId Credential Item ID. 552 | * @return bytes32 553 | */ 554 | function calculateId(address _idv, bytes32 _credentialItemId) internal pure returns (bytes32) { 555 | return keccak256(abi.encodePacked(_idv, ".", _credentialItemId)); 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /contracts/pricing/CvcPricingInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | pragma experimental ABIEncoderV2; 3 | 4 | 5 | /** 6 | * @title CvcPricingInterface 7 | * @dev This contract defines the pricing service interface. 8 | */ 9 | contract CvcPricingInterface { 10 | 11 | struct CredentialItemPrice { 12 | bytes32 id; 13 | uint256 price; 14 | address idv; 15 | string credentialItemType; 16 | string credentialItemName; 17 | string credentialItemVersion; 18 | bool deprecated; 19 | } 20 | 21 | /** 22 | * @dev The CredentialItemPriceSet event is emitted when Identity Validator sets new price for specific credential item. 23 | * 24 | * @param id Price record identifier. 25 | * @param price Credential Item price in CVC. 26 | * @param idv The address of Identity Validator who offers Credential Item for sale. 27 | * @param credentialItemType Credential Item Type. 28 | * @param credentialItemName Credential Item Name. 29 | * @param credentialItemVersion Credential Item Version. 30 | * @param credentialItemId Credential Item ID. 31 | */ 32 | event CredentialItemPriceSet( 33 | bytes32 indexed id, 34 | uint256 price, 35 | address indexed idv, 36 | string credentialItemType, 37 | string credentialItemName, 38 | string credentialItemVersion, 39 | bytes32 indexed credentialItemId 40 | ); 41 | 42 | /** 43 | * @dev The CredentialItemPriceDeleted event is emitted when Identity Validator deletes the price for specific credential item. 44 | * 45 | * @param id Price record identifier. 46 | * @param idv The address of Identity Validator who offers Credential Item for sale 47 | * @param credentialItemType Credential Item Type. 48 | * @param credentialItemName Credential Item Name. 49 | * @param credentialItemVersion Credential Item Version. 50 | * @param credentialItemId Credential Item ID. 51 | */ 52 | event CredentialItemPriceDeleted( 53 | bytes32 indexed id, 54 | address indexed idv, 55 | string credentialItemType, 56 | string credentialItemName, 57 | string credentialItemVersion, 58 | bytes32 indexed credentialItemId 59 | ); 60 | 61 | /** 62 | * @dev Sets the price for Credential Item of specific type, name and version. 63 | * The price is associated with IDV address (sender). 64 | * @param _credentialItemType Credential Item type. 65 | * @param _credentialItemName Credential Item name. 66 | * @param _credentialItemVersion Credential Item version. 67 | * @param _price Credential Item price. 68 | */ 69 | function setPrice( 70 | string _credentialItemType, 71 | string _credentialItemName, 72 | string _credentialItemVersion, 73 | uint256 _price 74 | ) external; 75 | 76 | /** 77 | * @dev Deletes the price for Credential Item of specific type, name and version. 78 | * @param _credentialItemType Credential Item type. 79 | * @param _credentialItemName Credential Item name. 80 | * @param _credentialItemVersion Credential Item version. 81 | */ 82 | function deletePrice( 83 | string _credentialItemType, 84 | string _credentialItemName, 85 | string _credentialItemVersion 86 | ) external; 87 | 88 | /** 89 | * @dev Returns the price set by IDV for Credential Item of specific type, name and version. 90 | * @param _idv IDV address. 91 | * @param _credentialItemType Credential Item type. 92 | * @param _credentialItemName Credential Item name. 93 | * @param _credentialItemVersion Credential Item version. 94 | * @return bytes32 Price ID. 95 | * @return uint256 Price value. 96 | * @return address IDV address. 97 | * @return string Credential Item type. 98 | * @return string Credential Item name. 99 | * @return string Credential Item version. 100 | */ 101 | function getPrice( 102 | address _idv, 103 | string _credentialItemType, 104 | string _credentialItemName, 105 | string _credentialItemVersion 106 | ) external view returns ( 107 | bytes32 id, 108 | uint256 price, 109 | address idv, 110 | string credentialItemType, 111 | string credentialItemName, 112 | string credentialItemVersion, 113 | bool deprecated 114 | ); 115 | 116 | /** 117 | * @dev Returns the price by Credential Item ID. 118 | * @param _idv IDV address. 119 | * @param _credentialItemId Credential Item ID. 120 | * @return bytes32 Price ID. 121 | * @return uint256 Price value. 122 | * @return address IDV address. 123 | * @return string Credential Item type. 124 | * @return string Credential Item name. 125 | * @return string Credential Item version. 126 | */ 127 | function getPriceByCredentialItemId( 128 | address _idv, 129 | bytes32 _credentialItemId 130 | ) external view returns ( 131 | bytes32 id, 132 | uint256 price, 133 | address idv, 134 | string credentialItemType, 135 | string credentialItemName, 136 | string credentialItemVersion, 137 | bool deprecated 138 | ); 139 | 140 | /** 141 | * @dev Returns all Credential Item prices. 142 | * @return CredentialItemPrice[] 143 | */ 144 | function getAllPrices() external view returns (CredentialItemPrice[]); 145 | 146 | /** 147 | * @dev Returns all IDs of registered Credential Item prices. 148 | * @return bytes32[] 149 | */ 150 | function getAllIds() external view returns (bytes32[]); 151 | 152 | /** 153 | * @dev Returns the price by ID. 154 | * @param _id Price ID 155 | * @return bytes32 Price ID. 156 | * @return uint256 Price value. 157 | * @return address IDV address. 158 | * @return string Credential Item type. 159 | * @return string Credential Item name. 160 | * @return string Credential Item version. 161 | */ 162 | function getPriceById( 163 | bytes32 _id 164 | ) public view returns ( 165 | bytes32 id, 166 | uint256 price, 167 | address idv, 168 | string credentialItemType, 169 | string credentialItemName, 170 | string credentialItemVersion, 171 | bool deprecated 172 | ); 173 | } 174 | -------------------------------------------------------------------------------- /contracts/upgradeability/CvcMigrator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./CvcProxy.sol"; 4 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 5 | import "openzeppelin-solidity/contracts/AddressUtils.sol"; 6 | 7 | /** 8 | * @title CvcMigrator 9 | * @dev This is a system contract which provides transactional upgrade functionality. 10 | * It allows the ability to add 'upgrade transactions' for multiple proxy contracts and execute all of them in single transaction. 11 | */ 12 | contract CvcMigrator is Ownable { 13 | 14 | /** 15 | * @dev The ProxyCreated event is emitted when new instance of CvcProxy contract is deployed. 16 | * @param proxyAddress New proxy contract instance address. 17 | */ 18 | event ProxyCreated(address indexed proxyAddress); 19 | 20 | struct Migration { 21 | address proxy; 22 | address implementation; 23 | bytes data; 24 | } 25 | 26 | /// List of registered upgrades. 27 | Migration[] public migrations; 28 | 29 | /** 30 | * @dev Store migration record for the next migration 31 | * @param _proxy Proxy address 32 | * @param _implementation Implementation address 33 | * @param _data Pass-through to proxy's updateToAndCall 34 | */ 35 | function addUpgrade(address _proxy, address _implementation, bytes _data) external onlyOwner { 36 | require(AddressUtils.isContract(_implementation), "Migrator error: no contract code at new implementation address"); 37 | require(CvcProxy(_proxy).implementation() != _implementation, "Migrator error: proxy contract already uses specified implementation"); 38 | migrations.push(Migration(_proxy, _implementation, _data)); 39 | } 40 | 41 | /** 42 | * @dev Applies stored upgrades to proxies. Flushes the list of migration records 43 | */ 44 | function migrate() external onlyOwner { 45 | for (uint256 i = 0; i < migrations.length; i++) { 46 | Migration storage migration = migrations[i]; 47 | if (migration.data.length > 0) { 48 | CvcProxy(migration.proxy).upgradeToAndCall(migration.implementation, migration.data); 49 | } else { 50 | CvcProxy(migration.proxy).upgradeTo(migration.implementation); 51 | } 52 | } 53 | delete migrations; 54 | } 55 | 56 | /** 57 | * @dev Flushes the migration list without applying them. Can be used in case wrong migration added to the list. 58 | */ 59 | function reset() external onlyOwner { 60 | delete migrations; 61 | } 62 | 63 | /** 64 | * @dev Transfers ownership from the migrator to a new address 65 | * @param _target Proxy address 66 | * @param _newOwner New proxy owner address 67 | */ 68 | function changeProxyAdmin(address _target, address _newOwner) external onlyOwner { 69 | CvcProxy(_target).changeAdmin(_newOwner); 70 | } 71 | 72 | /** 73 | * @dev Proxy factory 74 | * @return CvcProxy 75 | */ 76 | function createProxy() external onlyOwner returns (CvcProxy) { 77 | CvcProxy proxy = new CvcProxy(); 78 | // We emit event here to retrieve contract address later in the tx receipt 79 | emit ProxyCreated(address(proxy)); 80 | return proxy; 81 | } 82 | 83 | /** 84 | * @dev Returns migration record by index. Will become obsolete as soon as migrations() will be usable via web3.js 85 | * @param _index 0-based index 86 | * @return address Proxy address 87 | * @return address Implementation address 88 | * @return bytes Pass-through to proxy's updateToAndCall 89 | */ 90 | function getMigration(uint256 _index) external view returns (address, address, bytes) { 91 | return (migrations[_index].proxy, migrations[_index].implementation, migrations[_index].data); 92 | } 93 | 94 | /** 95 | * @dev Returns current stored migration count 96 | * @return uint256 Count 97 | */ 98 | function getMigrationCount() external view returns (uint256) { 99 | return migrations.length; 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /contracts/upgradeability/CvcProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./ImplementationStorage.sol"; 4 | import "openzeppelin-solidity/contracts/AddressUtils.sol"; 5 | 6 | 7 | /** 8 | * @title CvcProxy 9 | * @dev Transparent proxy with upgradeability functions and authorization control. 10 | */ 11 | contract CvcProxy is ImplementationStorage { 12 | 13 | /** 14 | * @dev Emitted when the implementation is upgraded. 15 | * @param implementation Address of the new implementation. 16 | */ 17 | event Upgraded(address implementation); 18 | 19 | /** 20 | * @dev Emitted when the administration has been transferred. 21 | * @param previousAdmin Address of the previous admin. 22 | * @param newAdmin Address of the new admin. 23 | */ 24 | event AdminChanged(address previousAdmin, address newAdmin); 25 | 26 | /** 27 | * @dev Storage slot with the admin of the contract. 28 | * This is the keccak-256 hash of "cvc.proxy.admin", and is validated in the constructor. 29 | */ 30 | bytes32 private constant ADMIN_SLOT = 0x2bbac3e52eee27be250d682577104e2abe776c40160cd3167b24633933100433; 31 | 32 | /** 33 | * @dev Modifier to check whether the `msg.sender` is the admin. 34 | * It executes the function if called by admin. Otherwise, it will delegate the call to the implementation. 35 | */ 36 | modifier ifAdmin() { 37 | if (msg.sender == currentAdmin()) { 38 | _; 39 | } else { 40 | delegate(implementation()); 41 | } 42 | } 43 | 44 | /** 45 | * Contract constructor. 46 | * It sets the `msg.sender` as the proxy admin. 47 | */ 48 | constructor() public { 49 | assert(ADMIN_SLOT == keccak256("cvc.proxy.admin")); 50 | setAdmin(msg.sender); 51 | } 52 | 53 | /** 54 | * @dev Fallback function. 55 | */ 56 | function() external payable { 57 | require(msg.sender != currentAdmin(), "Message sender is not contract admin"); 58 | delegate(implementation()); 59 | } 60 | 61 | /** 62 | * @dev Changes the admin of the proxy. 63 | * Only the current admin can call this function. 64 | * @param _newAdmin Address to transfer proxy administration to. 65 | */ 66 | function changeAdmin(address _newAdmin) external ifAdmin { 67 | require(_newAdmin != address(0), "Cannot change contract admin to zero address"); 68 | emit AdminChanged(currentAdmin(), _newAdmin); 69 | setAdmin(_newAdmin); 70 | } 71 | 72 | /** 73 | * @dev Allows the proxy owner to upgrade the current version of the proxy. 74 | * @param _implementation the address of the new implementation to be set. 75 | */ 76 | function upgradeTo(address _implementation) external ifAdmin { 77 | upgradeImplementation(_implementation); 78 | } 79 | 80 | /** 81 | * @dev Allows the proxy owner to upgrade and call the new implementation 82 | * to initialize whatever is needed through a low level call. 83 | * @param _implementation the address of the new implementation to be set. 84 | * @param _data the msg.data to bet sent in the low level call. This parameter may include the function 85 | * signature of the implementation to be called with the needed payload. 86 | */ 87 | function upgradeToAndCall(address _implementation, bytes _data) external payable ifAdmin { 88 | upgradeImplementation(_implementation); 89 | //solium-disable-next-line security/no-call-value 90 | require(address(this).call.value(msg.value)(_data), "Upgrade error: initialization method call failed"); 91 | } 92 | 93 | /** 94 | * @dev Returns the Address of the proxy admin. 95 | * @return address 96 | */ 97 | function admin() external view ifAdmin returns (address) { 98 | return currentAdmin(); 99 | } 100 | 101 | /** 102 | * @dev Upgrades the implementation address. 103 | * @param _newImplementation the address of the new implementation to be set 104 | */ 105 | function upgradeImplementation(address _newImplementation) private { 106 | address currentImplementation = implementation(); 107 | require(currentImplementation != _newImplementation, "Upgrade error: proxy contract already uses specified implementation"); 108 | setImplementation(_newImplementation); 109 | emit Upgraded(_newImplementation); 110 | } 111 | 112 | /** 113 | * @dev Delegates execution to an implementation contract. 114 | * This is a low level function that doesn't return to its internal call site. 115 | * It will return to the external caller whatever the implementation returns. 116 | * @param _implementation Address to delegate. 117 | */ 118 | function delegate(address _implementation) private { 119 | assembly { 120 | // Copy msg.data. 121 | calldatacopy(0, 0, calldatasize) 122 | 123 | // Call current implementation passing proxy calldata. 124 | let result := delegatecall(gas, _implementation, 0, calldatasize, 0, 0) 125 | 126 | // Copy the returned data. 127 | returndatacopy(0, 0, returndatasize) 128 | 129 | // Propagate result (delegatecall returns 0 on error). 130 | switch result 131 | case 0 {revert(0, returndatasize)} 132 | default {return (0, returndatasize)} 133 | } 134 | } 135 | 136 | /** 137 | * @return The admin slot. 138 | */ 139 | function currentAdmin() private view returns (address proxyAdmin) { 140 | bytes32 slot = ADMIN_SLOT; 141 | assembly { 142 | proxyAdmin := sload(slot) 143 | } 144 | } 145 | 146 | /** 147 | * @dev Sets the address of the proxy admin. 148 | * @param _newAdmin Address of the new proxy admin. 149 | */ 150 | function setAdmin(address _newAdmin) private { 151 | bytes32 slot = ADMIN_SLOT; 152 | assembly { 153 | sstore(slot, _newAdmin) 154 | } 155 | } 156 | 157 | /** 158 | * @dev Sets the implementation address of the proxy. 159 | * @param _newImplementation Address of the new implementation. 160 | */ 161 | function setImplementation(address _newImplementation) private { 162 | require( 163 | AddressUtils.isContract(_newImplementation), 164 | "Cannot set new implementation: no contract code at contract address" 165 | ); 166 | bytes32 slot = IMPLEMENTATION_SLOT; 167 | assembly { 168 | sstore(slot, _newImplementation) 169 | } 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /contracts/upgradeability/EternalStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | /** 5 | * @title EternalStorage 6 | * @dev This contract defines the generic storage structure 7 | * so that it could be re-used to implement any domain specific storage functionality 8 | */ 9 | contract EternalStorage { 10 | 11 | mapping(bytes32 => uint256) internal uintStorage; 12 | mapping(bytes32 => string) internal stringStorage; 13 | mapping(bytes32 => address) internal addressStorage; 14 | mapping(bytes32 => bytes) internal bytesStorage; 15 | mapping(bytes32 => bool) internal boolStorage; 16 | mapping(bytes32 => int256) internal intStorage; 17 | mapping(bytes32 => bytes32) internal bytes32Storage; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /contracts/upgradeability/ImplementationStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | /** 5 | * @title ImplementationStorage 6 | * @dev This contract stores proxy implementation address. 7 | */ 8 | contract ImplementationStorage { 9 | 10 | /** 11 | * @dev Storage slot with the address of the current implementation. 12 | * This is the keccak-256 hash of "cvc.proxy.implementation", and is validated in the constructor. 13 | */ 14 | bytes32 internal constant IMPLEMENTATION_SLOT = 0xa490aab0d89837371982f93f57ffd20c47991f88066ef92475bc8233036969bb; 15 | 16 | /** 17 | * @dev Constructor 18 | */ 19 | constructor() public { 20 | assert(IMPLEMENTATION_SLOT == keccak256("cvc.proxy.implementation")); 21 | } 22 | 23 | /** 24 | * @dev Returns the current implementation. 25 | * @return Address of the current implementation 26 | */ 27 | function implementation() public view returns (address impl) { 28 | bytes32 slot = IMPLEMENTATION_SLOT; 29 | assembly { 30 | impl := sload(slot) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/upgradeability/Initializable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./EternalStorage.sol"; 4 | import "./ImplementationStorage.sol"; 5 | 6 | 7 | /** 8 | * @title Initializable 9 | * @dev This contract provides basic initialization control 10 | */ 11 | contract Initializable is EternalStorage, ImplementationStorage { 12 | 13 | /** 14 | Data structures and storage layout: 15 | mapping(bytes32 => bool) initialized; 16 | **/ 17 | 18 | /** 19 | * @dev Throws if called before contract was initialized. 20 | */ 21 | modifier onlyInitialized() { 22 | // require(initialized[implementation()]); 23 | require(boolStorage[keccak256(abi.encodePacked(implementation(), "initialized"))], "Contract is not initialized"); 24 | _; 25 | } 26 | 27 | /** 28 | * @dev Controls the initialization state, allowing to call an initialization function only once. 29 | */ 30 | modifier initializes() { 31 | address impl = implementation(); 32 | // require(!initialized[implementation()]); 33 | require(!boolStorage[keccak256(abi.encodePacked(impl, "initialized"))], "Contract is already initialized"); 34 | _; 35 | // initialized[implementation()] = true; 36 | boolStorage[keccak256(abi.encodePacked(impl, "initialized"))] = true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contracts/upgradeability/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./EternalStorage.sol"; 4 | 5 | 6 | /** 7 | * @title Ownable 8 | * @dev This contract has an owner address providing basic authorization control 9 | */ 10 | contract Ownable is EternalStorage { 11 | 12 | /** 13 | Data structures and storage layout: 14 | address owner; 15 | **/ 16 | 17 | /** 18 | * @dev Event to show ownership has been transferred 19 | * @param previousOwner representing the address of the previous owner 20 | * @param newOwner representing the address of the new owner 21 | */ 22 | event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); 23 | 24 | /** 25 | * @dev Throws if called by any account other than the owner. 26 | */ 27 | modifier onlyOwner() { 28 | require(msg.sender == owner(), "Message sender must be contract admin"); 29 | _; 30 | } 31 | 32 | /** 33 | * @dev Tells the address of the owner 34 | * @return the address of the owner 35 | */ 36 | function owner() public view returns (address) { 37 | // return owner; 38 | return addressStorage[keccak256("owner")]; 39 | } 40 | 41 | /** 42 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 43 | * @param newOwner the address to transfer ownership to. 44 | */ 45 | function transferOwnership(address newOwner) public onlyOwner { 46 | require(newOwner != address(0), "Contract owner cannot be zero address"); 47 | setOwner(newOwner); 48 | } 49 | 50 | /** 51 | * @dev Sets a new owner address 52 | */ 53 | function setOwner(address newOwner) internal { 54 | emit OwnershipTransferred(owner(), newOwner); 55 | // owner = newOwner; 56 | addressStorage[keccak256("owner")] = newOwner; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contracts/upgradeability/Pausable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./Ownable.sol"; 4 | import "./ImplementationStorage.sol"; 5 | 6 | 7 | /** 8 | * @title Pausable 9 | * @dev Base contract which allows children to implement an emergency stop mechanism. 10 | */ 11 | contract Pausable is Ownable, ImplementationStorage { 12 | 13 | /** 14 | Data structures and storage layout: 15 | mapping(bytes32 => bool) paused; 16 | **/ 17 | 18 | event Pause(); 19 | event Unpause(); 20 | 21 | /** 22 | * @dev Modifier to make a function callable only when the contract is not paused. 23 | */ 24 | modifier whenNotPaused() { 25 | require(!paused(), "Contract is paused"); 26 | _; 27 | } 28 | 29 | /** 30 | * @dev Modifier to make a function callable only when the contract is paused. 31 | */ 32 | modifier whenPaused() { 33 | require(paused(), "Contract must be paused"); 34 | _; 35 | } 36 | 37 | /** 38 | * @dev called by the owner to pause, triggers stopped state 39 | */ 40 | function pause() public onlyOwner whenNotPaused { 41 | // paused[implementation()] = true; 42 | boolStorage[keccak256(abi.encodePacked(implementation(), "paused"))] = true; 43 | emit Pause(); 44 | } 45 | 46 | /** 47 | * @dev called by the owner to unpause, returns to normal state 48 | */ 49 | function unpause() public onlyOwner whenPaused { 50 | // paused[implementation()] = false; 51 | boolStorage[keccak256(abi.encodePacked(implementation(), "paused"))] = false; 52 | emit Unpause(); 53 | } 54 | 55 | /** 56 | * @dev Returns true when the contract is paused. 57 | * @return bool 58 | */ 59 | function paused() public view returns (bool) { 60 | // return paused[implementation()]; 61 | return boolStorage[keccak256(abi.encodePacked(implementation(), "paused"))]; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | 3 | services: 4 | ganache: 5 | image: trufflesuite/ganache-cli:v6.2.4 6 | ports: 7 | - 8545:8545 8 | entrypoint: 9 | - node 10 | - ./ganache-core.docker.cli.js 11 | - --mnemonic=drum muscle maximum umbrella night initial prevent auction pink old fault media 12 | - --host=0.0.0.0 13 | - --port=8545 14 | - --gasLimit=7992181 15 | - --gasPrice=1000000000 16 | - --defaultBalanceEther=1000 17 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const TruffleArtifactor = require('truffle-artifactor'); 2 | const TruffleConfig = require('../truffle'); 3 | const { unlockAccount } = require('./utils'); 4 | 5 | const Migrations = artifacts.require('./Migrations.sol'); 6 | const artifactor = new TruffleArtifactor('artifacts/deployed/'); 7 | 8 | module.exports = function(deployer, network) { 9 | const config = TruffleConfig.networks[network]; 10 | unlockAccount(deployer, config, 3660); 11 | return deployer.deploy(Migrations).then(() => artifactor.save(Migrations)); 12 | }; 13 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts_v1.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const TruffleArtifactor = require('truffle-artifactor'); 3 | 4 | const artifactor = new TruffleArtifactor('artifacts/deployed/'); 5 | 6 | const CvcMigrator = artifacts.require('CvcMigrator'); 7 | const Proxy = artifacts.require('CvcProxy'); 8 | 9 | const { CVC_DECIMALS, TOTAL_SUPPLY } = require('../constants'); 10 | const TruffleConfig = require('../truffle'); 11 | const compiledContractHelper = require('./utils/compiledContractHelper'); 12 | const { encodeABI, unlockAccount } = require('./utils'); 13 | 14 | // This is the version of compiled contracts we deploy. 15 | const CONTRACTS_IMPLEMENTATION_VERSION = 'v1'; 16 | 17 | // Gas limits: 18 | const GAS_DEPLOYMENT_TOKEN = 2100000; 19 | const GAS_DEPLOYMENT_MIGRATOR = 2300000; 20 | const GAS_DEPLOYMENT_ONTOLOGY = 4200000; 21 | const GAS_DEPLOYMENT_VALIDATOR_REGISTRY = 1800000; 22 | const GAS_DEPLOYMENT_PRICING = 5000000; 23 | const GAS_DEPLOYMENT_ESCROW = 6400000; 24 | const GAS_CALL_CREATE_PROXY = 600000; 25 | const GAS_CALL_ADD_UPGRADE_ONTOLOGY = 160000; 26 | const GAS_CALL_ADD_UPGRADE_VALIDATOR_REGISTRY = 150000; 27 | const GAS_CALL_ADD_UPGRADE_PRICING = 190000; 28 | const GAS_CALL_ADD_UPGRADE_ESCROW = 210000; 29 | const GAS_CALL_MIGRATE = 1000000; 30 | 31 | module.exports = transform(async (deployer, network) => { 32 | // Get truffle config for current network. 33 | const config = TruffleConfig.networks[network]; 34 | 35 | // Unlock sender account if necessary. 36 | unlockAccount(deployer, config); 37 | 38 | // Transaction default options: 39 | const { from, gasPrice } = config; 40 | const admin = from; 41 | const txOptions = { gasPrice, from }; 42 | 43 | // Load artifacts of the compiled contracts. 44 | const getCompiledContract = compiledContractHelper(deployer.provider, CONTRACTS_IMPLEMENTATION_VERSION); 45 | const [CvcToken, CvcPricing, CvcEscrow, CvcOntology, CvcValidatorRegistry] = [ 46 | 'CvcToken', 47 | 'CvcPricing', 48 | 'CvcEscrow', 49 | 'CvcOntology', 50 | 'CvcValidatorRegistry' 51 | ].map(getCompiledContract); 52 | 53 | // Deploy CVC token contract first (Note: Use real CVC token address for mainnet deployments) 54 | // eslint-disable-next-line max-len,prettier/prettier 55 | await deployer.deploy(CvcToken, admin, 'TestCVCToken', 'TCVC', TOTAL_SUPPLY, CVC_DECIMALS, {...txOptions, gas: GAS_DEPLOYMENT_TOKEN }); 56 | 57 | // Then deploy CvcMigrator contract. 58 | await deployer.deploy(CvcMigrator, { ...txOptions, gas: GAS_DEPLOYMENT_MIGRATOR }); 59 | 60 | // Deploy proxies for each contract. 61 | const migrator = CvcMigrator.at(CvcMigrator.address); 62 | // We can't make this with Promise.all (in single block) due to the nonce shuffling issues. 63 | const ontologyProxy = await createProxy(migrator, txOptions); 64 | const validatorRegistryProxy = await createProxy(migrator, txOptions); 65 | const pricingProxy = await createProxy(migrator, txOptions); 66 | const escrowProxy = await createProxy(migrator, txOptions); 67 | 68 | // Deploy Ontology contract. 69 | await deployer.deploy(CvcOntology, { ...txOptions, gas: GAS_DEPLOYMENT_ONTOLOGY }); 70 | console.log(`Adding Ontology proxy contract upgrade...`); 71 | await migrator.addUpgrade( 72 | ontologyProxy.address, 73 | CvcOntology.address, 74 | encodeABI(CvcOntology, 'initialize', [admin]), 75 | { ...txOptions, gas: GAS_CALL_ADD_UPGRADE_ONTOLOGY } 76 | ); 77 | 78 | // Deploy ValidatorRegistry contract. 79 | await deployer.deploy(CvcValidatorRegistry, { ...txOptions, gas: GAS_DEPLOYMENT_VALIDATOR_REGISTRY }); 80 | console.log(`Adding ValidatorRegistry proxy contract upgrade...`); 81 | await migrator.addUpgrade( 82 | validatorRegistryProxy.address, 83 | CvcValidatorRegistry.address, 84 | encodeABI(CvcValidatorRegistry, 'initialize', [admin]), 85 | { ...txOptions, gas: GAS_CALL_ADD_UPGRADE_VALIDATOR_REGISTRY } 86 | ); 87 | 88 | // Deploy Pricing contract. 89 | // eslint-disable-next-line max-len,prettier/prettier 90 | await deployer.deploy(CvcPricing, CvcOntology.address, CvcValidatorRegistry.address, { ...txOptions, gas: GAS_DEPLOYMENT_PRICING }); 91 | console.log(`Adding Pricing proxy contract upgrade...`); 92 | await migrator.addUpgrade( 93 | pricingProxy.address, 94 | CvcPricing.address, 95 | encodeABI(CvcPricing, 'initialize', [ontologyProxy.address, validatorRegistryProxy.address, admin]), 96 | { ...txOptions, gas: GAS_CALL_ADD_UPGRADE_PRICING } 97 | ); 98 | 99 | // Deploy marketplace escrow contract. 100 | // eslint-disable-next-line max-len,prettier/prettier 101 | await deployer.deploy(CvcEscrow, CvcToken.address, admin, CvcPricing.address, { ...txOptions, gas: GAS_DEPLOYMENT_ESCROW }); 102 | console.log(`Adding Escrow proxy contract upgrade...`); 103 | await migrator.addUpgrade( 104 | escrowProxy.address, 105 | CvcEscrow.address, 106 | encodeABI(CvcEscrow, 'initialize', [CvcToken.address, admin, pricingProxy.address, admin]), 107 | { ...txOptions, gas: GAS_CALL_ADD_UPGRADE_ESCROW } 108 | ); 109 | 110 | // Execute proxy upgrades - switch all Proxy contracts to provided implementations and initialize them. 111 | console.log(`Upgrading proxy contracts with new implementations...`); 112 | await migrator.migrate({ ...txOptions, gas: GAS_CALL_MIGRATE }); 113 | 114 | console.log(`Ontology proxy at ${ontologyProxy.address}`); 115 | console.log(`Validator registry proxy at ${validatorRegistryProxy.address}`); 116 | console.log(`Pricing proxy at ${pricingProxy.address}`); 117 | console.log(`Escrow proxy at ${escrowProxy.address}`); 118 | console.log(`Contracts have been deployed successfully from ${admin} address`); 119 | 120 | await artifactor.saveAll({ 121 | CvcToken, 122 | CvcOntology: _(CvcOntology) 123 | .pick(['contractName', 'abi', 'compiler', 'networks']) 124 | .set(['networks', deployer.network_id, 'address'], ontologyProxy.address) 125 | .value(), 126 | CvcEscrow: _(CvcEscrow) 127 | .pick(['contractName', 'abi', 'compiler', 'networks']) 128 | .set(['networks', deployer.network_id, 'address'], escrowProxy.address) 129 | .value(), 130 | CvcPricing: _(CvcPricing) 131 | .pick(['contractName', 'abi', 'compiler', 'networks']) 132 | .set(['networks', deployer.network_id, 'address'], pricingProxy.address) 133 | .value(), 134 | CvcValidatorRegistry: _(CvcValidatorRegistry) 135 | .pick(['contractName', 'abi', 'compiler', 'networks']) 136 | .set(['networks', deployer.network_id, 'address'], validatorRegistryProxy.address) 137 | .value(), 138 | CvcMigrator: _(CvcMigrator) 139 | .pick(['contractName', 'abi', 'compiler', 'networks']) 140 | .value() 141 | }); 142 | 143 | // We need to save CvcToken artifact to `build/contracts/` as it is not compiled from the sources anymore. 144 | // Artifacts has internal cache with the map of contracts that will be saved in `Saving artifacts...` step. 145 | // Thus, later on we could use `artifacts.require` to get CvcToken (i.e. in unit tests). 146 | artifacts.cache.CvcToken = CvcToken; 147 | }); 148 | 149 | function transform(callback) { 150 | return (deployer, network, accounts) => deployer.then(() => callback(deployer, network, accounts)); 151 | } 152 | 153 | async function createProxy(migrator, txOptions) { 154 | console.log(`Deploying new instance of Proxy contract...`); 155 | // eslint-disable-next-line max-len,prettier/prettier 156 | const { logs: [{ args: { proxyAddress } }] } = await migrator.createProxy({ ...txOptions, gas: GAS_CALL_CREATE_PROXY }); 157 | return Proxy.at(proxyAddress); 158 | } 159 | -------------------------------------------------------------------------------- /migrations/3_mint_cvc_tokens.js: -------------------------------------------------------------------------------- 1 | const TruffleConfig = require('../truffle'); 2 | const { ONE_CVC } = require('../constants'); 3 | const deployedContractHelper = require('./utils/deployedContractHelper'); 4 | const Web3 = require('web3'); 5 | 6 | module.exports = transform(async (deployer, network, accounts) => { 7 | const config = TruffleConfig.networks[network]; 8 | const admin = config.from; 9 | const { gasPrice } = config; 10 | const getDeployedContract = deployedContractHelper(deployer.provider); 11 | 12 | const token = await getDeployedContract('CvcToken'); 13 | const web3 = new Web3(deployer.provider); 14 | // Provision default IDR: 15 | const idr = '0x8935161928e65081bcaef7358e97dce1c560dc9b'; // SIP 16 | console.log('Crediting ETH...'); 17 | const ethAmount = 10; 18 | await web3.eth.sendTransaction({ 19 | from: admin, 20 | to: idr, 21 | value: web3.toWei(ethAmount, 'ether'), 22 | gas: 30000, 23 | gasPrice: 100000000000 24 | }); 25 | console.log(`${idr} has been credited with ${ethAmount} ETH`); 26 | 27 | // Credit tokens 28 | console.log('Crediting tokens...'); 29 | const cvcAmount = 1000; 30 | accounts.push(idr); 31 | accounts.push('0xf91a4ddfa76451d00b703311aae273f2f77cd52c'); 32 | accounts.push('0x3a8bc151852c3771b5933419e5c74481679789d0'); 33 | accounts.push('0xa27d4886302c55345a82f94436019e209c5c7bd6'); 34 | await Promise.all( 35 | accounts.map( 36 | address => token.transfer(address, cvcAmount * ONE_CVC, { from: admin, gasPrice }).then(() => { 37 | console.log(`${address} has been credited with ${cvcAmount} CVC`); 38 | }) 39 | ) 40 | ); 41 | }); 42 | 43 | function transform(callback) { 44 | return (deployer, network, accounts) => deployer.then(() => callback(deployer, network, accounts)); 45 | } 46 | -------------------------------------------------------------------------------- /migrations/4_default_ontology_records.js: -------------------------------------------------------------------------------- 1 | // `artifacts.require` helper introduced by Truffle 2 | const TruffleConfig = require('../truffle'); 3 | const deployedContractHelper = require('./utils/deployedContractHelper'); 4 | const { unlockAccount } = require('./utils'); 5 | 6 | module.exports = transform(async (deployer, network) => { 7 | const config = TruffleConfig.networks[network]; 8 | const admin = config.from; 9 | const gas = 1e6; 10 | const { gasPrice } = config; 11 | unlockAccount(deployer, config); 12 | 13 | const getDeployedContract = deployedContractHelper(deployer.provider); 14 | const ontology = await getDeployedContract('CvcOntology'); 15 | 16 | await Promise.all([ 17 | ontology.add( 18 | 'credential', 19 | 'proofOfIdentity', 20 | 'v1.0', 21 | 'https://www.identity.com/', 22 | 'JSON-LD-Context', 23 | '0x2cd9bf92c5e20b1b410f5ace94d963a96e89156fbe65b70365e8596b37f1f165', // keccak('qwerty') 24 | { from: admin, gas, gasPrice } 25 | ), 26 | ontology.add( 27 | 'credential', 28 | 'proofOfAge', 29 | 'v1.0', 30 | 'https://www.identity.com/', 31 | 'JSON-LD-Context', 32 | '0x2cd9bf92c5e20b1b410f5ace94d963a96e89156fbe65b70365e8596b37f1f165', // keccak('qwerty') 33 | { from: admin, gas, gasPrice } 34 | ), 35 | ontology.add( 36 | 'credential', 37 | 'proofOfResidence', 38 | 'v1.0', 39 | 'https://www.identity.com/', 40 | 'JSON-LD-Context', 41 | '0x2cd9bf92c5e20b1b410f5ace94d963a96e89156fbe65b70365e8596b37f1f165', // keccak('qwerty') 42 | { from: admin, gas, gasPrice } 43 | ) 44 | ]); 45 | 46 | console.log('Default ontology records added:'); 47 | const records = await ontology.getAllIds().then(ids => Promise.all(ids.map(id => ontology.getById(id)))); 48 | records.map(credentialItem => { 49 | const [id, type, name, version] = credentialItem; 50 | return console.log(`${type} ${name} ${version} with ID=${id}`); 51 | }); 52 | }); 53 | 54 | function transform(callback) { 55 | return (deployer, network, accounts) => deployer.then(() => callback(deployer, network, accounts)); 56 | } 57 | -------------------------------------------------------------------------------- /migrations/5_default_prices.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | 3 | const TruffleConfig = require('../truffle'); 4 | const deployedContractHelper = require('./utils/deployedContractHelper'); 5 | const { unlockAccount } = require('./utils'); 6 | 7 | module.exports = transform(async (deployer, network) => { 8 | const config = TruffleConfig.networks[network]; 9 | const admin = config.from; 10 | const { gasPrice } = config; 11 | const gas = 1e6; 12 | unlockAccount(deployer, config); 13 | 14 | const getDeployedContract = deployedContractHelper(deployer.provider); 15 | 16 | // Register default IDV first. 17 | const idvAddress = '0x1a88a35421a4a0d3e13fe4e8ebcf18e9a249dc5a'; 18 | const automationTestIDV = '0xb69271f06da20cf1b2545e1fb969cd827e281434'; 19 | const idvRegistry = await getDeployedContract('CvcValidatorRegistry'); 20 | await Promise.all([ 21 | idvRegistry.set(idvAddress, 'IDV', 'IDV company', { from: admin, gas, gasPrice }), 22 | idvRegistry.set(automationTestIDV, 'TestIDV', 'For test suite', { from: admin, gas, gasPrice }) 23 | ]); 24 | 25 | // Unlock IDV account. 26 | if (config.password) { 27 | const web3 = new Web3(deployer.provider); 28 | console.log(`>> Unlocking IDV account ${idvAddress}`); 29 | web3.personal.unlockAccount(idvAddress, config.password(), 36000); 30 | web3.personal.unlockAccount(automationTestIDV, config.password(), 36000); 31 | } 32 | // Set Proof Of Identity price. 33 | const pricing = await getDeployedContract('CvcPricing'); 34 | await Promise.all([ 35 | pricing.setPrice('credential', 'proofOfResidence', 'v1.0', 3000, { from: idvAddress, gas, gasPrice }), 36 | pricing.setPrice('credential', 'proofOfIdentity', 'v1.0', 2000, { from: idvAddress, gas, gasPrice }), 37 | pricing.setPrice('credential', 'proofOfAge', 'v1.0', 1000, { from: idvAddress, gas, gasPrice }), 38 | pricing.setPrice('credential', 'proofOfIdentity', 'v1.0', 3000, { from: automationTestIDV, gas, gasPrice }) 39 | ]); 40 | 41 | console.log(`Price added: credential, proofOfIdentity, v1.0, 2000, ${idvAddress}.`); 42 | console.log(`Price added: credential, proofOfAge, v1.0, 1000, ${idvAddress}.`); 43 | console.log(`Price added: credential, proofOfIdentity, v1.0, 3000, ${automationTestIDV}.`); 44 | }); 45 | 46 | function transform(callback) { 47 | return (deployer, network, accounts) => deployer.then(() => callback(deployer, network, accounts)); 48 | } 49 | -------------------------------------------------------------------------------- /migrations/README.md: -------------------------------------------------------------------------------- 1 | # Migrations 2 | 3 | ## The concept 4 | 5 | We use the [proxy-contract pattern](https://blog.zeppelinos.org/proxy-patterns/) to make our smart contracts (SC) addresses fixed and achieve [upgradeability](https://github.com/zeppelinos/labs/tree/master/upgradeability_using_eternal_storage). 6 | Since contract code cannot be changed once deployed, all interactions with the contracts use proxies that point to the latest implementation of the contract. 7 | 8 | In case a bug is found, a new implementation can be deployed without losing data or requiring clients to point to new contract addresses. 9 | 10 | ### Contracts or Applications? 11 | 12 | One of the advantages of smart contracts is the 'code as law' principle: users can trust the open and immutable nature of the contract code. 13 | Using proxies weakens this, and introduces a form of centralization, in that only trusted administrators can upgrade the contracts, 14 | thereby changing the underlying code. 15 | 16 | As custodians of the ecosystem, Identity.com reserves the right to make upgrades to the code during the beta phase, 17 | in order to protect and preserve the security of the ecosystem and its users, with the intention of switching off 18 | the proxies and switching to direct contract interaction once the system is considered ready. 19 | 20 | ### Proxy contract 21 | 22 | Solidity's assembly is used to use `delegatecall` to call other SC code. 23 | This allows to use proxy contract contexts (and storage), but execute implementation bytecode with provided arguments. 24 | When changing implementations, data is still present on the proxy's storage. 25 | 26 | ### Eternal storage 27 | 28 | There is a problem of SC state variable position collision (more info on how solidity determines at which address a var will be stored is [here](http://solidity.readthedocs.io/en/v0.4.24/miscellaneous.html#layout-of-state-variables-in-storage)). 29 | This preventds us from using ordinary SC vars in implementations as they may be shadowed by further implementations causing data loss. 30 | Instead, [Eternal storage](https://github.com/zeppelinos/labs/blob/master/upgradeability_using_eternal_storage/contracts/EternalStorage.sol) pattern is used. 31 | This allows us to store any value in the appropriate type map using any key. 32 | Thus, we can be sure that this piece of data will be accessible in future implementations by the same key. 33 | 34 | ### upgradeTo and upgradeToAndCall 35 | 36 | In order to switch to the new implementation we need to first deploy the new implementation and then tell proxy to use the new implementation instead of old one. 37 | This is carried out using 2 methods on proxy SC: `upgradeTo` and `upgradeToAndCall`. 38 | The first one simply switches to the new implementation. 39 | The second one switches to the new implementation and calls a method via `delegatecall`. 40 | This allows you to initialise the proxy storage with new values. 41 | Simply passing them to the implementation constructor has no effect - the call must be in the proxy's context to write to the proxy's storage. 42 | 43 | ### Migrator SC 44 | 45 | For security reasons, `upgradeTo` and `upgradeToAndCall` can only be be called by proxy __admin__. 46 | In addition, we use the [transparent proxy pattern](https://github.com/zeppelinos/zos-lib/pull/36). 47 | This means that proxy service functions can only be called by a proxy admin, other callers are passed to the implementation. 48 | If we need to upgrade a number of proxies at time, within a single transaction, in order to avoid a situation where some contracts are upgraded, and others are not, 49 | then the Migrator smart contract must be used. 50 | This smart contract is a proxy factory, and is therefore the admin of all proxies (it is ownable, so its owner can transfer admin rights to someone else). 51 | It uses the Builder pattern to keep a record of which proxy should be upgraded to which migration (optionally with `data`) and can upgrade all proxies within one transaction. 52 | 53 | ### SC history 54 | 55 | Since we want to keep track of all implementations that were in use, we save compiled version of each in separate folder `artifacts/compiled/vXXX`. 56 | This allows us to reproduce the entire history of upgrades with all initialisation data and values/flags set within those upgrades. 57 | 58 | ## How to make the next migration 59 | 60 | ### Create a new folder under compiled 61 | 62 | Create a new version folder under `artifacts/compiled`. 63 | Run `truffle compile`, move the resultant artifacts into the new folder and check in. 64 | You can now reference these artifacts in the migration script. 65 | 66 | ### Create a new migration script 67 | 68 | Create a new migration script (you must follow NUMBER_description_of_migration.js pattern). Reference your artifact by name and version: 69 | ```js 70 | const compiledContractHelper = require('./utils/compiledContractHelper'); 71 | // The first argument is web3 provider. Could be taken from the deployer instance. 72 | const getCompiledContract = compiledContractHelper(deployer.provider, 'version number'); 73 | const contract = getCompiledContract('contract name'); 74 | ``` 75 | 76 | You can now deploy your contract via `deployer`: 77 | ```js 78 | await deployer.deploy(contract, arguments, { from, gas, gasPrice }); 79 | ``` 80 | 81 | and don't forget to save the artifact: 82 | ```js 83 | const TruffleArtifactor = require('truffle-artifactor'); 84 | const artifactor = new TruffleArtifactor('artifacts/deployed/'); 85 | await artifactor.saveAll({ 86 | contractName: _(contract) 87 | .pick(['contractName', 'abi', 'compiler', 'networks']) 88 | .set(['networks', deployer.network_id, 'address'], proxy.address) 89 | .value() 90 | }); 91 | ``` -------------------------------------------------------------------------------- /migrations/utils/compiledContractHelper.js: -------------------------------------------------------------------------------- 1 | const truffleContract = require('truffle-contract'); 2 | 3 | module.exports = (provider, version) => name => { 4 | const contract = truffleContract(require(`../../artifacts/compiled/${version}/${name}.json`)); 5 | contract.setProvider(provider); 6 | return contract; 7 | }; 8 | -------------------------------------------------------------------------------- /migrations/utils/deployedContractHelper.js: -------------------------------------------------------------------------------- 1 | const truffleContract = require('truffle-contract'); 2 | 3 | module.exports = provider => name => { 4 | const contract = truffleContract(require(`../../artifacts/deployed/${name}`)); 5 | contract.setProvider(provider); 6 | return contract.deployed(); 7 | }; 8 | -------------------------------------------------------------------------------- /migrations/utils/index.js: -------------------------------------------------------------------------------- 1 | const Web3 = require('web3'); 2 | 3 | const encodeABI = (contract, method, args) => 4 | contract.at(contract.address).contract[method].getData.apply(contract, args); 5 | 6 | const unlockAccount = (deployer, config, duration = 600) => { 7 | if (!config.password || !config.from) return; 8 | 9 | const web3 = new Web3(deployer.provider); 10 | console.log(`>> Unlocking account ${config.from}`); 11 | web3.personal.unlockAccount(config.from, config.password(), duration); 12 | }; 13 | 14 | module.exports = { 15 | encodeABI, 16 | unlockAccount 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "identity-com-smart-contracts", 3 | "version": "1.0.0", 4 | "description": "Smart Contracts for Identity.com", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "ganache-up": "docker-compose up -d", 11 | "ganache-restart": "docker-compose restart", 12 | "ganache-down": "docker-compose down", 13 | "build": "truffle compile && cross-env NETWORK=ganache npm run deploy-contracts", 14 | "test": "npm-run-all ganache-up test-contracts ganache-down --continue-on-error", 15 | "test-contracts": "cross-env NODE_ENV=test truffle test --network ganache", 16 | "lint": "npm run solium", 17 | "solium": "solium -d contracts/", 18 | "solium-autofix": "solium -d contracts/ --fix", 19 | "predeploy-contracts": "mkdir -p ./build/contracts && find ./artifacts/deployed/ -name \\*.json -exec cp {} ./build/contracts/ \\;", 20 | "deploy-contracts": "cross-env ACCOUNT_PASSWORD=privatenet123 NODE_ENV=test truffle migrate --network ${NETWORK}", 21 | "check": "npm-run-all test lint", 22 | "check-ci": "npm-run-all test-contracts lint", 23 | "audit-ci": "audit-ci --config audit-ci.json" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/identity-com/smart-contracts.git" 28 | }, 29 | "author": "", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/identity-com/smart-contracts/issues" 33 | }, 34 | "homepage": "https://github.com/identity-com/smart-contracts#readme", 35 | "dependencies": { 36 | "babel-register": "^6.26.0", 37 | "bignumber.js": "^4.0.4", 38 | "ethereumjs-tx": "^1.3.3", 39 | "lodash": "^4.17.4", 40 | "openzeppelin-solidity": "1.10.0", 41 | "truffle": "^4.1.11", 42 | "truffle-artifactor": "^3.0.8", 43 | "truffle-contract": "^3.0.7", 44 | "web3": "^0.20.6" 45 | }, 46 | "devDependencies": { 47 | "audit-ci": "^1.3.0", 48 | "babel-eslint": "^8.2.1", 49 | "chai": "^4.1.2", 50 | "chai-as-promised": "^7.1.1", 51 | "chai-bignumber": "^2.0.2", 52 | "cross-env": "^5.1.3", 53 | "eth-gas-reporter": "^0.1.1", 54 | "ethereumjs-abi": "^0.6.5", 55 | "ethereumjs-util": "^5.1.2", 56 | "mocha": "^4.1.0", 57 | "npm-run-all": "^4.1.5", 58 | "solc": "^0.4.23", 59 | "solium": "^1.2.3", 60 | "solium-plugin-zeppelin": "0.0.2" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /scripts/downloadArtifacts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | STAGE=$1 3 | # S3 bucket name where contract artifacts are stored. 4 | BUCKET=marketplace-contract-artifacts 5 | 6 | echo "Downloading contract artifacts from ${BUCKET} for ${STAGE} stage" 7 | aws s3 sync s3://${BUCKET}/${STAGE}/ artifacts/deployed/ > /dev/stdout 2>&1 8 | -------------------------------------------------------------------------------- /scripts/uploadArtifacts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | STAGE=$1 3 | # S3 bucket name where contract artifacts are stored. 4 | BUCKET=marketplace-contract-artifacts 5 | 6 | echo "Uploading contract artifacts to ${BUCKET} for ${STAGE} stage" 7 | aws s3 sync artifacts/deployed/ s3://${BUCKET}/${STAGE}/ > /dev/stdout 2>&1 8 | -------------------------------------------------------------------------------- /scripts/withTunnel.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [[ "${STAGE}" == "test" ]]; then 3 | HOST=$BLOCKCHAIN_NODE_HOST_TEST 4 | else 5 | HOST=$BLOCKCHAIN_NODE_HOST_DEV 6 | fi; 7 | 8 | echo "Connecting to server ${HOST}" 9 | 10 | ssh -o ExitOnForwardFailure=yes -o StrictHostKeyChecking=no -L 9545:localhost:8545 -N ubuntu@${HOST} & 11 | pid=$! 12 | 13 | echo "Waiting a few seconds to establish the tunnel..." 14 | sleep 5 15 | 16 | # check if the tunnel exists and fail out if not 17 | kill -0 $pid > /dev/null 2>&1 || exit 1; 18 | 19 | "$@" 20 | exitcode=$? 21 | echo "Killing ssh tunnel $pid" 22 | kill $pid 23 | exit $exitcode 24 | -------------------------------------------------------------------------------- /test/TestCvcMigrator.js: -------------------------------------------------------------------------------- 1 | // `artifacts.require` helper introduced by Truffle 2 | const MigratorContract = artifacts.require('CvcMigrator'); 3 | const ProxyContract = artifacts.require('CvcProxy'); 4 | const ImplementationContract = artifacts.require('stubs/TestProxyImplementationV0'); 5 | const chai = require('chai'); 6 | const ethUtil = require('ethereumjs-util'); 7 | const encodeCall = require('./helpers/encodeCall'); 8 | 9 | chai.use(require('chai-as-promised')); 10 | chai.use(require('chai-bignumber')()); 11 | 12 | const { expect } = chai; 13 | 14 | // Disabling no-undef because of `contract` helper introduced by Truffle 15 | // eslint-disable-next-line no-undef 16 | contract('CvcMigrator', accounts => { 17 | const [admin, stranger] = accounts; 18 | let migrator; 19 | let proxy; 20 | let implementation; 21 | 22 | beforeEach('Get fresh migrator instance', async () => { 23 | migrator = await MigratorContract.new({ from: admin }); 24 | proxy = await ProxyContract.new({ from: admin }); 25 | implementation = await ImplementationContract.new({ from: admin }); 26 | await proxy.changeAdmin(migrator.address, { from: admin }); 27 | }); 28 | 29 | describe('can transfer ownership', () => { 30 | beforeEach('admin is the owner of the migrator', async () => expect(await migrator.owner()).to.equal(admin)); 31 | beforeEach('migrator is the admin of the proxy', async () => 32 | expect(await proxy.admin({ from: migrator.address })).to.equal(migrator.address) 33 | ); 34 | 35 | describe('by admin', () => { 36 | it('should transfer ownership back to admin', async () => { 37 | await migrator.changeProxyAdmin(proxy.address, admin, { from: admin }); 38 | expect(await proxy.admin({ from: admin })).to.equal(admin, 'oops'); 39 | }); 40 | }); 41 | 42 | describe('denied from stranger', () => { 43 | it('should revert', () => 44 | expect(migrator.changeProxyAdmin(proxy.address, stranger, { from: stranger })).to.be.rejected); 45 | 46 | afterEach('migrator is still the owner of the proxy', async () => 47 | expect(await proxy.admin({ from: migrator.address })).to.equal(migrator.address) 48 | ); 49 | }); 50 | }); 51 | 52 | describe('when migrating', () => { 53 | beforeEach('assert current implementation is empty', async () => 54 | expect(await proxy.implementation()).to.equal(ethUtil.zeroAddress()) 55 | ); 56 | 57 | it('saves upgrade in the list', async () => { 58 | await migrator.addUpgrade(proxy.address, implementation.address, '0xabcdef', { from: admin }); 59 | expect(await migrator.getMigrationCount()).to.bignumber.equal(1); 60 | expect(await migrator.getMigration(0)).to.deep.equal([proxy.address, implementation.address, '0xabcdef']); 61 | }); 62 | 63 | describe('with migration list', () => { 64 | describe('does upgrade', () => { 65 | beforeEach('setup migration list', () => 66 | migrator.addUpgrade(proxy.address, implementation.address, '0x', { from: admin }) 67 | ); 68 | 69 | it('can upgradeTo', async () => { 70 | await migrator.migrate(); 71 | expect(await migrator.getMigrationCount()).to.bignumber.equal(0); 72 | expect(await proxy.implementation()).to.equal(implementation.address); 73 | }); 74 | }); 75 | describe('does upgrade and initialize', () => { 76 | beforeEach('setup migration list with initialize', async () => { 77 | const initializeData = encodeCall('initialize', ['address'], [admin]); 78 | await migrator.addUpgrade(proxy.address, implementation.address, initializeData, { from: admin }); 79 | }); 80 | 81 | it('can upgradeToAndCall', async () => { 82 | await migrator.migrate(); 83 | expect(await migrator.getMigrationCount()).to.bignumber.equal(0); 84 | expect(await proxy.implementation()).to.equal(implementation.address); 85 | }); 86 | }); 87 | 88 | describe('can reset', () => { 89 | beforeEach('setup migration list', () => 90 | migrator.addUpgrade(proxy.address, implementation.address, '0x', { from: admin }) 91 | ); 92 | 93 | it('cleans the list', async () => { 94 | await migrator.reset({ from: admin }); 95 | expect(await proxy.implementation()).to.equal(ethUtil.zeroAddress()); 96 | expect(await migrator.getMigrationCount()).to.bignumber.equal(0); 97 | }); 98 | 99 | describe('when called by stranger', () => { 100 | it('reverts when reset by stranger', () => expect(migrator.reset({ from: stranger })).to.be.rejected); 101 | 102 | afterEach('current implementation is unchanged', async () => 103 | expect(await proxy.implementation()).to.equal(ethUtil.zeroAddress()) 104 | ); 105 | 106 | afterEach('migrations list is not empty', async () => 107 | expect(await migrator.getMigrationCount()).to.bignumber.be.above(0) 108 | ); 109 | }); 110 | }); 111 | }); 112 | }); 113 | 114 | describe('can create proxies', () => { 115 | it('deploys proxy', async () => { 116 | const { logs } = await migrator.createProxy(); 117 | expect(logs) 118 | .to.be.an('array') 119 | .with.lengthOf(1); 120 | const [event] = logs; 121 | expect(event).to.have.property('event', 'ProxyCreated'); 122 | expect(event).to.have.nested.property('args.proxyAddress'); 123 | const freshProxy = ProxyContract.at(event.args.proxyAddress); 124 | expect(await freshProxy.admin({ from: migrator.address })).to.equal(migrator.address); 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /test/TestCvcOntology.js: -------------------------------------------------------------------------------- 1 | // `artifacts.require` helper introduced by Truffle 2 | const CvcOntology = artifacts.require('CvcOntology'); 3 | const chai = require('chai'); 4 | chai.use(require('chai-as-promised')); 5 | 6 | const OntologyAccessor = artifacts.require('stubs/OntologyAccessor'); 7 | const { expect } = chai; 8 | 9 | // Disabling no-undef because of `contract` helper introduced by Truffle 10 | // eslint-disable-next-line no-undef 11 | contract('CvcOntology', accounts => { 12 | const [admin] = accounts; 13 | const gas = 6000000; 14 | let ontology; 15 | let ontologyAccessor; 16 | const type = 'credential'; 17 | const name = 'proofOfIdentity'; 18 | const version = 'v1.0'; 19 | const reference = 'https://www.identity.com/'; 20 | const referenceType = 'JSON-LD-Context'; 21 | const referenceHash = '0x2cd9bf92c5e20b1b410f5ace94d963a96e89156fbe65b70365e8596b37f1f165'; 22 | 23 | beforeEach('Get the fresh Ontology contract instance', async () => { 24 | ontology = await CvcOntology.new({ gas }); 25 | }); 26 | 27 | describe('get record', () => { 28 | beforeEach('add default proofOfIdentity', () => 29 | ontology.add(type, name, version, reference, referenceType, referenceHash) 30 | ); 31 | 32 | it('get by Type-Name-Version', async () => { 33 | const result = await ontology.getByTypeNameVersion(type, name, version); 34 | assertRecord(result, type, name, version); 35 | }); 36 | 37 | it('get non existing', async () => { 38 | const result = await ontology.getById('0x0000000000000000000000000100000000000000000000000000000000000000'); 39 | assertIsEmpty(result); 40 | }); 41 | 42 | it('get all ids', async () => { 43 | const result = await ontology.getAllIds(); 44 | expect(result) 45 | .to.be.an('array') 46 | .with.lengthOf(1); 47 | const [id] = result; 48 | assertNonEmptyHash(id); 49 | }); 50 | 51 | it('get by id', async () => { 52 | const [id] = await ontology.getAllIds(); 53 | const result = await ontology.getById(id); 54 | assertRecord(result, type, name, version); 55 | }); 56 | 57 | describe('get all via stub contract', () => { 58 | beforeEach(async () => { 59 | ontologyAccessor = await OntologyAccessor.new(ontology.address, { from: admin }); 60 | }); 61 | 62 | it('returns all records', async () => { 63 | const ids = await ontology.getAllIds(); 64 | await Promise.all( 65 | ids.map(async (id, i) => { 66 | const ontologyRecord = await ontologyAccessor.getOne(i); 67 | expect(ontologyRecord[0]).to.equal(id); 68 | }) 69 | ); 70 | }); 71 | }); 72 | }); 73 | 74 | describe('add new records', () => { 75 | describe('denies empty parameters', () => { 76 | it('empty type', () => 77 | expect(ontology.add('', name, version, reference, referenceType, referenceHash)).to.be.rejected); 78 | it('empty name', () => 79 | expect(ontology.add(type, '', version, reference, referenceType, referenceHash)).to.be.rejected); 80 | it('empty version', () => 81 | expect(ontology.add(type, name, '', reference, referenceType, referenceHash)).to.be.rejected); 82 | it('empty reference', () => 83 | expect(ontology.add(type, name, version, '', referenceType, referenceHash)).to.be.rejected); 84 | it('empty referenceType', () => 85 | expect(ontology.add(type, name, version, reference, '', referenceHash)).to.be.rejected); 86 | it('empty referenceHash', () => 87 | expect(ontology.add(type, name, version, reference, referenceType, '')).to.be.rejected); 88 | }); 89 | 90 | describe('accepts new claim', () => { 91 | beforeEach('add new claim', () => ontology.add('claim', 'age', 'v1.0', reference, referenceType, referenceHash)); 92 | 93 | it('returns new claim', async () => { 94 | const record = await ontology.getByTypeNameVersion('claim', 'age', 'v1.0'); 95 | assertRecord(record, 'claim', 'age', 'v1.0'); 96 | }); 97 | 98 | it('denies modifying existing record', () => 99 | expect(ontology.add('claim', 'age', 'v1.0', reference, referenceType, referenceHash)).to.be.rejected); 100 | 101 | it('denies adding by stranger', () => 102 | expect( 103 | ontology.add('claim', 'age', 'v1.0', reference, referenceType, referenceHash, { 104 | from: accounts[2] 105 | }) 106 | ).to.be.rejected); 107 | }); 108 | }); 109 | 110 | describe('deprecate old records', () => { 111 | beforeEach('add default proofOfIdentity', () => 112 | ontology.add(type, name, version, reference, referenceType, referenceHash) 113 | ); 114 | 115 | describe('can deprecate', () => { 116 | before('assert it is not deprecated', async () => { 117 | const record = await ontology.getByTypeNameVersion(type, name, version); 118 | expect(record[7]).to.be.false; // eslint-disable-line no-unused-expressions 119 | }); 120 | 121 | it('by external id', async () => { 122 | await ontology.deprecate(type, name, version); 123 | const record = await ontology.getByTypeNameVersion(type, name, version); 124 | assertRecord(record, type, name, version); 125 | expect(record[7]).to.be.true; // eslint-disable-line no-unused-expressions 126 | }); 127 | 128 | it('by internal id', async () => { 129 | const freshRecord = await ontology.getByTypeNameVersion(type, name, version); 130 | await ontology.deprecateById(freshRecord[0]); 131 | const deprecatedRecord = await ontology.getByTypeNameVersion(type, name, version); 132 | assertRecord(deprecatedRecord, type, name, version); 133 | expect(deprecatedRecord[7]).to.be.true; // eslint-disable-line no-unused-expressions 134 | }); 135 | }); 136 | 137 | describe('denies deprecating', () => { 138 | describe('by stranger', () => { 139 | it('by external id', () => 140 | expect(ontology.deprecate(type, name, version, { from: accounts[2] })).to.be.rejected); 141 | 142 | it('by internal id', async () => { 143 | const [id] = await ontology.getByTypeNameVersion(type, name, version); 144 | await expect(ontology.deprecateById(id, { from: accounts[2] })).to.be.rejected; 145 | }); 146 | }); 147 | 148 | describe('more than once', () => { 149 | beforeEach('deprecate default record', () => ontology.deprecate(type, name, version)); 150 | 151 | it('fails to deprecate', () => expect(ontology.deprecate(type, name, version)).to.be.rejected); 152 | }); 153 | 154 | describe('non-existing record', () => { 155 | it('fails to deprecate', () => expect(ontology.deprecate('some', 'random', 'record')).to.be.rejected); 156 | }); 157 | }); 158 | }); 159 | }); 160 | 161 | function assertIsEmpty(record) { 162 | expect(record) 163 | .to.be.an('array') 164 | .with.lengthOf(8); 165 | const [id, type, name, version, reference, referenceType, referenceHash, deprecated] = record; 166 | expect(id).to.match(/0x0{64}/); 167 | expect(type).to.equal(''); 168 | expect(name).to.equal(''); 169 | expect(version).to.equal(''); 170 | expect(reference).to.equal(''); 171 | expect(referenceType).to.equal(''); 172 | expect(referenceHash).to.match(/0x0{64}/); 173 | expect(deprecated).to.be.false; // eslint-disable-line no-unused-expressions 174 | } 175 | 176 | function assertRecord(record, expectedType, expectedName, expectedVersion) { 177 | expect(record) 178 | .to.be.an('array') 179 | .with.lengthOf(8); 180 | const [id, type, name, version, reference, referenceType, referenceHash, deprecated] = record; 181 | assertNonEmptyHash(id); 182 | expect(type).to.equal(expectedType); 183 | expect(name).to.equal(expectedName); 184 | expect(version).to.equal(expectedVersion); 185 | expect(reference).to.equal('https://www.identity.com/'); 186 | expect(referenceType).to.equal('JSON-LD-Context'); 187 | assertNonEmptyHash(referenceHash); 188 | expect(deprecated).to.be.a('boolean'); 189 | } 190 | 191 | function assertNonEmptyHash(bytes32string) { 192 | return expect(bytes32string) 193 | .to.match(/^0x[0-f]{64}$/, 'Value is not bytes32 hex string') 194 | .to.not.match(/0x0{64}/, 'Empty value'); 195 | } 196 | -------------------------------------------------------------------------------- /test/TestCvcPricing.js: -------------------------------------------------------------------------------- 1 | // `artifacts.require` helper introduced by Truffle 2 | const CvcOntology = artifacts.require('CvcOntology'); 3 | const CvcValidatorRegistry = artifacts.require('CvcValidatorRegistry'); 4 | const CvcPricing = artifacts.require('CvcPricing'); 5 | const PricingAccessor = artifacts.require('stubs/PricingAccessor'); 6 | const chai = require('chai'); 7 | chai.use(require('chai-bignumber')()); 8 | chai.use(require('chai-as-promised')); 9 | const Bn = require('bignumber.js'); 10 | const { 11 | TOTAL_SUPPLY, 12 | EVENT_CREDENTIAL_ITEM_PRICE_SET, 13 | EVENT_CREDENTIAL_ITEM_PRICE_DELETED 14 | } = require('../constants'); 15 | 16 | const { expect } = chai; 17 | 18 | // Disabling no-undef because of `contract` helper introduced by Truffle 19 | // eslint-disable-next-line no-undef 20 | contract('CvcPricing', accounts => { 21 | const [admin, idv, idv2, idv3, idv4, idv5] = accounts; 22 | const gas = 6000000; 23 | const type = 'Credential'; 24 | const name = 'proof_of_identity'; 25 | const version = 'v1.0'; 26 | const reference = 'https://www.identity.com/'; 27 | const referenceType = 'JSON-LD-Context'; 28 | const referenceHash = '0x2cd9bf92c5e20b1b410f5ace94d963a96e89156fbe65b70365e8596b37f1f165'; 29 | const price = new Bn(2000); 30 | 31 | // IDV 32 | const idvName = 'Test IDV'; 33 | const idvDescription = 'IDV for testing'; 34 | 35 | // Contracts 36 | let pricing; 37 | let ontology; 38 | let idvRegistry; 39 | let pricingAccessor; 40 | 41 | beforeEach('Deploy Pricing contract', async () => { 42 | ontology = await CvcOntology.new({ from: admin, gas }); 43 | idvRegistry = await CvcValidatorRegistry.new({ from: admin, gas }); 44 | pricing = await CvcPricing.new(ontology.address, idvRegistry.address, { from: admin, gas }); 45 | pricingAccessor = await PricingAccessor.new(pricing.address, { from: admin, gas }); 46 | }); 47 | 48 | describe('Initialization', () => { 49 | describe('when the CvcOntology is not a deployed contract address', () => { 50 | it('reverts', async () => { 51 | const cvcOntologyAddress = admin; 52 | await expect(CvcPricing.new(cvcOntologyAddress, idvRegistry.address, { from: admin, gas })).to.be.rejected; 53 | }); 54 | }); 55 | describe('when the CvcValidatorRegistry is not a deployed contract address', () => { 56 | it('reverts', async () => { 57 | const idvRegistryAddress = admin; 58 | await expect(CvcPricing.new(ontology.address, idvRegistryAddress, { from: admin, gas })).to.be.rejected; 59 | }); 60 | }); 61 | }); 62 | 63 | describe('Getting prices:', () => { 64 | describe('when IDV is not registered', () => { 65 | it('returns the fallback price', async () => 66 | assertFallbackPrice(await pricing.getPrice(idv, type, name, version))); 67 | }); 68 | 69 | describe('when IDV is registered', () => { 70 | beforeEach(() => idvRegistry.set(idv, idvName, idvDescription, { from: admin })); 71 | 72 | describe('when credential item is undefined', () => { 73 | it('returns the fallback price', async () => 74 | assertFallbackPrice(await pricing.getPrice(idv, type, 'non-existent', version))); 75 | }); 76 | 77 | describe('when credential item is registered', () => { 78 | beforeEach(() => ontology.add(type, name, version, reference, referenceType, referenceHash, { from: admin })); 79 | 80 | describe('when price is not set', () => { 81 | it('returns the fallback price', async () => 82 | assertFallbackPrice(await pricing.getPrice(idv, type, name, version))); 83 | }); 84 | 85 | describe('when price is set', () => { 86 | const from = idv; 87 | beforeEach(() => pricing.setPrice(type, name, version, price, { from })); 88 | 89 | it('returns the correct price', async () => { 90 | const credentialItemPrice = await pricing.getPrice(idv, type, name, version); 91 | assertCredentialItemPrice(credentialItemPrice, [price, idv, type, name, version, false]); 92 | }); 93 | 94 | it('returns the correct price by ID', async () => { 95 | // get first price from the list of all prices via accessor 96 | const credentialItemPrice = await pricingAccessor.getOne(0); 97 | // fetch single credential item price by ID 98 | expect(credentialItemPrice).to.deep.equal(await pricing.getPriceById(credentialItemPrice[0])); 99 | }); 100 | 101 | it('returns the correct price by credential item id', async () => { 102 | const credentialItem = await ontology.getByTypeNameVersion(type, name, version); 103 | const priceByCredentialTypeNameVersion = await pricing.getPrice(idv, type, name, version); 104 | const priceByCredentialItemId = await pricing.getPriceByCredentialItemId(idv, credentialItem[0]); 105 | expect(priceByCredentialTypeNameVersion).to.deep.equal(priceByCredentialItemId); 106 | }); 107 | 108 | describe('when credential item is deprecated', () => { 109 | beforeEach(() => ontology.deprecate(type, name, version, { from: admin })); 110 | 111 | it('still returns correct price', async () => { 112 | const credentialItemPrice = await pricing.getPrice(idv, type, name, version); 113 | assertCredentialItemPrice(credentialItemPrice, [price, idv, type, name, version, true]); 114 | }); 115 | }); 116 | }); 117 | 118 | describe('when multiple prices are set', () => { 119 | const from = idv; 120 | const names = ['A', 'B', 'C'].map(i => `${name}${i}`); 121 | beforeEach(async () => { 122 | for (const credentialItemName of names) { 123 | // eslint-disable-next-line no-await-in-loop 124 | await ontology.add(type, credentialItemName, version, reference, referenceType, referenceHash, { 125 | from: admin 126 | }); 127 | // eslint-disable-next-line no-await-in-loop 128 | await pricing.setPrice(type, credentialItemName, version, price, { from }); 129 | } 130 | }); 131 | 132 | it('returns all IDs', async () => { 133 | const ids = await pricing.getAllIds(); 134 | expect(ids.length).to.equal(names.length); 135 | }); 136 | 137 | it('returns all prices', async () => { 138 | await Promise.all( 139 | names.map(async (credentialItemName, i) => { 140 | const credentialItemPrice = await pricingAccessor.getOne(i); 141 | assertCredentialItemPrice(credentialItemPrice, [price, idv, type, credentialItemName, version, false]); 142 | }) 143 | ); 144 | }); 145 | }); 146 | }); 147 | }); 148 | }); 149 | 150 | describe('Setting prices:', () => { 151 | describe('when the contract is paused', () => { 152 | beforeEach(() => pricing.pause({ from: admin })); 153 | it('reverts', () => expect(pricing.setPrice(type, name, version, price, { from: idv })).to.be.rejected); 154 | }); 155 | 156 | describe('when IDV is not registered', () => { 157 | it('reverts', () => expect(pricing.setPrice(type, name, version, price, { from: idv })).to.be.rejected); 158 | }); 159 | 160 | describe('when IDV is registered', () => { 161 | beforeEach(() => idvRegistry.set(idv, idvName, idvDescription, { from: admin })); 162 | 163 | describe('when the sender is not registered IDV', () => { 164 | it('reverts', () => expect(pricing.setPrice(type, name, version, price, { from: admin })).to.be.rejected); 165 | }); 166 | 167 | describe('when credential item is undefined', () => { 168 | it('reverts', () => 169 | expect(pricing.setPrice(type, 'non-existent', version, price, { from: idv })).to.be.rejected); 170 | }); 171 | 172 | describe('when credential item is registered', () => { 173 | beforeEach(() => ontology.add(type, name, version, reference, referenceType, referenceHash, { from: admin })); 174 | 175 | describe('when credential item is deprecated', () => { 176 | beforeEach(() => ontology.deprecate(type, name, version, { from: admin })); 177 | 178 | it('reverts', () => expect(pricing.setPrice(type, name, version, price, { from: idv })).to.be.rejected); 179 | }); 180 | 181 | describe('when price is not set', () => { 182 | it('sets the correct price', async () => { 183 | await pricing.setPrice(type, name, version, price, { from: idv }); 184 | const credentialItemPrice = await pricing.getPrice(idv, type, name, version); 185 | assertCredentialItemPrice(credentialItemPrice, [price, idv, type, name, version]); 186 | }); 187 | 188 | it('dispatches price set event', async () => { 189 | const credentialItem = await ontology.getByTypeNameVersion(type, name, version); 190 | const { logs } = await pricing.setPrice(type, name, version, price, { from: idv }); 191 | const priceSettingEvents = logs.filter(e => e.event === EVENT_CREDENTIAL_ITEM_PRICE_SET); 192 | expect(priceSettingEvents).to.be.lengthOf(1); 193 | 194 | assertCredentialItemPriceSetEvent(priceSettingEvents[0], [ 195 | price, 196 | idv, 197 | type, 198 | name, 199 | version, 200 | credentialItem[0] 201 | ]); 202 | }); 203 | }); 204 | 205 | describe('when price is set', () => { 206 | beforeEach(() => pricing.setPrice(type, name, version, price, { from: idv })); 207 | 208 | it('updates price correctly', async () => { 209 | const newPrice = price.add(100); 210 | await pricing.setPrice(type, name, version, newPrice, { from: idv }); 211 | const credentialItemPrice = await pricing.getPrice(idv, type, name, version); 212 | assertCredentialItemPrice(credentialItemPrice, [newPrice, idv, type, name, version]); 213 | }); 214 | 215 | it('does not duplicate IDs', async () => { 216 | const idsBefore = await pricing.getAllIds(); 217 | // set price for existing credential item again 218 | const newPrice = price.add(100); 219 | await pricing.setPrice(type, name, version, newPrice, { from: idv }); 220 | const idsAfter = await pricing.getAllIds(); 221 | expect(idsBefore).to.deep.equal(idsAfter); 222 | }); 223 | 224 | it('dispatches price set event', async () => { 225 | const credentialItem = await ontology.getByTypeNameVersion(type, name, version); 226 | const result = await pricing.setPrice(type, name, version, price, { from: idv }); 227 | const priceSettingEvents = result.logs.filter(e => e.event === EVENT_CREDENTIAL_ITEM_PRICE_SET); 228 | expect(priceSettingEvents).to.be.lengthOf(1); 229 | 230 | assertCredentialItemPriceSetEvent(priceSettingEvents[0], [ 231 | price, 232 | idv, 233 | type, 234 | name, 235 | version, 236 | credentialItem[0] 237 | ]); 238 | }); 239 | }); 240 | 241 | describe('when price is greater than CVC total supply', () => { 242 | it('reverts', () => { 243 | const invalidPrice = new Bn(TOTAL_SUPPLY).add(1); 244 | return expect(pricing.setPrice(type, name, version, invalidPrice, { from: idv })).to.be.rejected; 245 | }); 246 | }); 247 | 248 | describe('when price is equal to CVC total supply', () => { 249 | it('sets the correct price', async () => { 250 | const maxPrice = new Bn(TOTAL_SUPPLY); 251 | await pricing.setPrice(type, name, version, maxPrice, { from: idv }); 252 | const credentialItemPrice = await pricing.getPrice(idv, type, name, version); 253 | assertCredentialItemPrice(credentialItemPrice, [maxPrice, idv, type, name, version]); 254 | }); 255 | }); 256 | 257 | describe('when price is zero', () => { 258 | it('sets the correct price', async () => { 259 | const zeroPrice = 0; 260 | await pricing.setPrice(type, name, version, zeroPrice, { from: idv }); 261 | const credentialItemPrice = await pricing.getPrice(idv, type, name, version); 262 | assertCredentialItemPrice(credentialItemPrice, [zeroPrice, idv, type, name, version]); 263 | }); 264 | }); 265 | }); 266 | }); 267 | }); 268 | 269 | describe('Deleting prices:', () => { 270 | describe('when the contract is paused', () => { 271 | beforeEach(() => pricing.pause({ from: admin })); 272 | it('reverts', () => expect(pricing.deletePrice(type, name, version, { from: idv })).to.be.rejected); 273 | }); 274 | 275 | describe('when credential item is undefined', () => { 276 | it('reverts', () => expect(pricing.deletePrice(type, 'non-existent', version, { from: idv })).to.be.rejected); 277 | }); 278 | 279 | describe('when credential item is registered', () => { 280 | beforeEach(() => ontology.add(type, name, version, reference, referenceType, referenceHash, { from: admin })); 281 | 282 | describe('when price is not set', () => { 283 | it('reverts', () => expect(pricing.deletePrice(type, name, version, { from: idv })).to.be.rejected); 284 | }); 285 | 286 | describe('when price is set', () => { 287 | const idv1 = idv; 288 | const assertIdIsExcluded = (idsBefore, idsAfter, excludedId, includedIds = []) => { 289 | expect(idsAfter).has.lengthOf(idsBefore.length - 1); 290 | expect(idsAfter).not.include(excludedId); 291 | if (includedIds.length > 0) { 292 | expect(idsAfter).to.have.members(includedIds); 293 | } 294 | }; 295 | 296 | beforeEach('register IDVs', async () => { 297 | await idvRegistry.set(idv1, idvName, idvDescription, { from: admin }); 298 | await idvRegistry.set(idv2, idvName, idvDescription, { from: admin }); 299 | await idvRegistry.set(idv3, idvName, idvDescription, { from: admin }); 300 | }); 301 | beforeEach('set prices', async () => { 302 | await pricing.setPrice(type, name, version, price, { from: idv1 }); 303 | await pricing.setPrice(type, name, version, price, { from: idv2 }); 304 | await pricing.setPrice(type, name, version, price, { from: idv3 }); 305 | }); 306 | 307 | describe('when the sender is not price owner IDV', () => { 308 | it('reverts', () => expect(pricing.deletePrice(type, name, version, { from: admin })).to.be.rejected); 309 | }); 310 | 311 | it('deletes first price entry', async () => { 312 | const idsBefore = await pricing.getAllIds(); 313 | const [firstItemId] = await pricing.getPrice(idv1, type, name, version); 314 | expect(firstItemId).to.equal(idsBefore[0], 'First id matches'); 315 | await pricing.deletePrice(type, name, version, { from: idv1 }); 316 | const credentialItemPrice = await pricing.getPrice(idv1, type, name, version); 317 | assertFallbackPrice(credentialItemPrice); 318 | assertIdIsExcluded(idsBefore, await pricing.getAllIds(), firstItemId); 319 | }); 320 | 321 | it('deletes last price entry', async () => { 322 | const idsBefore = await pricing.getAllIds(); 323 | const [lastItemId] = await pricing.getPrice(idv3, type, name, version); 324 | expect(lastItemId).to.equal(idsBefore[2], 'Last id matches'); 325 | await pricing.deletePrice(type, name, version, { from: idv3 }); 326 | const credentialItemPrice = await pricing.getPrice(idv3, type, name, version); 327 | assertFallbackPrice(credentialItemPrice); 328 | assertIdIsExcluded(idsBefore, await pricing.getAllIds(), lastItemId); 329 | }); 330 | 331 | it('dispatches price deleted event', async () => { 332 | const credentialItem = await ontology.getByTypeNameVersion(type, name, version); 333 | const result = await pricing.deletePrice(type, name, version, { from: idv3 }); 334 | const priceDeletingEvents = result.logs.filter(e => e.event === EVENT_CREDENTIAL_ITEM_PRICE_DELETED); 335 | expect(priceDeletingEvents).to.be.lengthOf(1); 336 | assertCredentialItemPriceDeletedEvent(priceDeletingEvents[0], [idv3, type, name, version, credentialItem[0]]); 337 | }); 338 | 339 | it('deletes all prices from a long list', async () => { 340 | await idvRegistry.set(idv4, idvName, idvDescription, { from: admin }); 341 | await idvRegistry.set(idv5, idvName, idvDescription, { from: admin }); 342 | await pricing.setPrice(type, name, version, price, { from: idv4 }); 343 | await pricing.setPrice(type, name, version, price, { from: idv5 }); 344 | let allIdsBefore = await pricing.getAllIds(); 345 | const [id1, id2, id3, id4, id5] = allIdsBefore; 346 | let allIdsAfter; 347 | 348 | // delete 3rd price 349 | await pricing.deletePrice(type, name, version, { from: idv3 }); 350 | assertFallbackPrice(await pricing.getPrice(idv3, type, name, version)); 351 | assertFallbackPrice(await pricing.getPriceById(id3)); 352 | allIdsAfter = await pricing.getAllIds(); 353 | assertIdIsExcluded(allIdsBefore, allIdsAfter, id3, [id1, id2, id4, id5]); 354 | 355 | // delete 5th price 356 | allIdsBefore = await pricing.getAllIds(); 357 | await pricing.deletePrice(type, name, version, { from: idv5 }); 358 | assertFallbackPrice(await pricing.getPrice(idv5, type, name, version)); 359 | assertFallbackPrice(await pricing.getPriceById(id5)); 360 | allIdsAfter = await pricing.getAllIds(); 361 | assertIdIsExcluded(allIdsBefore, allIdsAfter, id5, [id1, id2, id4]); 362 | 363 | // delete 4th price 364 | allIdsBefore = await pricing.getAllIds(); 365 | await pricing.deletePrice(type, name, version, { from: idv4 }); 366 | assertFallbackPrice(await pricing.getPrice(idv4, type, name, version)); 367 | assertFallbackPrice(await pricing.getPriceById(id4)); 368 | allIdsAfter = await pricing.getAllIds(); 369 | assertIdIsExcluded(allIdsBefore, allIdsAfter, id4, [id1, id2]); 370 | 371 | // delete 1st price 372 | allIdsBefore = await pricing.getAllIds(); 373 | await pricing.deletePrice(type, name, version, { from: idv1 }); 374 | assertFallbackPrice(await pricing.getPrice(idv1, type, name, version)); 375 | assertFallbackPrice(await pricing.getPriceById(id1)); 376 | allIdsAfter = await pricing.getAllIds(); 377 | assertIdIsExcluded(allIdsBefore, allIdsAfter, id1, [id2]); 378 | 379 | // delete 2nd price 380 | allIdsBefore = await pricing.getAllIds(); 381 | await pricing.deletePrice(type, name, version, { from: idv2 }); 382 | assertFallbackPrice(await pricing.getPrice(idv2, type, name, version)); 383 | assertFallbackPrice(await pricing.getPriceById(id2)); 384 | allIdsAfter = await pricing.getAllIds(); 385 | assertIdIsExcluded(allIdsBefore, allIdsAfter, id2); 386 | 387 | expect(await pricing.getAllIds()).to.be.lengthOf(0); 388 | }); 389 | }); 390 | }); 391 | }); 392 | }); 393 | 394 | function assertCredentialItemPrice( 395 | credentialItemPrice, 396 | [expectedPrice, expectedIdv, expectedType, expectedName, expectedVersion, expectedDeprecated] 397 | ) { 398 | expect(credentialItemPrice) 399 | .to.be.an('array') 400 | .with.lengthOf(7); 401 | const [ 402 | id, 403 | price, 404 | idv, 405 | credentialItemType, 406 | credentialItemName, 407 | credentialItemVersion, 408 | deprecated 409 | ] = credentialItemPrice; 410 | expect(id) 411 | .to.match(/^0x[0-f]{64}$/, 'Price ID is not bytes32 hex string') 412 | .to.not.match(/0x0{64}/, 'Empty price ID'); 413 | expect(price).to.bignumber.equal(expectedPrice, 'Invalid price'); 414 | expect(idv).to.equal(expectedIdv, 'Invalid IDV address'); 415 | expect(credentialItemType).to.equal(expectedType, 'Invalid Type'); 416 | expect(credentialItemName).to.equal(expectedName, 'Invalid Name'); 417 | expect(credentialItemVersion).to.equal(expectedVersion, 'Invalid Version'); 418 | expect(deprecated).to.be.a('boolean', 'Deprecated flag missing or invalid'); 419 | if (typeof expectedDeprecated !== 'undefined') { 420 | expect(deprecated).to.equal(expectedDeprecated, 'Invalid deprecated value'); 421 | } 422 | } 423 | 424 | function assertFallbackPrice(credentialItemPrice) { 425 | // Max available value could returned by getPrice method. 426 | // It is more than CVC total supply, which is invalid price. 427 | const fallbackPrice = new Bn(TOTAL_SUPPLY).add(1); 428 | expect(credentialItemPrice) 429 | .to.be.an('array') 430 | .with.lengthOf(7); 431 | const [ 432 | id, 433 | price, 434 | idv, 435 | credentialItemType, 436 | credentialItemName, 437 | credentialItemVersion, 438 | deprecated 439 | ] = credentialItemPrice; 440 | expect(id).to.match(/0x0{64}/, 'Invalid price ID'); 441 | expect(price).to.bignumber.equal(fallbackPrice, 'Invalid price'); 442 | expect(idv).to.match(/0x0{40}/, 'Invalid IDV address'); 443 | expect(credentialItemType).to.equal('', 'Invalid Type'); 444 | expect(credentialItemName).to.equal('', 'Invalid Name'); 445 | expect(credentialItemVersion).to.equal('', 'Invalid Version'); 446 | expect(deprecated).to.equal(false, 'Invalid deprecated flag value'); 447 | } 448 | 449 | function assertCredentialItemPriceSetEvent( 450 | event, 451 | [ 452 | expectedPrice, 453 | expectedIdv, 454 | expectedCredentialItemType, 455 | expectedCredentialItemName, 456 | expectedCredentialItemVersion, 457 | expectedCredentialItemId 458 | ] 459 | ) { 460 | expect(event.args.id).to.match(/^0x[0-f]{64}$/, 'Invalid price ID'); 461 | expect(event.args.price).to.bignumber.equal(expectedPrice, 'Invalid price'); 462 | expect(event.args.idv).to.equal(expectedIdv, 'Invalid IDV address'); 463 | expect(event.args.credentialItemType).to.equal(expectedCredentialItemType, 'Invalid Credential Item Type'); 464 | expect(event.args.credentialItemName).to.equal(expectedCredentialItemName, 'Invalid Credential Item Name'); 465 | expect(event.args.credentialItemVersion).to.equal(expectedCredentialItemVersion, 'Invalid Credential Item Version'); 466 | expect(event.args.credentialItemId).to.equal(expectedCredentialItemId, 'Invalid Credential Item ID'); 467 | } 468 | 469 | function assertCredentialItemPriceDeletedEvent( 470 | event, 471 | [ 472 | expectedIdv, 473 | expectedCredentialItemType, 474 | expectedCredentialItemName, 475 | expectedCredentialItemVersion, 476 | expectedCredentialItemId 477 | ] 478 | ) { 479 | expect(event.args.id).to.match(/^0x[0-f]{64}$/, 'Invalid price ID'); 480 | expect(event.args.idv).to.equal(expectedIdv, 'Invalid IDV address'); 481 | expect(event.args.credentialItemType).to.equal(expectedCredentialItemType, 'Invalid Credential Item Type'); 482 | expect(event.args.credentialItemName).to.equal(expectedCredentialItemName, 'Invalid Credential Item Name'); 483 | expect(event.args.credentialItemVersion).to.equal(expectedCredentialItemVersion, 'Invalid Credential Item Version'); 484 | expect(event.args.credentialItemId).to.equal(expectedCredentialItemId, 'Invalid Credential Item ID'); 485 | } 486 | -------------------------------------------------------------------------------- /test/TestCvcProxy.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | const chai = require('chai'); 3 | chai.use(require('chai-as-promised')); 4 | chai.use(require('chai-bignumber')()); 5 | const ethUtil = require('ethereumjs-util'); 6 | const Web3 = require('web3'); 7 | const encodeCall = require('./helpers/encodeCall'); 8 | 9 | const CvcProxy = artifacts.require('CvcProxy'); 10 | const DummyImplementation = artifacts.require('stubs/DummyImplementation'); 11 | const DummyImplementationV2 = artifacts.require('stubs/DummyImplementationV2'); 12 | const DummyImplementationV3 = artifacts.require('stubs/DummyImplementationV3'); 13 | const ClashingImplementation = artifacts.require('stubs/ClashingImplementation'); 14 | 15 | const { expect } = chai; 16 | 17 | contract('CvcProxy', accounts => { 18 | const [proxyAdmin, anotherAccount] = accounts; 19 | let proxy; 20 | let implementationV1; 21 | let implementationV2; 22 | let implementationV3; 23 | 24 | const url = process.env.RSK_NODE_URL; 25 | const web3 = new Web3(new Web3.providers.HttpProvider(url)); 26 | 27 | beforeEach('Deploy proxy', async () => { 28 | proxy = await CvcProxy.new({ from: proxyAdmin }); 29 | implementationV1 = (await DummyImplementation.new()).address; 30 | await proxy.upgradeTo(implementationV1, { from: proxyAdmin }); 31 | }); 32 | 33 | it('has an admin', async () => expect(await proxy.admin()).equal(proxyAdmin)); 34 | 35 | describe('changing admin', () => { 36 | describe('when the new proposed admin is the zero address', () => { 37 | it('reverts', async () => expect(proxy.changeAdmin(ethUtil.zeroAddress(), { from: proxyAdmin })).to.be.rejected); 38 | }); 39 | 40 | describe('when the new proposed admin is not the zero address', () => { 41 | const newAdmin = anotherAccount; 42 | 43 | describe('when the sender is not admin', () => { 44 | it('reverts', async () => expect(proxy.changeAdmin(newAdmin, { from: anotherAccount })).to.be.rejected); 45 | }); 46 | 47 | describe('when the sender is the admin', () => { 48 | it('stores the admin address in specified location', async () => { 49 | await proxy.changeAdmin(newAdmin, { from: proxyAdmin }); 50 | const slot = web3.sha3('cvc.proxy.admin'); 51 | const admin = await web3.eth.getStorageAt(proxy.address, slot); 52 | expect(admin).to.equal(newAdmin); 53 | }); 54 | 55 | it('sets new admin', async () => { 56 | await proxy.changeAdmin(newAdmin, { from: proxyAdmin }); 57 | expect(await proxy.admin({ from: newAdmin })).equal(newAdmin); 58 | }); 59 | 60 | it('emits an event', async () => { 61 | const { logs } = await proxy.changeAdmin(newAdmin, { from: proxyAdmin }); 62 | expect(logs).to.have.lengthOf(1); 63 | expect(logs[0].event).to.equal('AdminChanged'); 64 | expect(logs[0].args.previousAdmin).to.equal(proxyAdmin); 65 | expect(logs[0].args.newAdmin).to.equal(newAdmin); 66 | }); 67 | }); 68 | }); 69 | }); 70 | 71 | describe('implementation', () => { 72 | it('stores the implementation address in specified location', async () => { 73 | const slot = web3.sha3('cvc.proxy.implementation'); 74 | const implementation = (await web3.eth.getStorageAt(proxy.address, slot)).substr(2); 75 | expect(`0x${_.padStart(implementation, 40, '0')}`).to.equal(implementationV1); 76 | }); 77 | 78 | it('returns current implementation', async () => { 79 | expect(await proxy.implementation()).to.equal(implementationV1); 80 | }); 81 | 82 | it('delegates to the implementation', async () => { 83 | const implementation = DummyImplementation.at(proxy.address); 84 | const version = await implementation.version({ from: anotherAccount }); 85 | expect(version).to.equal('V1'); 86 | }); 87 | }); 88 | 89 | describe('upgradeTo', () => { 90 | beforeEach('Deploy new implementation', async () => { 91 | implementationV2 = (await DummyImplementationV2.new()).address; 92 | }); 93 | 94 | describe('when the new implementation is not contract', () => { 95 | it('reverts', async () => expect(proxy.upgradeTo(anotherAccount, { from: proxyAdmin })).to.be.rejected); 96 | }); 97 | 98 | describe('when the new implementation is zero address', () => { 99 | it('reverts', async () => expect(proxy.upgradeTo(ethUtil.zeroAddress(), { from: proxyAdmin })).to.be.rejected); 100 | }); 101 | 102 | describe('when the new implementation has the same address', () => { 103 | it('reverts', async () => expect(proxy.upgradeTo(implementationV1, { from: proxyAdmin })).to.be.rejected); 104 | }); 105 | 106 | describe('when the sender is not the proxy admin', () => { 107 | it('reverts', async () => expect(proxy.upgradeTo(implementationV2, { from: anotherAccount })).to.be.rejected); 108 | }); 109 | 110 | describe('when the sender is the proxy admin', () => { 111 | const from = proxyAdmin; 112 | 113 | it('upgrades to the new implementation', async () => { 114 | await proxy.upgradeTo(implementationV2, { from }); 115 | expect(await proxy.implementation()).to.equal(implementationV2); 116 | const implementation = DummyImplementationV2.at(proxy.address); 117 | const version = await implementation.version({ from: anotherAccount }); 118 | expect(version).to.equal('V2'); 119 | }); 120 | 121 | it('emits an event', async () => { 122 | const { logs } = await proxy.upgradeTo(implementationV2, { from }); 123 | expect(logs).to.have.lengthOf(1); 124 | expect(logs[0].event).to.equal('Upgraded'); 125 | expect(logs[0].args.implementation).to.equal(implementationV2); 126 | }); 127 | }); 128 | }); 129 | 130 | describe('upgradeToAndCall', () => { 131 | beforeEach('Deploy new implementation', async () => { 132 | implementationV2 = (await DummyImplementationV2.new()).address; 133 | }); 134 | 135 | describe('when the call does fail', () => { 136 | const v2InitializeData = encodeCall('fail'); 137 | it('reverts', async () => 138 | expect(proxy.upgradeToAndCall(implementationV2, v2InitializeData, { from: proxyAdmin })).to.be.rejected); 139 | }); 140 | 141 | describe('when the call does not fail', () => { 142 | const v2InitializeData = encodeCall('initialize', ['uint256'], [42]); 143 | describe('when the sender is not proxy admin', () => { 144 | it('reverts', async () => 145 | expect(proxy.upgradeToAndCall(implementationV2, v2InitializeData, { from: anotherAccount })).to.be.rejected); 146 | }); 147 | 148 | describe('when the sender is the proxy admin', () => { 149 | const from = proxyAdmin; 150 | 151 | describe('when the new implementation is not contract', () => { 152 | it('reverts', async () => 153 | expect(proxy.upgradeToAndCall(anotherAccount, v2InitializeData, { from: proxyAdmin })).to.be.rejected); 154 | }); 155 | 156 | describe('when the new implementation is zero address', () => { 157 | it('reverts', async () => 158 | expect(proxy.upgradeToAndCall(ethUtil.zeroAddress(), v2InitializeData, { from: proxyAdmin })).to.be 159 | .rejected); 160 | }); 161 | 162 | describe('when the new implementation has the same address', () => { 163 | it('reverts', async () => 164 | expect(proxy.upgradeToAndCall(implementationV1, v2InitializeData, { from: proxyAdmin })).to.be.rejected); 165 | }); 166 | 167 | describe('when upgrading to V2', () => { 168 | it('upgrades to the new implementation', async () => { 169 | await proxy.upgradeToAndCall(implementationV2, v2InitializeData, { from }); 170 | expect(await proxy.implementation()).to.equal(implementationV2); 171 | const implementation = DummyImplementationV2.at(proxy.address); 172 | const version = await implementation.version({ from: anotherAccount }); 173 | expect(version).to.equal('V2'); 174 | }); 175 | 176 | it('emits an event', async () => { 177 | const { logs } = await proxy.upgradeToAndCall(implementationV2, v2InitializeData, { from }); 178 | expect(logs).to.have.lengthOf(1); 179 | expect(logs[0].event).to.equal('Upgraded'); 180 | expect(logs[0].args.implementation).to.equal(implementationV2); 181 | }); 182 | 183 | it('calls the "initialize" function', async () => { 184 | await proxy.upgradeToAndCall(implementationV2, v2InitializeData, { from }); 185 | const implementation = DummyImplementationV2.at(proxy.address); 186 | const value = await implementation.value({ from: anotherAccount }); 187 | expect(value).to.bignumber.equal(42); 188 | }); 189 | 190 | it('sends given value to the proxy balance', async () => { 191 | const value = 1e8; 192 | await proxy.upgradeToAndCall(implementationV2, v2InitializeData, { from, value }); 193 | const balance = await web3.eth.getBalance(proxy.address); 194 | expect(balance).to.bignumber.equal(value); 195 | }); 196 | 197 | it('stores data in proxy context', async () => { 198 | await proxy.upgradeToAndCall(implementationV2, v2InitializeData, { from }); 199 | const value = await web3.eth.getStorageAt(proxy.address, 0); 200 | expect(value).to.bignumber.equal(42); 201 | }); 202 | }); 203 | 204 | describe('when upgrading to V3', () => { 205 | const v3InitializeData = encodeCall('initialize', ['uint256'], [84]); 206 | beforeEach('Deploy new implementation', async () => { 207 | implementationV3 = (await DummyImplementationV3.new()).address; 208 | }); 209 | 210 | it('upgrades to the new implementation', async () => { 211 | await proxy.upgradeToAndCall(implementationV3, v3InitializeData, { from }); 212 | expect(await proxy.implementation()).to.equal(implementationV3); 213 | const implementation = DummyImplementationV3.at(proxy.address); 214 | const version = await implementation.version({ from: anotherAccount }); 215 | expect(version).to.equal('V3'); 216 | }); 217 | 218 | it('emits an event', async () => { 219 | const { logs } = await proxy.upgradeToAndCall(implementationV3, v3InitializeData, { from }); 220 | expect(logs).to.have.lengthOf(1); 221 | expect(logs[0].event).to.equal('Upgraded'); 222 | expect(logs[0].args.implementation).to.equal(implementationV3); 223 | }); 224 | 225 | it('calls the "initialize" function', async () => { 226 | await proxy.upgradeToAndCall(implementationV3, v3InitializeData, { from }); 227 | const implementation = DummyImplementationV3.at(proxy.address); 228 | const value = await implementation.value({ from: anotherAccount }); 229 | expect(value).to.bignumber.equal(84); 230 | }); 231 | }); 232 | }); 233 | }); 234 | }); 235 | 236 | describe('transparent proxy', () => { 237 | let transparentProxy; 238 | let clashing; 239 | beforeEach('Deploy proxy with clashing implementation', async () => { 240 | transparentProxy = await CvcProxy.new({ from: proxyAdmin }); 241 | const implementation = (await ClashingImplementation.new()).address; 242 | await transparentProxy.upgradeTo(implementation, { from: proxyAdmin }); 243 | clashing = ClashingImplementation.at(transparentProxy.address); 244 | }); 245 | 246 | describe('when proxy admin calls delegated function', async () => { 247 | it('reverts', async () => expect(clashing.delegatedFunction({ from: proxyAdmin })).to.be.rejected); 248 | }); 249 | 250 | describe('when function names clash', () => { 251 | describe('when sender is proxy admin', () => { 252 | it('calls the proxy function', async () => { 253 | const value = await transparentProxy.admin({ from: proxyAdmin }); 254 | expect(value).to.equal(proxyAdmin); 255 | }); 256 | }); 257 | 258 | describe('when sender is not proxy admin', () => { 259 | it('delegates to implementation', async () => { 260 | const value = await transparentProxy.admin({ from: anotherAccount }); 261 | expect(value).to.equal('0x1111111111111111111111111111111111111111'); 262 | }); 263 | }); 264 | }); 265 | }); 266 | }); 267 | -------------------------------------------------------------------------------- /test/TestCvcValidatorRegistry.js: -------------------------------------------------------------------------------- 1 | // `artifacts.require` helper introduced by Truffle 2 | const CvcValidatorRegistry = artifacts.require('CvcValidatorRegistry'); 3 | const chai = require('chai'); 4 | const ethUtil = require('ethereumjs-util'); 5 | 6 | const { expect } = chai; 7 | 8 | // Disabling no-undef because of `contract` helper introduced by Truffle 9 | // eslint-disable-next-line no-undef 10 | contract('CvcValidatorRegistry', accounts => { 11 | const [admin, idv, anotherAccount] = accounts; 12 | 13 | const name = 'Test IDV'; 14 | const description = 'IDV for testing'; 15 | 16 | let registry; 17 | beforeEach('Get the Validator Registry contract deployed instance', async () => { 18 | registry = await CvcValidatorRegistry.new({ from: admin }); 19 | }); 20 | 21 | describe('Setting IDV entry data:', () => { 22 | describe('when the sender is not the registry owner', () => { 23 | const from = anotherAccount; 24 | it('reverts', () => expect(registry.set(idv, name, description, { from })).to.be.rejected); 25 | }); 26 | 27 | describe('when the sender is the registry owner', () => { 28 | const from = admin; 29 | 30 | describe('when entry is new', () => { 31 | beforeEach(async () => expect(await registry.exists(idv)).to.be.false); 32 | 33 | it('adds new entry', async () => { 34 | await registry.set(idv, name, description, { from }); 35 | const record = await registry.get(idv); 36 | assertRecord(record, name, description); 37 | }); 38 | }); 39 | 40 | describe('when existing entry', () => { 41 | beforeEach(() => registry.set(idv, name, description, { from })); 42 | 43 | it('updates the existing entry', async () => { 44 | const newName = `${name} Updated`; 45 | const newDescription = `${description} Updated`; 46 | await registry.set(idv, newName, newDescription, { from }); 47 | const record = await registry.get(idv); 48 | assertRecord(record, newName, newDescription); 49 | }); 50 | }); 51 | 52 | describe('when IDV address is empty', () => { 53 | it('reverts', () => expect(registry.set(ethUtil.zeroAddress(), name, description, { from })).to.be.rejected); 54 | }); 55 | 56 | describe('when IDV Name is empty', () => { 57 | it('reverts', () => expect(registry.set(idv, '', description, { from })).to.be.rejected); 58 | }); 59 | }); 60 | }); 61 | 62 | describe('Getting IDV entry data:', () => { 63 | const from = admin; 64 | describe('when entry exists', () => { 65 | beforeEach(() => registry.set(idv, name, description, { from })); 66 | 67 | it('returns entry data', async () => { 68 | const record = await registry.get(idv); 69 | assertRecord(record, name, description); 70 | }); 71 | }); 72 | 73 | describe('when entry does not exist', () => { 74 | it('returns empty result', async () => { 75 | const record = await registry.get(ethUtil.zeroAddress()); 76 | assertRecord(record, '', ''); 77 | }); 78 | }); 79 | }); 80 | 81 | describe('Verifying IDV entry existence:', () => { 82 | const from = admin; 83 | describe('when existing entry', () => { 84 | beforeEach(() => registry.set(idv, name, description, { from })); 85 | 86 | it('returns true', async () => expect(await registry.exists(idv)).to.be.true); 87 | }); 88 | 89 | describe('when entry does not exist', () => { 90 | it('returns false', async () => expect(await registry.exists(idv)).to.be.false); 91 | }); 92 | }); 93 | }); 94 | 95 | function assertRecord(record, expectedName, expectedDescription) { 96 | expect(record) 97 | .to.be.an('array') 98 | .with.lengthOf(2); 99 | const [name, description] = record; 100 | expect(name).to.equal(expectedName); 101 | expect(description).to.equal(expectedDescription); 102 | } 103 | -------------------------------------------------------------------------------- /test/helpers/encodeCall.js: -------------------------------------------------------------------------------- 1 | const abi = require('ethereumjs-abi'); 2 | const BN = require('bignumber.js'); 3 | 4 | function encodeCall(name, args = [], rawValues = []) { 5 | const formatValue = value => (typeof value === 'number' || value instanceof BN ? value.toString() : value); 6 | const methodId = abi.methodID(name, args).toString('hex'); 7 | const params = abi.rawEncode(args, rawValues.map(formatValue)).toString('hex'); 8 | return `0x${methodId}${params}`; 9 | } 10 | 11 | module.exports = encodeCall; 12 | -------------------------------------------------------------------------------- /test/stubs/ClashingImplementation.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | contract ClashingImplementation { 5 | 6 | function admin() external pure returns (address) { 7 | return 0x1111111111111111111111111111111111111111; 8 | } 9 | 10 | function delegatedFunction() external pure returns (bool) { 11 | return true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/stubs/DummyImplementation.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | contract DummyImplementation { 5 | 6 | uint256 public value; 7 | 8 | function initialize(uint256 _value) public payable { 9 | value = _value; 10 | } 11 | 12 | function version() public pure returns (string) { 13 | return "V1"; 14 | } 15 | } 16 | 17 | 18 | contract DummyImplementationV2 is DummyImplementation { 19 | function version() public pure returns (string) { 20 | return "V2"; 21 | } 22 | } 23 | 24 | 25 | contract DummyImplementationV3 is DummyImplementationV2 { 26 | function version() public pure returns (string) { 27 | return "V3"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/stubs/OntologyAccessor.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../contracts/ontology/CvcOntology.sol"; 5 | import "../../contracts/ontology/CvcOntologyInterface.sol"; 6 | 7 | 8 | contract OntologyAccessor { 9 | address internal ontology; 10 | 11 | constructor(address _ontology) public { 12 | ontology = _ontology; 13 | } 14 | 15 | function getOne(uint256 idx) public view returns (bytes32, string, string, string, string, string, bytes32) { 16 | CvcOntologyInterface.CredentialItem memory credentialItem = CvcOntology(ontology).getAll()[idx]; 17 | return ( 18 | credentialItem.id, 19 | credentialItem.recordType, 20 | credentialItem.recordName, 21 | credentialItem.recordVersion, 22 | credentialItem.reference, 23 | credentialItem.referenceType, 24 | credentialItem.referenceHash 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/stubs/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | 4 | /** 5 | * @title Ownable 6 | * @dev This contract has the owner address providing basic authorization control 7 | */ 8 | contract Ownable { 9 | /** 10 | * @dev Event to show ownership has been transferred 11 | * @param previousOwner representing the address of the previous owner 12 | * @param newOwner representing the address of the new owner 13 | */ 14 | event OwnershipTransferred(address previousOwner, address newOwner); 15 | 16 | // Owner of the contract 17 | address private _owner; 18 | 19 | /** 20 | * @dev Throws if called by any account other than the owner. 21 | */ 22 | modifier onlyOwner() { 23 | require(msg.sender == owner()); 24 | _; 25 | } 26 | 27 | /** 28 | * @dev The constructor sets the original owner of the contract to the sender account. 29 | */ 30 | constructor() public { 31 | setOwner(msg.sender); 32 | } 33 | 34 | /** 35 | * @dev Tells the address of the owner 36 | * @return the address of the owner 37 | */ 38 | function owner() public view returns (address) { 39 | return _owner; 40 | } 41 | 42 | /** 43 | * @dev Allows the current owner to transfer control of the contract to a newOwner. 44 | * @param newOwner The address to transfer ownership to. 45 | */ 46 | function transferOwnership(address newOwner) public onlyOwner { 47 | require(newOwner != address(0)); 48 | emit OwnershipTransferred(owner(), newOwner); // solhint-disable-line 49 | setOwner(newOwner); 50 | } 51 | 52 | /** 53 | * @dev Sets a new owner address 54 | */ 55 | function setOwner(address newOwner) internal { 56 | _owner = newOwner; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/stubs/PricingAccessor.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "../../contracts/pricing/CvcPricing.sol"; 5 | 6 | 7 | contract PricingAccessor { 8 | address internal pricing; 9 | 10 | constructor(address _pricing) public {//solhint-disable-line state-visibility 11 | pricing = _pricing; //solhint-disable-line state-visibility 12 | } 13 | 14 | function getOne(uint idx) public view returns (bytes32, uint256, address, string, string, string, bool) { 15 | CvcPricing.CredentialItemPrice memory price = CvcPricing(pricing).getAllPrices()[idx]; 16 | return ( 17 | price.id, 18 | price.price, 19 | price.idv, 20 | price.credentialItemType, 21 | price.credentialItemName, 22 | price.credentialItemVersion, 23 | price.deprecated 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/stubs/TestProxyImplementationV0.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | import "./Ownable.sol"; 5 | 6 | 7 | /** 8 | * @title TestProxyImplementationV0 9 | * @dev Version 0 of a generic contract to show upgradeability. 10 | */ 11 | contract TestProxyImplementationV0 is Ownable { 12 | 13 | using SafeMath for uint256; 14 | 15 | uint256 internal _intValue; 16 | bool internal _initialized; 17 | 18 | function initialize(address owner) public { 19 | require(!_initialized); 20 | setOwner(owner); 21 | _initialized = true; 22 | } 23 | 24 | function getIntValue() public view returns (uint256) { 25 | return _intValue; 26 | } 27 | 28 | function setIntValue(uint256 value) public onlyOwner returns (uint256) { 29 | _intValue = value; 30 | return _intValue; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/stubs/TestProxyImplementationV1.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import "./TestProxyImplementationV0.sol"; 4 | 5 | 6 | /** 7 | * @title TestProxyImplementationV1 8 | * @dev Version 1 of a generic contract to show upgradeability. 9 | */ 10 | contract TestProxyImplementationV1 is TestProxyImplementationV0 { 11 | 12 | function addIntValue(uint256 value) public onlyOwner returns (uint256) { 13 | _intValue = _intValue.add(value); 14 | return _intValue; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | // Allows us to use ES6 in our migrations and tests. 2 | require('babel-register') 3 | 4 | module.exports = { 5 | networks: { 6 | // local testing with ganache-cli 7 | ganache: { 8 | host: 'localhost', 9 | port: 8545, 10 | gas: 3141592, 11 | gasPrice: 100000000000, 12 | network_id: '*', // Match any network id 13 | from: '0x48089757dbc23bd8e49436247c9966ff15802978' 14 | }, 15 | // local or CI integration testing with a privatenet node 16 | // we need to separate this from the development network due to a bug with ganache-cli 17 | // https://github.com/trufflesuite/ganache-cli/issues/405 18 | // It currently doesn't support unlocking accounts properly 19 | integration: { 20 | host: 'localhost', 21 | port: 8545, 22 | gas: 3141592, 23 | gasPrice: 100000000000, 24 | network_id: '*', // Match any network id 25 | from: '0x48089757dbc23bd8e49436247c9966ff15802978', 26 | password: () => process.env.ACCOUNT_PASSWORD 27 | }, 28 | tunnel: { 29 | host: 'localhost', 30 | port: 9545, 31 | gas: 3141592, 32 | network_id: '*', // Match any network id 33 | from: '0x48089757dbc23bd8e49436247c9966ff15802978', 34 | password: () => process.env.ACCOUNT_PASSWORD 35 | }, 36 | } 37 | }; 38 | 39 | if (!!process.env.GAS_USAGE) { 40 | module.exports.mocha = { 41 | reporter: 'eth-gas-reporter', 42 | reporterOptions: { 43 | currency: 'USD' 44 | } 45 | }; 46 | } 47 | --------------------------------------------------------------------------------