Skip to content

Commit

Permalink
Update ERC-1900 after comment suggestions
Browse files Browse the repository at this point in the history
Updating ERC-1900 after suggestions from ethereum#1882 (comment):
- clearer explanation of what a type is and what it's composed of
- defining the purpose of the struct fields when they are first encountered
- mentioning type immutability and the possibility of removing the dType `remove` function
- adding more information about the `source` field
- mentioning human readable names as a primary identifier for types

Additionally:
- removed the TypeStorage contract description, postponing it for a future EIP, due to multiple storage patterns being researched
  • Loading branch information
loredanacirstea committed Jul 2, 2019
1 parent e1ceb1b commit 1adf3de
Showing 1 changed file with 132 additions and 105 deletions.
237 changes: 132 additions & 105 deletions EIPS/eip-1900.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,53 +27,90 @@ However, this is only the first phase. As defined in this document and in the fu

To summarize:

* We would like to have a good decentralized medium for integrating all Ethereum data, and relationships between the different types of data. Also a way to address the behavior related to each data type.
* Functional programming becomes easier. Functions like map, reduce, filter for data types are implemented by each type library.
* Solidity development tools could be transparently extended to include the created types (For example in IDEs like Remix). At a later point, Solidity itself can have native support for these types.
* We would like to have a good decentralized medium for integrating all Ethereum data, and relationships between the different types of data. Also, a way to address the behavior related to each data type.
* Functional programming becomes easier. Functions like `map`, `reduce`, `filter`, are implemented by each type library.
* Solidity development tools could be transparently extended to include the created types (For example in IDEs like Remix). At a later point, the EVM itself can have precompiled support for these types.
* The system can be easily extended to types pertaining to other languages. (With type definitions in the source (Swarm stored source code in the respective language))
* The dType database should be part of the System Registry for the Operating System of The World Computer


## Specification

The Type Registry can have a governance protocol for its CRUD operations. However, this and other permission guards are not covered in the current proposal.
The Type Registry can have a governance protocol for its CRUD operations. However, this, and other permission guards are not covered in this proposal.

### Type Definition
### Type Definition and Metadata

The dType registry should support the registration of Solidity's elementary and complex types. In addition, it should also support contract events definitions. In this EIP, the focus will be on describing the minimal on-chain type definition and metadata needed for registering Solidity user-defined types.

#### Type Definition: TypeLibrary

A type definition consists of a type library containing:
- the nominal `struct` used to define the type
- additional functions used to:
- check whether a given variable is an instance of the defined type
- enforce other type-specific rules
- provide HOFs such as `map`, `filter`, `reduce`
- provide type structuring and destructuring. This can be useful for low-level calls or assembly code, when importing contract interfaces is not an efficient option.

A simple example is:

```
enum TypeChoices {
BaseType,
PayableFunction,
StateFunction,
ViewFunction,
PureFunction,
Event
}
pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;
struct dTypes {
string name;
string label;
}
library myBalanceLib {
struct dType {
TypeChoices typeChoice;
address contractAddress;
bytes32 source;
string name;
dTypes[] types;
}
struct myBalance {
string accountName;
uint256 amount;
}
function structureBytes(bytes memory data) pure public returns(myBalance memory balance)
function destructureBytes(myBalance memory balance) pure public returns(bytes memory data)
function map(
address callbackAddr,
bytes4 callbackSig,
myBalance[] memory balanceArr
)
view
internal
returns (myBalance[] memory result)
}
```

For storing the actual types, we propose the following format, which allows additional metadata to be attached to the type:
Types can also use existing types in their composition. However, this will always result in a directed acyclic graph.

```
struct Type {
dType data;
uint256 index;
library myTokenLib {
using myBalanceLib for myBalanceLib.myBalance;
struct myToken {
address token;
myBalanceLib.myBalance;
}
}
```

For example, some standard types can be defined as:
#### Type Metadata: dType Registry

Type metadata will be registered on-chain, in the dType registry contract. This consists of:
- `name` - the type's name, as it would be used in Solidity; it can be stored as a `string` or encoded as `bytes`. The name can have a human-readable part and a version number.
- `typeChoice` - used for storing additional ABI data that differentiate how types are handled on and off chain. It is defined as an `enum` with the following options: `BaseType`, `PayableFunction`, `StateFunction`, `ViewFunction`, `PureFunction`, `Event`
- `contractAddress` - the Ethereum `address` of the `TypeRootContract`. For this proposal, we can consider the Type Library address as the `TypeRootContract`. Future EIPs will propose additional TypeStorage contracts that will modify the scope of `contractAddress`.
- `source` - a `bytes32` Swarm hash where the source code of the type library and contracts can be found; in future EIPs, where dType will be extended to support other languages (e.g. JavaScript, Rust), the file identified by the Swarm hash will contain the type definitions in that language.
- `types` - metadata for subtypes: the first depth level internal components. This is an array of objects (`structs`), with the following fields:
- `name` - the subtype name, of type `string`, similar to the above `name` definition
- `label` - the subtype label
- `dimensions` - `string[]` used for storing array dimensions. E.g.:
- `[]` -> `TypeA`
- `[""]` -> `TypeA[]`
- `["2"]` -> `TypeA[2]`
- `["",""]` -> `TypeA[][]`
- `["2","3"]` -> `TypeA[2][3]`

Examples of metadata, for simple, value types:
```
{
"contractAddress": "0x0000000000000000000000000000000000000000",
Expand All @@ -92,16 +129,16 @@ For example, some standard types can be defined as:
}
```

Composed types can be defines as:
Composed types can be defined as:
```
{
"contractAddress": "0x105631C6CdDBa84D12Fa916f0045B1F97eC9C268",
"typeChoice": 0,
"source": <a SWARM hash for source files>,
"source": <a SWARM hash for type source code files>,
"name": "myBalance",
"types": [
{"name": "string", "label": "accountName"},
{"name": "uint256", "label": "amount"}
{"name": "string", "label": "accountName", dimensions: []},
{"name": "uint256", "label": "amount", dimensions: []}
]
}
```
Expand All @@ -110,122 +147,112 @@ Composed types can be further composed:
```
{
"contractAddress": "0x91E3737f15e9b182EdD44D45d943cF248b3a3BF9",
"source": <a SWARM hash for source files>,
"source": <a SWARM hash for type source code files>,
"name": "myToken",
"types": [
{"name": "address", "label": "token"},
{"name": "myBalance", "label": "balance"}
{"name": "address", "label": "token", dimensions: []},
{"name": "myBalance", "label": "balance", dimensions: []}
]
}
```

That is: a `myToken` type will have the final data format: `(address,(string,uint256))` and a labeled format: `(address token, (string accountName, uint256 amount))`
`myToken` type will have the final data format: `(address,(string,uint256))` and a labeled format: `(address token, (string accountName, uint256 amount))`.

##### dType Registry Data Structures and Interface

### Type Registry Interface
To store this metadata, the dType registry will have the following data structures:

```
interface dType {
event LogNew(bytes32 indexed hash, uint256 indexed index);
event LogUpdate(bytes32 indexed hash, uint256 indexed index);
event LogRemove(bytes32 indexed hash, uint256 indexed index);
enum TypeChoices {
BaseType,
PayableFunction,
StateFunction,
ViewFunction,
PureFunction,
Event
}
function insert(dTypeLib.dType memory data) external returns (bytes32 dataHash)
struct dTypes {
string name;
string label;
string[] dimensions;
}
function remove(bytes32 typeHash) external returns(uint256 index)
struct dType {
TypeChoices typeChoice;
address contractAddress;
bytes32 source;
string name;
dTypes[] types;
}
function count() external view returns(uint256 counter)
```

function getTypeHash(string memory name) pure external returns (bytes32 typeHash)
For storage, we propose a pattern which isolates the type metadata from additional storage-specific data and allows CRUD operations on records. This pattern has been described in detail here: https://medium.com/robhitchens/solidity-crud-part-1-824ffa69509a.

function getByHash(bytes32 typeHash) view external returns(Type memory dtype)
```
// key: typeHash
mapping(bytes32 => Type) public typeStruct;
function get(string memory name) view external returns(Type memory dtype)
// array of typeHashes
bytes32[] public typeIndex;
function isType(bytes32 typeHash) view external returns(bool isIndeed)
struct Type {
dType data;
uint256 index;
}
```

### Type Libraries
Note that we are proposing to define the type's primary identifier, `typeHash`, as `keccak256(abi.encodePacked(name))`. If the system is extended to other programming languages, we can define `typeHash` as `keccak256(abi.encodePacked(language, name))`.
Initially, single word English names can be disallowed, avoiding name squatting.

Bellow is an example of a type library. It can also implement helper functions for structuring and destructuring data. Map, filter, reduce functions should also be included.

```
pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;
The dType registry interface is:

library myBalanceLib {
```
interface dType {
event LogNew(bytes32 indexed hash, uint256 indexed index);
event LogUpdate(bytes32 indexed hash, uint256 indexed index);
event LogRemove(bytes32 indexed hash, uint256 indexed index);
struct myBalance {
string accountName;
uint256 amount;
}
function insert(dTypeLib.dType memory data) external returns (bytes32 dataHash)
function structureBytes(bytes memory data) pure public returns(myBalance memory balance)
function remove(bytes32 typeHash) external returns(uint256 index)
function destructureBytes(myBalance memory balance) pure public returns(bytes memory data)
function count() external view returns(uint256 counter)
function map(
address callbackAddr,
bytes4 callbackSig,
myBalance[] memory balanceArr
)
view
internal
returns (myBalance[] memory result)
}
```
function getTypeHash(string memory name) pure external returns (bytes32 typeHash)
Types can also use existing types in their composition. However, this will always result in a directed acyclic graph.
function getByHash(bytes32 typeHash) view external returns(Type memory dtype)
```
library myTokenLib {
using myBalanceLib for myBalanceLib.myBalance;
function get(string memory name) view external returns(Type memory dtype)
struct myToken {
address token;
myBalanceLib.myBalance;
}
function isType(bytes32 typeHash) view external returns(bool isIndeed)
}
```

### Type Contract
**Notes:**

A type must implement a library and a contract that stores the Ethereum address of that library, along with other contract addresses related to that type (e.g. smart contract for storing the data entries for that type).
To ensure backward compatibility, we suggest that updating types should not be supported.

```
contract TypeRootContract {
address public libraryAddress;
address public storageAddress;
constructor(address _library, address _storage) public {
require(_library != address(0x0));
libraryAddress = _library;
storageAddress = _storage;
}
}
```
The `remove` function can also be removed from the interface, to ensure immutability. One reason for keeping it would be clearing up storage for types that are not in use or have been made obsolete. However, this can have undesired effects and should be accompanied by a solid permissions system, testing and governance process. This part will be updated when enough feedback has been received.

## Rationale

For now, the Type Registry stores the minimum amount of information for the types, needed to:
The Type Registry must store the minimum amount of information for rebuilding the type ABI definition. This allows us to:
* support on-chain interoperability
* decode blockchain side effects off-chain (useful for block explorers)
* allow off-chain tools to cache and search through the collection (e.g. editor plugin for writing typed smart contracts)

We suggest storing the following information for each type:
There is one advantage that has become clear with the emergence of global operating systems, like Ethereum: we can have a global type system through which the system’s parts can interoperate. Projects should agree on standardizing types and a type registry, continuously working on improving them, instead of creating encapsulated projects, each with their own types.

* `TypeChoices typeChoice` - defines whether the type is a base type, function or event
* `address contractAddress` - defines the Ethereum address of the `TypeRootContract`
* `bytes32 source` - defines a Swarm hash where the source code of the type library and contracts can be found
* `string name` - the name of the type; the packed `bytes` encoding format can also be taken into consideration
* `dTypes[] types` - the types of the first depth level internal components, with component type and label
The effort of having consensus on new types being added or removing unused ones is left to the governance system.

Note that we are proposing to define the `typeHash` as `keccak256(name)`. If the system is extended to other languages, we can define `typeHash` as `keccak256(language, name)`.
We also propose to not allow the update of existing types, in order to ensure backwards compatibility. The effort of having consensus on new types being added or removing unused ones is left to the governance system.
Initially, single word English names can be disallowed, to avoid name squatting.
After the basis of such a system is specified, we can move forward to building a static type checking system at compile time, based on the type definitions and rules stored in the dType registry.

We also recommend that the Type Library and Type Contract for a specific type should express the behavior pertinent strictly to the data structure. The additional behavior that is pertinent to the logic should later be added by external contract functions that use the respective type. This is an approach that will not pollute the data with behavior and allow for fine-grained upgrades by upgrading just the behavior/functions.
The Type Library must express the behavior strictly pertinent to its defined type. Storage patterns for type instances will be covered in a future ERC and must abide by the same rule. Additional behavior, required by various project's business logic can be added later, through libraries containing functions that handle the respective type. These can also be registered in dType, but will be detailed in a future ERC.

This is an approach that will separate definitions from stored data and behavior, allowing for easier and more secure fine-grained upgrades.

## Backwards Compatibility

Expand All @@ -237,7 +264,7 @@ Will be added.

## Implementation

In work implementation examples can be found at https://github.com/ctzurcanu/dType/tree/master/contracts/contracts.
An in-work implementation can be found at https://github.com/pipeos-one/dType/tree/master/contracts/contracts.
This proposal will be updated with an appropriate implementation when consensus is reached on the specifications.

A video demo of the current implementation (a more extended version of this proposal) can be seen at https://youtu.be/pcqi4yWBDuQ.
Expand Down

0 comments on commit 1adf3de

Please sign in to comment.