Smart contracts of the Winding Tree platform.
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)).
When a producer wants to participate, they have to do the following:
- Locate Winding Tree Entrypoint address
- Prepare off-chain data conforming to the specification
- Create their organization smart contract (commonly referred to as 0xORG)
- Fully custom
- Create an implementation of
OrganizationInterface
smart contract. - Deploy the custom implementation.
- Create an implementation of
- Assisted
- Locate Organization Factory address from Winding Tree Entrypoint
- 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.
- Fully custom
- Locate the appropriate Segment Directory address
- 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.
When a consumer wants to participate, they have to do the following:
- Locate Winding Tree Entrypoint address
- Locate the appropriate Segment Directory address
- Call
getOrganizations
on the Segment Directory. - Call
getOrgJsonUri
on every non-zero address returned as an instance ofOrganizationInterface
and crawl the off-chain data for more information. - Call
getOrgJsonHash
on every non-zero address returned as an instance ofOrganizationInterface
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.
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": "[email protected]"
}
}
}`;
// 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.
Node 10 is required for running the tests and contract compilation.
npm install @windingtree/wt-contracts
import Organization from '@windingtree/wt-contracts/build/contracts/Organization.json';
// or
import { OrganizationInterface, AbstractSegmentDirectory } from '@windingtree/wt-contracts';
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
.
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.
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>"
}
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.
- Run
npm version
and release on NPM. This will also bump the version inzos.json
file. - Deploy upgraded contracts with
./node_modules/.bin/openzeppelin push --network development
(use the network which you need). TheOrganization
implementation used by the Factory is changed in this step. - 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 upgradeOrganizationFactory
as well. - Upgrade Organization contracts with
node management/upgrade-organizations.js
. Make sure that its setup in a proper way. You can check theOrganization
implementation address inzos.<network>.json
file. Also, use the account set as Organization Factory owner. Only that account can change Organizations' implementation.
-
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.
-
Start an openzeppelin session.
> ./node_modules/.bin/openzeppelin session --network development --from 0xbbc04b7c97846af2dac2d0c115f06d6cdab188d8 --expires 3600
-
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
-
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.