Ethereum, Libra and a Unified Type System
By: Loredana Cirstea
Tags: Type Systems, Blockchain, Libra, Ethereum, Programming

Libra, with its new language Move, was recently announced and some of its features are interesting. Here, we will only be covering those related to its type system, comparing it to Ethereum/Solidity and dType and then presenting some interesting conclusions, suggestions, and some demos at the end.

For additional Ethereum-Libra feature comparisons, check out: https://github.com/pipeos-one/chaincompare and feel free to correct and contribute.

Type-related Features

EVM & Solidity

Solidity is a statically typed language with support for elementary types (booleans, integers, address, byte arrays, strings, enums, functions etc.) and complex types (mappings, fixed-size & dynamic-size arrays, structs). Developers can create their own named types with struct .

These custom types and helper functions can be reused by others if they are defined in libraries, or by inheritance if they are defined in a contract . The former is more elegant, as it separates type definition and helper functions from data storage.

Therefore, you can define reusable types like this:

// MarketProductLib.sol

library MarketProductLib {

struct MarketProduct {

bytes32 id;

uint64 amount;

uint64 price;

}

function productFunction (MarketProduct product) public {}

} // OriginatedMarketProductLib.sol

import 'MarketProductLib.sol'; library OriginatedMarketProductLib {

struct OriginatedMarketProduct {

MarketProductLib.MarketProduct product;

address creator;

}

function originatedProductFunction (OriginatedMarketProduct product) public {}

}

This is the basis of how dType types are defined, but we will go into more details below.

Libra & Move

Both Move and Move VM are typed, currently supporting a small number of native types and values: booleans, unsigned 64-bit integers, 256-bit addresses, fixed-size byte arrays, structs (including resources), and references.

A key feature is its modularity, through separating custom type definitions (resources or unrestricted structs) and static functions from actual data, stored in each account. Modules, which contain the actual code, have no state. Modules can define resource types (nominal types, similar to Solidity structs ) and procedures. They are kept in the global scope, meaning types can be reused from any defined module, as long as you know the module address, name, and resource name (e.g. 0x56.Currency.TCoin ). Modules have an acyclic dependency relation.

The key feature of Move is the ability to define custom resource types … the Move type system provides special safety guarantees for resources. A resource can never be copied, only moved. These guarantees are enforced statically by the Move VM.

The bytecode verifier checks properties like type-safety, reference-safety, and resource-safety (i.e., resources are not duplicated, reused, or inadvertently destroyed). The rules for mutating, deleting, and publishing a resource are encoded in the module that created the resource and declared its type.

The state is stored in account resources. For example, the number of TCoins held by an account 0x12.. , is found at a unique access path determined by account address, module address, and name, resource name: 0x12/resources/0x56.Currency.TCoin .

Procedures are uniquely identified by its module identifier and signature and module dependencies are acyclic. This ensures that all procedure calls in Move are statically determined.

These features, along with no dynamic dispatch and limited mutability offer a more deterministic environment for doing static verification of computations.

Additionally, the design supports parallel execution of transactions if there are no state conflicts. Memory cells are found at a unique access path (e.g. 0x12/resources/0x56.Currency.TCoin ), therefore conflicts are easier to find.

Let us write our initial type definition example in Move IR:

module MarketProductModule {

resource MarketProduct {

id: bytearray,

amount: u64,

price: u64,

}

} module OriginatedMarketProductModule {

import 0x3.MarketProductModule; resource OriginatedMarketProduct {

creator: address,

product: R#MarketProductModule.MarketProduct,

}

}

Type Registry on-chain (Global Scope)

The advantage of having a type system is that bugs stemming from incorrect type assumptions (wrong input types given to a function, applying operators on variable types that should not be compatible) are caught at compile or run time. In our case, with both Solidity and Move, we are talking about static type checking at compile time. There are other advantages — enabling compiler optimizations, type documentation for free, customizing and extending type rules, etc.

However, 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.

Currently, neither Ethereum nor Libra are using such a system, but it is just a matter of time. Libra Association is in a good position to control the types used for their platform and we do not know when they plan to open their testnet to external developers or whether external developers will have enough freedom on their platform.

For Ethereum, we have started dType, a decentralized typing system. For details, check out dType — Decentralized Type System & Functional Programming on Ethereum.

With dType, you define your types in libraries , along with helper functions, as in my first MarketProductLib and OriginatedMarketProductLib Ethereum example.

You can also define libraries for additional non-state changing helper functions. As in Libra’s case, these functions are easy to test for invariants and are deterministic. You can run your tests directly on Mainnet deployed libraries. Each of these functions has a unique identifier inside the registry, in the same way as Libra has a unique identifier for its procedures.

You can have an optional StorageContract for each type, if you want to centralized data storage for each type. Libra does the same thing — separates definitions and rules from data. However, in Libra, resource data is stored under each account. In Ethereum, this might be achievable with user-specific smart contracts, but a central storage contract has its benefits, even on the legal side: there is no one owner that is responsible. In a world where legal standards are not always congruent with moral standards, this can be useful.