Skip to content

Commit

Permalink
Merge pull request #360 from energywebfoundation/feat/new-token-struc…
Browse files Browse the repository at this point in the history
…ture

New token structure
  • Loading branch information
josipbagaric authored Jan 20, 2020
2 parents c652c07 + f5caf3a commit e36a5a3
Show file tree
Hide file tree
Showing 49 changed files with 2,942 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 8. Migrate certificate structure to conform to ERC-1888

Date: 2020-01-17

## Status

Accepted

## Context

Our current certificate structure is based on ERC-721 non-fungible tokens. This presents an issue when a part of a certificate's volume has to be transferred to another owner.
In cases like these, we currently "split" the certificate into 2 smaller certificates, and then transfer one of the certificates to the new owner, and leave the original certificate to the original owner - deprecating the old certificate.
This approach is not ideal, so we started looking into better ways of changing owners for smaller parts of the certificates.

## Decision

We decided to use the [ERC-1888](https://github.com/ethereum/EIPs/issues/1888) Certificate structure so that we can comply and work on standardizing Certificates.

## Consequences

We would no longer need to split certificates and lose the certificate history whenever we want to transfer a part of the certificate to a new owner.
3 changes: 3 additions & 0 deletions packages/issuer/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
WEB3="http://localhost:8560"
DEPLOY_KEY="d9066ff9f753a1898709b568119055660a77d9aae4d7a4ad677b8fb3d2a571e5"
BACKEND_URL="http://localhost:3040"
7 changes: 7 additions & 0 deletions packages/issuer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
node_modules
dist
.vscode
build
db.sqlite
.openzeppelin/.session
206 changes: 206 additions & 0 deletions packages/issuer/README mermaid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# Issuer

## Registry

`Registry.sol` is ERC 1888 compatible registry for certificates issued by various issuers over various topics.

## Issuers

1) `PrivateIssuer.sol` is an implementation of privacy focused issuer which hides the volume for newly created certificates until the `ERC1888` claiming event.

Migration process equals to a swap from private certificate to public certificate and is (currently) a one-way process. For certificate owner

Issuer uses specific topic to issue:
- private certificates
- public certificates (used when migrating from private to public certificate)

2) `PublicIssuer.sol` is an implementation of a public I-REC compliant issuer

### Recipes

1) Private issuance and private trading
- issue using PrivateIssuer.requestIssue / PrivateIssuer.approveIssue
- transfer using PrivateIssuer.privateTransfer

2) Private issuance and public trading
- issue using PrivateIssuer.requestIssue / PrivateIssuer.approveIssue
- migrate to public using PrivateIssuer.migrateToPublic
- transfer / trade public volumes

3) Public issuance and private trading
- issue using PublicIssuer
- deposit volume to PrivateIssuer
- transfer using PrivateIssuer.privateTransfer

### Technical documentation

1) Private requesting and issuance

```mermaid
graph TD;
A(Device Owner) -->|1. Request Issue| P(PrivateIssuer.sol)
I(I-REC) -->|2. Approve Issue| P
P-->|3. Issue with private topic| R(Registry.sol)
```

```mermaid
sequenceDiagram
participant U as Device Owner
participant IS as Issuer
participant I as Issuer Contract
participant R as Registry Contract
participant A as I-REC API
participant DB as Database
U->>+I: encodeIssue(from, to, deviceId)
I-->>-U: (bytes)data
U->>+I: requestIssue(data)
I-->-U: emit IssueRequest(msg.sender, id)
IS->>+I: getRequest(id)
I-->-IS: RequestIssue
IS->>+A: validate(relevant request data)
A-->-IS: (boolean)result
IS->>+IS: createProof(to, balance)
IS->>+DB: storeProof()
DB-->-IS: ok
IS->>+I: approveIssue(to, requestId, commitment, validityData)
Note over IS,I: commitment = rootHash of the proof
I->>+R: issue(to, validityData, privateTopic,0, data)
R-->-I: (uint)id
I-->-IS: emit IssueSingle, emit CommitmentUpdated
```

2) Migrating certificate to public certificate

```mermaid
graph TD;
A(Certificate Owner) -->|1. Request Migration| P(PrivateIssuer.sol)
I(I-REC) -->|2. Approve Migration| P
P-->|3. Was already migrated?|P
P-->|4. NO: Request issuance| PUB(PublicIssuer.sol)
P-->|5. YES: Request minting| PUB
PUB-->|6. Issue or mint|R(Registry)
```

```mermaid
sequenceDiagram
participant U as Device/Certificate Owner
participant IS as Issuer
participant PUB as Public Issuer Contract
participant I as Private Issuer Contract
participant R as Registry Contract
participant A as I-REC API
participant DB as Database
U->>+IS: requestProofForBalance()
IS-->>-U: (address, value, salt, proof)
U->>U: hash = sha3(address, value, salt)
U->>+I: requestMigrateToPublic(id, hash)
I-->-U: emit MigrateToPublicRequest(msg.sender, id)
IS->>+I: getRequestMigrateToPublic(id)
I-->-IS: RequestStateChange
IS->>+I: migrateToPublic(id, value, salt, proof, newCommitment)
Note over IS,I: newCommitment = private balance update
I->>+PUB: requestIssueFor(request.owner, data)
PUB->>-I: returns requestId
I->>+PUB: approveIssue()
PUB->>-I: returns id
I-->-IS: emit PublicCertificateCreated(privateId, id)
```

Note: alternatively we define a request / approve migration from private to public. So instead of private issuer calling `mint or issue` we call public issuer "issue" function, this also means that public issuer becomes a issuer address for public certificates.

3) Claiming

Claiming is supported only by public issued certificates. Private certificates has to be migrated to public before being claimed.

```mermaid
graph TD;
A(Device Owner) -->|1. Claim| R(Registry)
```

4) Transferring from public to private

```mermaid
graph TD;
C(Certificate Owner)-->|1. 1155.Transfer| R(PrivateIssuer)
R --> |2. Update commitment| R
```

5) Migrating volume to public certificate

This is a case where private certificate was migrated to public, then part of the volume was again transferred to a private certificate.

An example is where buyer buys a public certificate but wishes to perform private trading activities before final migration to public and claiming

Note: Since `PrivateIssuer` is an owner of the token (from step 4) ) it can send publicly the request part to the requesting user. Total amount of tokens locked to smart contract won't change.

```mermaid
graph TD;
A(Certificate Owner) -->|1. Request Migration| P(PrivateIssuer)
I(I-REC) -->|2. Approve Migration| P
P-->|3. Transfer from P to requesting address| R(Registry)
P-->|4. Update commitment|P
```

Note: alternatively we can implement it as burn/mint flow. Tokens deposited to PrivateIssuer contract will be burnt immediately. Then transfer from private to public will use `Migrating certificate to public certificate` flow.

6) Private transfers

This is a case where volume can be transferred privately inside the private registry.

As an example, this can be used to transfer given volume to exchange or other account.

```mermaid
graph TD;
A(Certificate Owner) -->|1. Request private transfer| X(API)
A(Certificate Owner) -->|2. Request Private transfer| P(PrivateIssuer)
I(I-REC) -->|2. Approve| P
P-->|3. Validate new commitment| P
P-->|4. Update commitment|P
```

```mermaid
sequenceDiagram
participant U as Device/Certificate Owner
participant IS as Issuer
participant I as Issuer Contract
participant R as Registry Contract
participant A as I-REC API
participant DB as Database
U->>+IS: requestPrivateTransfer(id, value, newOwner)
IS->>DB: store updated proof tree, linked to requested
IS-->>-U: (address, value, salt, proof)
Note over IS,U: address = sender address, value = remaining balance
U->>U: hash = sha3(address, value, salt)
U->>+I: requestPrivateTransfer(id, hash)
I-->-U: emit PrivateTransferRequest(msg.sender, id)
IS->>+I: getRequestPrivateTransfer(id)
I-->-IS: RequestStateChange
IS->>+I: privateTransfer(requestId, proof, prevCommitment, newCommitment)
Note over IS,I: newCommitment = private balance update
I->>I: updateCommitment(id, prevCommitment, newCommitment)
I-->-IS: emit CommitmentUpdated
```

Notes:
`prevCommitment` is required to prevent state corruption, transition to new commitment based on other state that's currently on-chain will result in error.

Implementation:
- Certificate owner A has 1000kWh of energy on certificate id = 1 (C1)
- A requesting private transfer of 500kWh from C1 to B
- A calls API with (id, value, newOwner) in our case (1, 500000, B)
- if API approves the transfer (enough balance, maybe other API checks)
- API returns (updatedBalanceOfA, salt) in our case (500000, 'randomsalt')
- A creates onChain request where hash = hash(address, updatedBalanceOfA, salt) in our case hash(A, 5000000, salt)
- Issuer - approves by sending new commitment that is verified against the request.hash
88 changes: 88 additions & 0 deletions packages/issuer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Issuer

## Registry

`Registry.sol` is ERC 1888 compatible registry for certificates issued by various issuers over various topics.

## Issuers

1) `PrivateIssuer.sol` is an implementation of privacy focused issuer which hides the volume for newly created certificates until the `ERC1888` claiming event.

Migration process equals to a swap from private certificate to public certificate and is (currently) a one-way process. For certificate owner

Issuer uses specific topic to issue:
- private certificates
- public certificates (used when migrating from private to public certificate)

2) `PublicIssuer.sol` is an implementation of public I-REC compliant issuer

### Recipes

1) Private issuance and private trading
- issue using PrivateIssuer.requestIssue / PrivateIssuer.approveIssue
- transfer using PrivateIssuer.privateTransfer

2) Private issuance and public trading
- issue using PrivateIssuer.requestIssue / PrivateIssuer.approveIssue
- migrate to public using PrivateIssuer.migrateToPublic
- transfer / trade public volumes

3) Public issuance and private trading
- issue using PublicIssuer
- deposit volume to PrivateIssuer
- transfer using PrivateIssuer.privateTransfer

### Technical documentation

1) Private requesting and issuance

![Private request flow](docs/private_issuance_flow.png)
![Private request activity](docs/private_issuance_activity.png)

2) Migrating certificate to public certificate

![Migrate to public flow](docs/migrate_to_public_flow.png)
![Migrate to public activity](docs/migrate_to_public_activity.png)

3) Claiming

Claiming is supported only by public issued certificates. Private certificates has to be migrated to public before being claimed.

![Claim](docs/claim.png)

4) Transferring from public to private

![Public to private](docs/public_to_private.png)

5) Migrating volume to public certificate

This is a case where private certificate was migrated to public, then part of the volume was again transferred to a private certificate.

An example is where buyer buys a public certificate but wishes to perform private trading activities before final migration to public and claiming

Note: Since `PrivateIssuer` is an owner of the token (from step 4) ) it can send publicly the request part to the requesting user. Total amount of tokens locked to smart contract won't change.

![Transfer public to private](docs/transfer_to_public.png)

Note: alternatively we can implement it as burn/mint flow. Tokens deposited to PrivateIssuer contract will be burnt immediately. Then transfer from private to public will use `Migrating certificate to public certificate` flow.

6) Private transfers

This is a case where volume can be transferred privately inside the private registry.

As an example, this can be used to transfer given volume to exchange or other account.

![Private transfer flow](docs/private_transfer_flow.png)
![Private transfer activity](docs/private_transfer_activity.png)

Notes:
`prevCommitment` is required to prevent state corruption, transition to new commitment based on other state that's currently on-chain will result in error.

Implementation:
- Certificate owner A has 1000kWh of energy on certificate id = 1 (C1)
- A requesting private transfer of 500kWh from C1 to B
- A calls API with (id, value, newOwner) in our case (1, 500000, B)
- if API approves the transfer (enough balance, maybe other API checks)
- API returns (updatedBalanceOfA, salt) in our case (500000, 'randomsalt')
- A creates onChain request where hash = hash(address, updatedBalanceOfA, salt) in our case hash(A, 5000000, salt)
- Issuer - approves by sending new commitment that is verified against the request.hash
29 changes: 29 additions & 0 deletions packages/issuer/contracts/ERC1155/Address.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
pragma solidity ^0.5.0;


/**
* Utility library of inline functions on addresses
*/
library Address {

/**
* Returns whether the target address is a contract
* @dev This function will return false if invoked during the constructor of a contract,
* as the code is not actually created until after the constructor finishes.
* @param account address of the account to check
* @return whether the target address is a contract
*/
function isContract(address account) internal view returns (bool) {
uint256 size;
// XXX Currently there is no better way to check if there is a contract in an address
// than to check the size of the code at that address.
// See https://ethereum.stackexchange.com/a/14016/36603
// for more details about how this works.
// TODO Check this again before the Serenity release, because all addresses will be
// contracts then.
// solium-disable-next-line security/no-inline-assembly
assembly { size := extcodesize(account) }
return size > 0;
}

}
10 changes: 10 additions & 0 deletions packages/issuer/contracts/ERC1155/Common.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pragma solidity ^0.5.0;

/**
Note: Simple contract to use as base for const vals
*/
contract CommonConstants {

bytes4 constant internal ERC1155_ACCEPTED = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))
bytes4 constant internal ERC1155_BATCH_ACCEPTED = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))
}
Loading

0 comments on commit e36a5a3

Please sign in to comment.