/wt-contracts

Smart contracts of the Winding Tree platfrom.

Primary LanguageJavaScriptGNU General Public License v3.0GPL-3.0

Build Status Coverage Status Greenkeeper badge

WT Smart Contracts

Smart contracts of the Winding Tree platform.

Documentation

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
  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. Assisted
      1. Locate Organization Factory address from Winding Tree Entrypoint
      2. Call create method on the Organization Factory with the URI of off-chain data and a keccak256 hash of its contents. Organization smart contract that belongs to the transaction sender is created.
  4. Locate the appropriate Segment Directory address
  5. Add their newly created 0xORG to the segment directory by calling the add method

The Organization created in OrganizationFactory uses the Upgradeability Proxy pattern. In short, the Factory owner will keep the ownership of the contract logic (proxy), whereas the transaction sender will keep the ownership of the data. Thus the Factory owner is responsible for the code. It is possible to transfer the proxy ownership to another account if need be.

In any case, every Organization can have many associated keys. An associated key is an Ethereum address registered in the Organization that can operate on behalf of the organization. That means that for example, the associated key can sign messages on behalf of the Organization. This is handy when providing guarantees, proving data integrity or disclosing identity.

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. Let's try an example:

const web3utils = require('web3-utils');
const stringOrgJsonContents = `{
  "dataFormatVersion": "0.2.3",
  "updatedAt": "2019-06-04T11:10:00.000Z",
  "legalEntity": {
    "name": "Acme Corp, Inc.",
    "address": {
      "road": "5th Avenue",
      "houseNumber": "123",
      "city": "New York",
      "countryCode": "US"
    },
    "contact": {
      "email": "ceo@acmecorpz.com"
    }
  }
}`;
// It is important to work with a textual ORG.JSON and *not* a JSON-parsed and re-serialized form.
// JSON serializers might be producing different outcomes which would result in different hashes.
const hashedOrgJson = web3utils.soliditySha3(stringOrgJsonContents);
console.log(`Put me into 0xORG: ${hashedOrgJson}`);

You can also produce keccak256 hashes in a myriad of other tools, such as this one.

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.

Warning: We are not using the zos.json in tests, rather zos.test.json. If you are getting the Cannot set a proxy implementation to a non-contract address error, its probably because the contract is not inzos.test.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 using the upgradeability proxy from openzeppelin and the deployment pipeline is using their system as well. You can read more about the publishing process and upgrading in openzeppelin documentation.

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 Factory owner can, yes. As long as we adhere to openzeppelin recommendations, it should be safe. The same applies for Segment Directory, Entrypoint and Factory.

Can I reclaim the proxy ownership of an Organization? If your Organization is created via the Factory, the proxy is owned by the Factory owner (i. e. only the owner can upgrade your Organization). You can, however, ask the owner to transfer the proxy admin to a different account by calling changeAdmin on the Organization itself. The new proxy admin can then upgrade the Organization implementation.

Can I switch to the new Organization version?

If you created your Organization via Organization Factory, no. The Organization Factory owner has to do that for you. If you deployed the (upgradeable) Organization yourself or reclaimed the proxy ownership from the Factory owner, 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.

Contract upgrade process

  1. Run npm version and release on NPM. This will also bump the version in zos.json file.
  2. Deploy upgraded contracts with ./node_modules/.bin/openzeppelin push --network development (use the network which you need). The Organization implementation used by the Factory is changed in this step.
  3. Upgrade contracts with ./node_modules/.bin/openzeppelin upgrade --network development (use the network which you need). This will interactively ask you which contracts to upgrade. If you have changed the interface of Organization, make sure to upgrade OrganizationFactory as well.
  4. Upgrade Organization contracts with node management/upgrade-organizations.js. Make sure that its setup in a proper way. You can check the Organization implementation address in zos.<network>.json file. Also, use the account set as Organization Factory owner. Only that account can change Organizations' implementation.

Local testing

  1. 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) 0xbbc04b7c97846af2dac2d0c115f06d6cdab188d8 (~100 ETH)
    (1) 0xeb3d7449df1453ac074492a9fc73f6aebdfe9b2f (~100 ETH)
    (2) 0x14f016e73a18c5a68c475d2dff17af38f85db6b7 (~100 ETH)
    (3) 0x3e083ef62949f90a6f5f46cd314797fed7fa9468 (~100 ETH)
    (4) 0x994d319557cd049b13de8b78c00d97c5aefec192 (~100 ETH)
    (5) 0x6c44a1706d7e4866fc5fbfc8e7547f0682aa8756 (~100 ETH)
    (6) 0x06a99bd405aec473af091454e93a48fa45d8df85 (~100 ETH)
    (7) 0x2e0bcd1841dafdc2f740a4dfcdbb882be21a383a (~100 ETH)
    (8) 0xb34287e5520e3ae9430c53b624830021eff110db (~100 ETH)
    (9) 0x9d112ca960b447d9046816505ca869e06708759b (~100 ETH)
    

    In this example we will use the 0 index account for proxy ownership, the 1 index account for contract ownership and the 2 index account for organization creation and ownership. The LifToken address will be 0x0, we dont need it to test locally.

  2. Start an openzeppelin session.

    > ./node_modules/.bin/openzeppelin session --network development --from 0xbbc04b7c97846af2dac2d0c115f06d6cdab188d8 --expires 3600
  3. Deploy your contracts. This only uploads the logic, the contracts are not meant to be directly interacted with.

    > ./node_modules/.bin/openzeppelin push --network development
  4. Create the proxy instances of deployed contracts you can interact with. The args attribute is passed to the initializer function. See documentation of the appropriate contracts for details. The openzeppelin app might differ for each deployment. You don't need a deployed Lif token to play with this locally. You can get the ZOS_APP_ADDRESS from the zos dev file inside the .openzeppelin (or root) folder.

    > ./node_modules/.bin/openzeppelin create OrganizationFactory --network development --init initialize --args 0xeb3d7449df1453ac074492a9fc73f6aebdfe9b2f,ZOS_APP_ADDRESS
    > ./node_modules/.bin/openzeppelin create WindingTreeEntrypoint --network development --init initialize --args 0xeb3d7449df1453ac074492a9fc73f6aebdfe9b2f,0x0000000000000000000000000000000000000000,ORG_FACTORY_PROXY_ADDRESS
    > ./node_modules/.bin/openzeppelin create SegmentDirectory --network development --init initialize --args 0xeb3d7449df1453ac074492a9fc73f6aebdfe9b2f,hotels,0x0000000000000000000000000000000000000000

    These commands will return a network address where you can actually interact with the contracts. For a quick test, you can use the openzeppelin sdk, you can get all addresses from the zos file that was created.

    > ./node_modules/.bin/openzeppelin send-tx --network development --to WINDINGTREEENTRYPOINT_PROXY_ADDRESS --method setSegment --args 'hotels',SEGMENTHOTEL_PROXY_ADDRESS --from 0xeb3d7449df1453ac074492a9fc73f6aebdfe9b2f
    > ./node_modules/.bin/openzeppelin send-tx --network development --to ORGFACTORY_PROXY_ADDRESS --method create --args 'https://windingtree.com','0xd1e15bcea4bbf5fa55e36bb5aa9ad5183a4acdc1b06a0f21f3dba8868dee2c99' --from 0x14f016e73a18c5a68c475d2dff17af38f85db6b7
    > ./node_modules/.bin/openzeppelin call --network development --to SEGMENTHOTEL_PROXY_ADDRESS --method getOrganizations
    > ./node_modules/.bin/openzeppelin call --network development --to ORGANIZATION_ADDRESS --method getOrgJsonUri
    > ./node_modules/.bin/openzeppelin call --network development --to ORGANIZATION_ADDRESS --method getOrgJsonUri

    With these commands we have deployed the Winding Tree core contracts, and register and organization in our local network.