fate
FATE
The fast æternity transaction engine.
Design
The high level machine (or the fast æternity transaction engine) has æternity transactions as its basic operations and it operates directly on the state tree of the æternity chain. This is a new paradigm in blockchain virtual machine specifications which makes it possible to create type safe and efficient implementations of the machine.
Every operation is typed and all values are typed (either by being stored in a typed memory or by a tag). Any type violation results in an exception and reverts all state changes. In version 1.0 there will be no catch instruction.
In addition to normal machine instructions, such as ADD, the machine also has support for constructing most of the transactions available on the æternity chain from native low level chain transaction instructions.
The instruction memory is divided into functions and basic blocks. Only basic blocks can be indexed and used as jump destinations.
There are instructions to operate on the chain state tree in a safe and formalized way.
FATE is "functional" in the sense that "updates" of data structures, such as tuples, lists or maps do not change the old values of the structure. Instead a new version is created, unless specific operations to write to the contract store are used.
FATE does have the ability to write the value of an operation back to the same register or stack position as one of the arguments, in effect updating the memory. Thus, any other references to the structure before the operation will have the same structure as before the operation.
Objectives
Type safety
FATE solves some fundamental problems programmers run into when coding for Ethereum: integer overflow, weak type checking and poor data flow. FATE checks all arithmetic operations to keep the right meaning of it. Also you can't implicitly cast types (eg integers to booleans).
In EVM contracts are not typed. When calling a function, EVM will try to find which function you wanted to use by looking at the data that you send into the code. In FATE, you have actual functions and the functions have types - function call has to match the function type.
FATE ultimately makes a safer coding platform for smart contracts.
Rich data types
FATE has more built in data types: like maps, lists.
Faster transactions, smaller code size
Having a higher level instructions makes the code deployed smaller and it reduces the blockchain size. Smaller code and smaller hashes also keeps a blockchain network from clogging up and results in cheaper transactions.
Components
Machine State
Current contract: Address
Current owner: Address
Caller: Address
Code: Code of current contract
Memory: Several components as defined below
CF, current function: Hash of current function
CBB, current basic block: Index of current basic block.
EC, Execution continuation: A list of instructions left in the current basic block.
Memory
The machine memory is divided into:
The chain top (block height, etc)
Event stream
The account state tree
The contract store
The execution/code memory
Execution stack (push/pop on call/return)
Accumulator stack (push/pop on instruction level)
Local storage/stack/environment/context (push/pop on let...in...end)
The execution memory
The code to execute is stored in the execution memory. The execution memory is a three level map. The key on the top level is a contract address. The second level is a map from function hashes to a map of basic blocks. The keys in the map of basic blocks are indices to basic blocks (a 0 based, dense enumeration). The values are lists of instructions.
When a contract is called the code of the contract is read from the state tree into the execution memory. At this point the machine implementation can deserialize the serialized version of FATE code into instructions and basic blocks.
The serialization format for FATE code is described in Appendix 1: Fate instruction set and serialization.
The chain
The chain "memory" contains information about the current block and the current iteration. These values are available through special instructions.
Beneficiary
Timestamp
(Block) Height
Difficulty
Gaslimit
Address
Balance
Caller
Value
The contract store
The permanent storage of a contract. Stored in the chain state tree. This is a key value store that is mapped to the contract state by the compiler.
The compiler can choose how to represent the contract state in the store. For instance, save the entire state under a single key (this is how it works in AEVM at the moment), or it could flatten any state records and store each field under a separate key.
The state tree
Contains all accounts, oracles and contracts on the chain. Most values such as account balances can be read directly through specific load instructions. Some values can be changed through transaction instructions.
The local storage
The local storage is a key value store. The key is a an integer. The value can be of any FATE type. The local store is local to a function call.
The accumulator stack
The accumulator stack is a stack of typed values. The top of the stack can be used as an argument to an operation. Values can be pushed to the stack and popped from the stack.
The execution stack
The execution stack contain return addresses as a tuple in the form of {contract address, function hash, and basic block index}.
The event stream
A contract can emit events similar to the EVM.
Types
The machine supports the following types:
Integer (signed arbitrary precision integers)
Boolean (true | false)
Strings (arbitrary size byte arrays)
Bytes (fixed size byte arrays)
Bits (An arbitrary size bitmap)
Address (A pointer into the state tree)
Contract Address (A pointer to a contract)
Oracle (A pointer to an oracle)
Oracle Query (A pointer to an oracle query)
Channel (A pointer to a channel)
Tuples (a typed series of values)
Lists (a list of values of a specific type)
Maps (a key value store)
Store Map (a key value store mapped to the contract state)
Variant Types (a set of tagged and typed values)
Type (a representation of a FATE type)
These types can be used as argument and return types of functions and also as the type of the elements in the contract store.
Integer
The type Integer, is used for any integer, infinitely large or small (below zero).
Examples:
Boolean
The Boolean type only has two values, true or false.
Examples (all of them):
Operations on booleans include:
and
,or
,not
conditional jumps
Addresses, Contract Addresses, Oracles, Oracle Querries, Channels
Values of any address type are 32-byte (256-bit) pointers to entities on the chain (Accounts, Oracles, Contracts, etc)
Examples:
Strings
Strings are stored as byte arrays. From VM version FATE_02
they are strictly UTF-8 encoded unicode characters. In particular operations String.to_list
and String.from_list
ensure that they only contain well formed code points.
Examples:
Bytes
In VM versions FATE_01
and FATE_02
only fixed (size known at compile time) size byte arrays exist. With the change to Strings, making them UTF-8 encoded byte arrays, there is a need for general arbitrary length byte arrays. These are introduced in FATE_03
- and a couple of new operations handling (and converting) byte arrays are added. Technical note: the bytes type was {bytes, N} / bytes(n)
- to change as little as possible arbitrary size byte arrays have the type {bytes, -1} / bytes()
.
Tuples
Tuples have an arity indicating the number of elements in the tuple. Each element in the tuple has a type. The 0-tuple is also sometimes called unit. The maximum number of elements in the tuple is 255 (included).
Examples:
Lists
Lists are monomorphic series of values. Hence lists have a type. There is also an empty list (or Nil).
Examples:
Maps
Maps are monomorphic key value stores, that is the key is always the same type for all elements in a map and the value has the same type in all mappings in a map. The key can have any type except a map. The value can have any type including a map.
Examples:
Type: Map(Integer, String)
Type: Map(String, Map(String, Integer))
Variant Types
The variant type is a type consisting of a list of sizes and a tag (index) where each tag represents a series of values of specified types. For example you could have the Option type which could be None or Some(Integer). The sizes of the variant are 0 and 1 (there are two variants), The value None would be indicated by tag 0. The value Some(42) would be represented by tag 1 followed by the integer 42.
Examples:
Note that the tuple syntax for the elements does not indicate a Fate tuple but a series of values.
TypeRep
A TypeRep is the representation of a type as a value. A TypeRep is one of
integer
boolean
any
{list, T}
{tuple, Ts}
address
contract
oracle
oracle_query
channel
bits
{bytes, N}
{map, K, V}
string
{variant, ListOfVariants}
{tvar, N}
where T, K and V are TypeReps, Ts is a list of TypeReps ([T1, T2, ... ]), and ListOfVariants is a list ([]) of tuples of TypeReps ([{tuple, [Ts1]}, {tuple, [Ts2]} ... ]). N is a byte (0...255).
Operations
All operations are typed. An operand can be the accumulator, an immediate, a name, a reference to a field in the state tree, or a reference to the contract state. The operand must be of the right type.
Operands
Operand specifiers are
immediate
arg
var
a (accumulator == stack 0)
The specifiers are encoded as
immediate
11
var
10
arg
01
stack
00
Operand specifiers for operations with 1 to 4 arguments are stored in the byte following the opcode. Operand specifiers for operations with 5 to 8 arguments are stored in the two bytes following the opcode. Note that for many operation the first argument (arg0) is the destination for the operation.
bit:
7
6
5
4
3
2