UNPKG

@windingtree/wt-contracts

Version:

Smart contracts of the Winding Tree platform

517 lines (411 loc) 24.9 kB
[![Build Status](https://travis-ci.org/windingtree/wt-contracts.svg?branch=master)](https://travis-ci.org/windingtree/wt-contracts) [![Coverage Status](https://coveralls.io/repos/github/windingtree/wt-contracts/badge.svg?branch=master)](https://coveralls.io/github/windingtree/wt-contracts?branch=master&v=2.0) [![Greenkeeper badge](https://badges.greenkeeper.io/windingtree/wt-contracts.svg)](https://greenkeeper.io/) # WT Smart Contracts Smart contracts of the Winding Tree platform. ## Documentation ![contracts-schema.png](./assets/contracts-schema.png) Generated documentation is in the [`docs`](https://github.com/windingtree/wt-contracts/tree/master/docs) folder and can be generated by running `npm run soldoc`. There are two main groups of users in the Winding Tree platform - content producers (e. g. Hotels, Airlines) and content consumers (e. g. OTAs (Online Travel Agencies)). ### Content producers When a producer wants to participate, they have to do the following: 1. Locate Winding Tree Entrypoint address 1. Prepare off-chain data conforming to the [specification](https://github.com/windingtree/wt-organization-schemas). This specification is conformed with [Decentralized Identifiers (DIDs) standard](https://w3c.github.io/did-core/) 1. Create their organization smart contract (commonly referred to as 0xORG) 1. Fully custom 1. Create an implementation of `OrganizationInterface` smart contract. 1. Deploy the custom implementation. 1. Using forked version of the `Organization` smart contract 1. Fork current repository and deploy your own Organization 1. Use the URI of off-chain data and a keccak256 hash of its contents as initialization parameters 1. As an Organization owner you can create subsidiaries organizations by calling `createSubsidiary` method 2. Locate the appropriate Segment Directory address 3. Add their newly created organization of subsidiaries to the segment directory by calling the `add` method ### Content consumers When a consumer wants to participate, they have to do the following: 1. Locate Winding Tree Entrypoint address 1. Locate the appropriate Segment Directory address 1. Call `getOrganizations` on the Segment Directory. 1. Call `getOrgJsonUri` on every non-zero address returned as an instance of `OrganizationInterface` and crawl the off-chain data for more information. 1. Call `getOrgJsonHash` on every non-zero address returned as an instance of `OrganizationInterface` and verify that the current off-chain data contents hash matches the hash published in the smart contract. If a signed message occurs somewhere in the platform, a content consumer might want to decide if it was signed by an account associated with the declared Organization. That's when they would first verify the signature and obtain an address of the signer. In the next step, they have to verify that the actual signer is registered as an `associatedKey` with the Organization by checking its smart contract. ### Working with content hashes In order to reduce the attack surface, we require a hash of the off-chain stored data. We assume that it will not change very frequently, so updating the hash every-so-often won't add a significant cost to the whole operation. So, how does the hash actually look like? It is a `keccak256` (an Ethereum flavour of `sha3`) of the stringified ORG.JSON. You can produce `keccak256` hashes in a myriad of other tools, such as [this one](https://emn178.github.io/online-tools/keccak_256.html). Also, you can use [our own CLI tools](./management/tools/README.md) to generate a hash. Here the example of the command that generates a hash of given json file: ```bash ./management/tools/index.js --network development cmd=makehash file=./relative/path/to/file.json ``` Execution of the command results with: ```bash WindingTree Command Line Interface Version: 0.9.0 ================================================================================ ORG.JSON JSON hash DID: did:orgid:0xA0B74BFE28223c9e08d6DBFa74B5bf4Da763f959 Sha3 Hash: 0x91d6fc816cffa960aeb3a610607e37ed735f05718b9a72d1c0223396dab50626 ``` ## Requirements Node 10 is required for running the tests and contract compilation. ## Installation ```sh npm install @windingtree/wt-contracts ``` ```js import Organization from '@windingtree/wt-contracts/build/contracts/Organization.json'; // or import { OrganizationInterface, AbstractSegmentDirectory } from '@windingtree/wt-contracts'; ``` ## Development ```sh git clone https://github.com/windingtree/wt-contracts nvm install npm install npm test ``` You can run a specific test with `npm test -- test/segment-directory.js` or you can generate a coverage report with `npm run coverage`. Project configuration file is placed here: [.openzeppelin/project.json](.openzeppelin/project.json) ### Flattener A flattener script is also available. `npm run flattener` command will create a flattened version without imports - one file per contract. This is needed if you plan to use tools like [etherscan verifier](https://etherscan.io/verifyContract) or [securify.ch](https://securify.ch/). ## Deployment We are recommending to manage the deployment and upgrades of contracts using our CLI tools. These tools allowing to manage each contract as a separate upgradeable project. More about WindingTree CLI tools you can [read here](./management/tools/README.md). In order to interact with "real" networks such as `mainnet`, `ropsten` or others, you need to setup a `keys.json` file used by [truffle](https://truffleframework.com/) that does the heavy lifting for openzeppelin. ```json { "mnemonic": "<SEED_PHRASE>", "infura_projectid": "<PROJECT_ID>" } ``` ### Upgradeability FAQ **What does upgradeability mean?** We can update the logic of Entrypoint, Segment Directory or Organization while keeping their public address the same and **without touching any data**. **Who is the proxy admin on mainnet?** The proxies are administered by a 2/5 multisignature wallet, the ENS address is [proxyowner.windingtree.eth](https://etherscan.io/enslookup?q=proxyowner.windingtree.eth). **Who is the owner wt contracts deployed on mainnet?** The WindingTreeEntrypoint, OrganizationFactory and Segments are owned by a 3/5 multisignature wallet, the ENS address is [windingtree.eth](https://etherscan.io/enslookup?q=windingtree.eth). **Can you change the Organization data structure?** The Organization owner can, yes. As long as we adhere to [openzeppelin recommendations](https://docs.openzeppelin.com/sdk/2.5/writing-contracts), it should be safe. The same applies for Segment Directory and Entrypoint **Can I switch to the new Organization version?** If you deployed the (upgradeable) Organization yourself you can do it yourself. If you used a non-upgradeable smart contract implementation, then no. **Why do I keep getting "revert Cannot call fallback function from the proxy admin" when interacting with Organization?** This is a documented behaviour of [openzeppelin upgradeability](https://docs.openzeppelin.com/sdk/2.5/faq#why-are-my-getting-the-error-cannot-call-fallback-function-from-the-proxy-admin). You need to call the proxied Organization contract from a different account than is the proxy owner. **What happens when you upgrade a Segment Directory?** The Directory address stays the same, the client software has to interact with the Directory only with the updated ABI which is distributed via NPM (under the new version number). No data is lost. **How do I work with different organization versions on the client?** That should be possible by using an ABI of `OrganizationInterface` on the client side. ### Organizations hierarchy Organizations can have subsidiaries. The subsidiary is like a regular organization but linked with a parent organization and can be managed by a special role - `entityDirector`. This director can use the following methods: `changeOrgJsonUri`, `changeOrgJsonHash`, `changeOrgJsonUriAndHash`, `addAssociatedKey`, `removeAssociatedKey` but cannot transfer organization ownership. Organization ownership transferring, enabling or disabling subsidiaries, changing of the subsidiary director are available only for a parent organization owner (via parent organization interface). For the creation of a new subsidiary owner of the organization (or its entity director) should use the following functions of `Organization` contract: - `createSubsidiary` with parameters: `orgJsonUri` (string type), `orgJsonHash` (bytes32 type), `subsidiaryDirector` (address type). - `createSubsidiaryAndAddToDirectory` with parameters: `orgJsonUri` (string type), `orgJsonHash` (bytes32 type), `subsidiaryDirector` (address type), `directory` (address type). This function also will add a new subsidiary organization to the provided directory. As a result of the function execution, will be obtained an address of the subsidiary organization. The director of the subsidiary has to confirm his ownership using the function `confirmSubsidiaryDirectorOwnership` with parameter `subsidiaryAddress` (address type). Subsidiaries, where the director does not confirm his ownership rights, are not shown in the public list of subsidiaries that available via getter `getSubsisiaries`. For the change of the subsidiary status (enabled/disabled) owner of the parent organization can use the function: - `toggleSubsidiary` with parameter `subsidiaryAddress` (address type). Disabled subsidiaries are not shown in the public subsidiaries list available via getter `getSubsidiaries`. For getting the address of the parent organization can be used public getter `parentEntity`. For getting the account address of the director can be used public getter `entityDirector`. For getting current information about the subsidiary can be used function `getSubsidiary` with parameter `subsidiaryAddress` (address type). This function will return the following options: `id` - subsidiary address (address type), `state` - subsidiary state (boolean type), `confirmed` - director ownership confirmation status, `director` - account address of the director. ### Contracts deployment and upgrade process - All contracts in this repository can be managed as separate upgradability projects (recommended). For these purposes, you can use `OpenZeppelin CLI` or our simplified tool `WindingRree CLI` that can be found in the folder `./management/tools`. - All initial contracts deployments are will be based on the version that pointed in the `package.json` file. - After an initial deployment project configuration file will be created in the folder `./openzeppelin` automatically. Name of this file should have the following format `[network_Type]-[Contract_Name].json` (e.g. `private-Contract.json`). The local development network type is named as `private`, all other networks will have its own regular names like `ropsten`, `rinkeby` or `mainnet`. > Here the example of the project configuration: ```json { "version": "0.9.0", "contract": { "name": "Organization", "implementation": "0x630589690929E9cdEFDeF0734717a9eF3Ec7Fcfe", "proxy": "0xDb56f2e9369E0D7bD191099125a3f6C370F8ed15" }, "owner": "0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1", "app": "0x0290FB167208Af455bB137780163b7B7a9a10C16", "proxyAdmin": "0x26b4AFb60d6C903165150C6F0AA14F8016bE4aec", "implementationDirectory": "0x67B5656d60a809915323Bf2C40A8bEF15A152e3e", "package": "0x9b1f7F645351AF3631a656421eD2e40f2802E6c0", "blockNumber": 18 } ``` - To instantiate new contract on the network you should run the following command: ```bash ./management/tools/index.js --network development cmd=contract name=Organization from=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1 initArgs=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1,https://gist.githubusercontent.com/[username]/3bde88a0e8248c73c68c1aed2ca4b9be/raw/c3b4ebfe4af22832fb468393032a416b2482e99a/ORG.ID,0x1fe120cfd8f0cf216189a07e1a25b7da38030986b849b79ed59c0036456561dd,[APP],[PROXY_ADMIN],0x0000000000000000000000000000000000000000,0x0000000000000000000000000000000000000000 ``` > Command parameters explanation: > - `--network development`: network from the `truffle.js` configuration file. Required > - `cmd-contract`: command type. This type is allowing making new deployments or upgrades. Type is required > - `from=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1`: transaction sender address. Required > - `initArgs=[ARG_1],[ARG_2],[ARG_3]`: initial deployment initializer arguments. Optional. initial deployment initializer arguments. Optional. Arguments can have, also templates like `[APP]` and `[PROXY_ADMIN]`. These templates will be replaced with their actual values during the deployment (or upgrade). The result of this command execution should looks like: ``` ================================================================================ WindingTree Command Line Interface Version: 0.9.0 // <-- tool version Contract name: Organization // <-- Contract name Actual version: 0.9.0 // <-- Current repository version Last known version: 0.9.0 // <-- Previouisly deployed version App address: 0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab Proxy admin: 0xD833215cBcc3f914bD1C9ece3EE7BF8B14f841bb Contract implementation: 0x9561C133DD8580860B6b7E504bC5Aa500f0f06a7 ================================================================================ New deployment Contract proxy: 0x59d3631c86BbE35EF041872d502F218A39FBa150 ``` - Contract upgrade > CLI tool will detect upgrade need automatically from the difference of previous and current version number. ```bash ./management/tools/index.js --network development cmd=contract name=Organization from=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1 upgradeProxies=0x7e664541678C4997aD9dBDb9978C6E2B5A9445bE,0x9561C133DD8580860B6b7E504bC5Aa500f0f06a7 ``` > `upgradeProxies=[PROXY_ADDRESS],[PROXY_ADDRESS][...]`: optional parameter. If you want to upgrade your subsidiary organizations then using `upgradeProxies` parameter you should list their address > If your upgraded contract requiring running of its own initialization method then you can define it using parameter `upgradeMethod` and its arguments can be listed as `upgradeArgs` ## Local testing You need to run `npm run dev-net` and you will have an output of your addresses and private keys ready to use like this: ``` Available Accounts ================== (0) 0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1 (~100 ETH) (1) 0xffcf8fdee72ac11b5c542428b35eef5769c409f0 (~100 ETH) (2) 0x22d491bde2303f2f43325b2108d26f1eaba1e32b (~100 ETH) (3) 0xe11ba2b4d45eaed5996cd0823791e0c93114882d (~100 ETH) (4) 0xd03ea8624c8c5987235048901fb614fdca89b117 (~100 ETH) (5) 0x95ced938f7991cd0dfcb48f0a06a40fa1af46ebc (~100 ETH) (6) 0x3e5e9111ae8eb78fe1cc3bb8915d5d461f3ef9a9 (~100 ETH) (7) 0x28a8746e75304c0780e011bed21c72cd78cd535e (~100 ETH) (8) 0xaca94ef8bd5ffee41947b4585a84bda5a3d3da6e (~100 ETH) (9) 0x1df62f291b2e969fb0849d99d9ce41e2f137006e (~100 ETH) ``` > You can instantiate your own local `ganache-cli` configuration. We will use accounts shown above just for example purposes, you will need to use your owns. - Deployment of the `WindingTreeEntrypoint` ```bash ./management/tools/index.js --network development cmd=contract name=WindingTreeEntrypoint from=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1 initArgs=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1,0x0000000000000000000000000000000000000000 ``` Results with: ``` ================================================================================ WindingTree Command Line Interface Version: 0.9.0 Contract name: WindingTreeEntrypoint Actual version: 0.9.0 Last known version: 0.9.0 App address: 0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab Proxy admin: 0xD833215cBcc3f914bD1C9ece3EE7BF8B14f841bb Contract implementation: 0x9561C133DD8580860B6b7E504bC5Aa500f0f06a7 ================================================================================ New deployment Contract proxy: 0x59d3631c86BbE35EF041872d502F218A39FBa150 ``` - Deployment of the `SegmentDirectory` ```bash ./management/tools/index.js --network development cmd=contract name=SegmentDirectory from=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1 initArgs=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1,hotels,0x0000000000000000000000000000000000000000 ``` Results with: ``` ================================================================================ WindingTree Command Line Interface Version: 0.9.0 Contract name: SegmentDirectory Actual version: 0.9.0 Last known version: 0.9.0 App address: 0x0290FB167208Af455bB137780163b7B7a9a10C16 Proxy admin: 0x26b4AFb60d6C903165150C6F0AA14F8016bE4aec Contract implementation: 0x630589690929E9cdEFDeF0734717a9eF3Ec7Fcfe ================================================================================ New deployment Contract proxy: 0xDb56f2e9369E0D7bD191099125a3f6C370F8ed15 ``` - Adding of new directory to the EntryPoint ```bash ./management/tools/index.js --network development cmd=tx name=WindingTreeEntrypoint address=0x59d3631c86BbE35EF041872d502F218A39FBa150 from=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1 method='setSegment(string,address)' args=hotels,0xDb56f2e9369E0D7bD191099125a3f6C370F8ed15 ``` Results with: ``` ================================================================================ WindingTree Command Line Interface Version: 0.9.0 ================================================================================ Sending a transaction to contract Contract name: WindingTreeEntrypoint Method: setSegment(string,address) Arguments: hotels,0xDb56f2e9369E0D7bD191099125a3f6C370F8ed15 Result: { transactionHash: ..., events: { SegmentSet: { logIndex: '0', transactionIndex: '0', transactionHash: '0xb4f11f24e2614699bf540ed264c6c175293a61ef31c5e4627f9f94a0e73e49fb', blockHash: '0xe7f4ab504fbcca80ba0548920cf2956fabcee56f7dd487d2b5ad085163056c8c', blockNumber: '19', address: '0x59d3631c86BbE35EF041872d502F218A39FBa150', type: 'mined', id: 'log_71b8a880', returnValues: [Result], event: 'SegmentSet', signature: '0x1e5616724a154b534b96005ebef3069bc0b088cacf14dd358fab72fd52604a42', raw: [Object] } } } ``` > `tx` command type properties: > - `cmd=tx` command type > - `name=WindingTreeEntrypoint` name of the contract > - `address=0x59d3631c86BbE35EF041872d502F218A39FBa150` address of the contract on the network > - `method='setSegment(string,address)'` name of the method to send transaction > - `args=hotels,0xDb56f2e9369E0D7bD191099125a3f6C370F8ed15` - Deployment of the Organization ```bash ./management/tools/index.js --network development cmd=contract name=Organization from=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1 initArgs=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1,https://gist.githubusercontent.com/[username]/3bde88a0e8248c73c68c1aed2ca4b9be/raw/c3b4ebfe4af22832fb468393032a416b2482e99a/ORG.ID,0x1fe120cfd8f0cf216189a07e1a25b7da38030986b849b79ed59c0036456561dd,[APP],[PROXY_ADMIN],0x0000000000000000000000000000000000000000,0x0000000000000000000000000000000000000000 ``` Results with: ``` ================================================================================ WindingTree Command Line Interface Version: 0.9.0 Contract name: Organization Actual version: 0.9.0 Last known version: 0.9.0 App address: 0x6eD79Aa1c71FD7BdBC515EfdA3Bd4e26394435cC Proxy admin: 0xD86C8F0327494034F60e25074420BcCF560D5610 Contract implementation: 0x4bf749ec68270027C5910220CEAB30Cc284c7BA2 ================================================================================ New deployment Contract proxy: 0x7C728214be9A0049e6a86f2137ec61030D0AA964 ``` > Properties of this command has been described above in the previous chapter. - Creation of the subsidiary organization ```bash ./management/tools/index.js --network development cmd=tx name=Organization address=0x7C728214be9A0049e6a86f2137ec61030D0AA964 from=0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1 method='createSubsidiary(string,bytes32,address,string,string)' args=https://gist.githubusercontent.com/[username]/path/to/your/gist/with/ORG.ID,0x1fe120cfd8f0cf216189a07e1a25b7da38030986b849b79ed59c0036456561dd,0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1,'','' ``` Results with: ``` ================================================================================ WindingTree Command Line Interface Version: 0.9.0 ================================================================================ Sending a transaction to contract Contract name: Organization Method: createSubsidiary(string,bytes32,address,string,string) Arguments: https://gist.githubusercontent.com/kostysh/3b680e83da367b68c6e84407e5f2d44/raw/569ce8f321499a8249bec31fd09f6c618bcf52cd/Subsidiary%2520ORG.ID,0x1fe120cfd8f0cf216189a07e1a25b7da38030986b849b79ed59c0036456561dd,0xA0B74BFE28223c9e08d6DBFa74B5bf4Da763f959,, Result: { blockHash: ..., transactionHash: '0xa9ee7cb3e3b190bc82a3e0a386c74fbc92ed5f1b7f876964c317d67c37fc29e6', transactionIndex: '52', events: { '0': { address: '0xcfF7EE0f5F28C71c3941C53AcbfDE37c3BC0DD08', blockHash: '0xcb09f60c66a75a8f2e752c29d7abf128d20f6d62b6c8d3f40243bb0dfab94d9f', blockNumber: '7296804', logIndex: '15', removed: false, transactionHash: '0xa9ee7cb3e3b190bc82a3e0a386c74fbc92ed5f1b7f876964c317d67c37fc29e6', transactionIndex: '52', id: 'log_33b90ab0', returnValues: Result {}, event: undefined, signature: null, raw: [Object] }, OwnershipTransferred: { address: '0xDa3D89fda52DE133DBbcC06d71E05Af75b8fcE52', blockHash: '0xcb09f60c66a75a8f2e752c29d7abf128d20f6d62b6c8d3f40243bb0dfab94d9f', blockNumber: '7296804', logIndex: '14', removed: false, transactionHash: '0xa9ee7cb3e3b190bc82a3e0a386c74fbc92ed5f1b7f876964c317d67c37fc29e6', transactionIndex: '52', id: 'log_853292da', returnValues: [Result], event: 'OwnershipTransferred', signature: '0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0', raw: [Object] }, SubsidiaryCreated: { address: '0xd75C6F89C0d2B5808C720eF4078c5b62E804A50e', blockHash: '0xcb09f60c66a75a8f2e752c29d7abf128d20f6d62b6c8d3f40243bb0dfab94d9f', blockNumber: '7296804', logIndex: '16', removed: false, transactionHash: '0xa9ee7cb3e3b190bc82a3e0a386c74fbc92ed5f1b7f876964c317d67c37fc29e6', transactionIndex: '52', id: 'log_db922705', returnValues: [Result], event: 'SubsidiaryCreated', signature: '0x1d19959a9df12178565ee79ed0f54483da27fb2be079d78a0728f5ac11c06795', raw: [Object] }, SubsidiaryDirectorOwnershipConfirmed: { address: '0xd75C6F89C0d2B5808C720eF4078c5b62E804A50e', blockHash: '0xcb09f60c66a75a8f2e752c29d7abf128d20f6d62b6c8d3f40243bb0dfab94d9f', blockNumber: '7296804', logIndex: '17', removed: false, transactionHash: '0xa9ee7cb3e3b190bc82a3e0a386c74fbc92ed5f1b7f876964c317d67c37fc29e6', transactionIndex: '52', id: 'log_c0fe4c45', returnValues: [Result], event: 'SubsidiaryDirectorOwnershipConfirmed', signature: '0xcaea9a4e47c93c447fb88037cab74d00f477961a7231c8ae7a94881b661929f5', raw: [Object] } } } ``` - Creation of ORG.ID JSON hash > Examples of ORG.ID JSON files can be found in the directory `./assets` ```bash ./management/tools/index.js --network development cmd=makehash file=./assets/orgid-unit.json ``` Results with: ``` ================================================================================ WindingTree Command Line Interface Version: 0.9.0 ================================================================================ ORG.JSON JSON hash DID: did:orgid:0xA0B74BFE28223c9e08d6DBFa74B5bf4Da763f959 Sha3 Hash: 0x91d6fc816cffa960aeb3a610607e37ed735f05718b9a72d1c0223396dab50626 ``` > You should generate (and update in the contract) ORG.ID hash every time you change json file content - Getting the list of subsidiaries ```bash ./management/tools/index.js --network development cmd=call name=Organization address=0x7C728214be9A0049e6a86f2137ec61030D0AA964 method='getSubsidiaries()' ``` Results with: ``` ================================================================================ WindingTree Command Line Interface Version: 0.9.0 ================================================================================ Contract method call Contract name: Organization Method: getSubsidiaries() Arguments: [] Result: [ '0x067805E69e62E8bE56e8D13f4EBf53372D3dD02e' ] ```