@windingtree/wt-contracts

Smart contracts of the Winding Tree platform

Usage no npm install needed!

<script type="module">
  import windingtreeWtContracts from 'https://cdn.skypack.dev/@windingtree/wt-contracts';
</script>

README

Build Status Coverage Status Greenkeeper badge

WT Smart Contracts

Smart contracts of the Winding Tree platform.

Documentation

contracts-schema.png

Generated documentation is in the 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
  2. Prepare off-chain data conforming to the specification. This specification is conformed with Decentralized Identifiers (DIDs) standard
  3. Create their organization smart contract (commonly referred to as 0xORG)
    1. Fully custom
      1. Create an implementation of OrganizationInterface smart contract.
      2. Deploy the custom implementation.
    2. Using forked version of the Organization smart contract
      1. Fork current repository and deploy your own Organization
      2. Use the URI of off-chain data and a keccak256 hash of its contents as initialization parameters
    3. As an Organization owner you can create subsidiaries organizations by calling createSubsidiary method
  4. Locate the appropriate Segment Directory address
  5. 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
  2. Locate the appropriate Segment Directory address
  3. Call getOrganizations on the Segment Directory.
  4. Call getOrgJsonUri on every non-zero address returned as an instance of OrganizationInterface and crawl the off-chain data for more information.
  5. 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.

Also, you can use our own CLI tools to generate a hash. Here the example of the command that generates a hash of given json file:

./management/tools/index.js --network development cmd=makehash file=./relative/path/to/file.json

Execution of the command results with:

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

npm install @windingtree/wt-contracts
import Organization from '@windingtree/wt-contracts/build/contracts/Organization.json';
// or
import { OrganizationInterface, AbstractSegmentDirectory } from '@windingtree/wt-contracts';

Development

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

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 or 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.

In order to interact with "real" networks such as mainnet, ropsten or others, you need to setup a keys.json file used by truffle that does the heavy lifting for openzeppelin.

{
  "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.

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.

Can you change the Organization data structure?

The Organization owner can, yes. As long as we adhere to openzeppelin recommendations, 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. 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:

{
  "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:
./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.

./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
./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
./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
./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
./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
./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

./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
./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' ]