The Fe Guide

Fe logo


Let's get started with Fe! In this chapter you will learn how to install the Fe compiler and write your first contract.


At this point Fe is only available for Linux and MacOS.

Note: If you happen to be a Windows developer, consider getting involved and help us to support the Windows platform. Here would be a good place to start.

Download the compiler

At this point Fe is only distributed via a single executable file linked from the home page. In the future we will make sure it can be installed through popular package managers such as apt or homebrew.

Depending on your operating system, the file that you download is either named fe_amd64 or fe_mac.

Note: We will rename the file to fe and assume that name for the rest of the guide. In the future when Fe can be installed via other mechanisms we can assume fe to become the canonical command name.

Add permission to execute

In order to be able to execute the Fe compiler we will have to make the file executable. This can be done by navigating to the directory where the file is located and executing chmod + x <filename> (e.g. chmod +x fe).

After we have set the proper permissions we should be able to run ./fe_amd64 --help and an output that should be roughly compareable to:

Fe 0.4.0-alpha
Compiler for the Fe language

    fe_amd64 [FLAGS] [OPTIONS] <input>

    -h, --help         Prints help information
        --optimize     Enables the Yul optimizer`
        --overwrite    Overwrite contents of output directory`
    -V, --version      Prints version information

    -e, --emit <emit>                Comma separated compile targets e.g. -e=bytecode,yul [default: abi,bytecode]
                                     [possible values: abi, bytecode, ast, tokens, yul, loweredAst]
    -o, --output-dir <output-dir>    The directory to store the compiler output e.g /tmp/output [default: output]

    <input>    The input source file to use e.g erc20.fe

Write your first Fe contract

Now that we have the compiler installed let's write our first contract. A contract contains the code that will be deployed to the Ethereum blockchain and resides at a specific address.

The code of the contract dictates how:

  • it manipulates its own state
  • interacts with other contracts
  • exposes external APIs to be called from other contracts or users

To keep things simple we will just write a basic guestbook where people can leave a message associated with their Ethereum address.

Note: Real code would not instrument the Ethereum blockchain in such a way as it is a waste of precious resources. This code is for demo purposes only.

Create a guest_book.fe file

Fe code is written in files ending on the .fe file extension. Let's create a file guest_book.fe and put in the following content.


contract GuestBook:

Now, execute ./fe guest_book.fe to compile the file.

Oops, the compiler is telling us that it didn't expect our code to end here.

Unable to compile guest_book.fe.
error: unexpected end of file
  ┌─ guest_book.fe:1:20
1 │ contract GuestBook:
  │                    ^

Fe follows Pythonic block indentation rules and the compiler expects us to provide a block of indented code after GuestBook:.

Let's expand the code by providing a map where we can associate messages with Ethereum addresses. The messages will simply be a string of a maximum length of 100 written as string100. The addresses are represented by the builtin address type.

contract GuestBook:
  messages: Map<address, String<100>>

Execute ./fe guest_book.fe again to recompile the file.

This time, the compiler tells us that it compiled our contract and that it has put the artifacts into a subdirectory called output.

Compiled guest_book.fe. Outputs in `output`

If we examine the output directory we'll find a subdirectory GuestBook with a GuestBook_abi.json and a GuestBook.bin file.

├── fe
├── guest_book.fe
└── output
    └── GuestBook
        ├── GuestBook_abi.json
        └── GuestBook.bin

The GuestBook_abi.json is a JSON representation that describes the binary interface of our contract but since our contract doesn't yet expose anything useful its content for now resembles an empty array.

The GuestBook.bin is slightly more interesting containing what looks like a gibberish of characters which in fact is the compiled binary contract code written in hexadecimal characters.

We don't need to do anything further yet with these files that the compiler produces but they will become important when we get to the point where we want to deploy our code to the Ethereum blockchain.

Add a method to sign the guest book

Let's focus on the functionality of our world changing application and add a method to sign the guestbook.

contract GuestBook:
  messages: Map<address, String<100>>

  pub def sign(book_msg: string100):
      self.messages[msg.sender] = book_msg

The code should look familar to those of us that have written Python before except that in Fe every method that is defined without the pub keyword becomes private. Since we want people to interact with our contract and call the sign method we have to prefix it with pub.

Let's recompile the contract again and see what happens.

Failed to write output to directory: `output`. Error: Directory 'output' is not empty. Use --overwrite to overwrite.

Oops, the compiler is telling us that the output directory is a non-empty directory and plays it safe by asking us if we are sure that we want to overwrite it. We have to use the --overwrite flag to allow the compiler to overwrite it is that is stored in the output directory.

Let's try it again with ./fe guest_book.fe --overwrite.

This time it worked and we can also see that the GuestBook_abi.json has become slightly more interesting.

    "name": "sign",
    "type": "function",
    "inputs": [
        "name": "book_msg",
        "type": "bytes100"
    "outputs": []

Since our contract now has a public sign method the corresponding ABI has changed accordingly.

Add a method to read a message

To make the guest book more useful we will also add a method get_msg to read entries from a given address.

contract GuestBook:
  messages: Map<address, String<100>>

  pub def sign(book_msg: string100):
      self.messages[msg.sender] = book_msg

  pub def get_msg(addr: address) -> string100:
      return self.messages[addr]

However, we will hit another error as we try to recompile the current code.

Unable to compile guest_book.fe.
Analyzer error: CannotMove on line 8
pub def get_msg(addr: address) -> string100:
      return self.messages[addr]

When we try to return a reference type such as an array from the storage of the contract we have to explicitly copy it to memory using the to_mem() function.

Note: In the future Fe will likely introduce immutable storage pointers which might affect these semantics.

The code should compile fine when we change it accordingly.

contract GuestBook:
  messages: Map<address, String<100>>

  pub def sign(book_msg: string100):
      self.messages[msg.sender] = book_msg

  pub def get_msg(addr: address) -> string100:
      return self.messages[addr].to_mem()

Congratulations! You finished your first little Fe project. 👏 In the next chapter we will learn how to deploy our code and tweak it a bit further.

Deploy your contract.

Since we have written our first contract now, how about we bring it to live and use it on an actual chain?

Deploying such a demo contract to the Ethereum mainnet would be a waste of money but fortunately we have a few other options to choose from. For instance, we can use our very own local blockchain instance which is great for local development. Alternatively we can use a test network that provides developers shared infrastructure to deploy code without spending actual money on it.

In this guide we will learn how to deploy and interact with our guest book on the popular Görli testnet.

Setting the stage with

The Ethereum ecosystem provides a rich set of tools to assist smart contract developers in various ways when it comes to developing, testing, deploying and upgrading smart contracts. Fe is still a very young language and there are no tools yet that are tailored for the language. Having said that, most tooling should be flexible enough to work with Fe in some way that might feel slightly less integrated. For this guide we choose to use DappTools which is a very lightweight set of command line tools for managing smart contract development.

To follow this guide, you will first need to head over to and follow the installation steps.

Note: If you are a seasoned smart contract developer who uses different tools, feel free to follow the tutorial using your own toolchain.

Setting up a Görli user account

To deploy our contract to the Görli testnet we will need to have an Ethereum account that has some GöETH. GöETH has no real value but it is still a resource that is needed as a basic line of defense against spamming the testnet. If you don't have any GöETH yet, you can request some from this faucet

The next thing we need is to create a keystore file for our account so that dapp tools can sign messages via ethsign.

IMPORTANT: It is good practice to never use an Ethereum account for a testnet that is also used for the actual Ethereum mainnet.

To create the keystore file for your testnet account, you can use ethsign to import your private key. Run the following command and follow the instructions.

ethsign import --keystore ~/.ethereum/keystore/

Making the deployment transaction

Let's recall that we finished our guest book in the previous chapter with the following code.

contract GuestBook:
  messages: Map<address, String<100>>

  pub def sign(book_msg: String<100>):
      self.messages[msg.sender] = book_msg

  pub def get_msg(addr: address) -> String<100>:
      return self.messages[addr].to_mem()

If you haven't already, run ./fe guest_book.fe --overwrite to obtain the bytecode that we want to deploy.

To make the deployment, we will need to send a transaction to a node that participates in the Görli network. We can run our own node, sign up at Infura to use one of their nodes or find an open public node such as which we will use to keep this tutorial as accessible as possible.

Use the following command to deploy the contract. Please note that <rpc-url> needs to be replaced with the URL of the node that we connect to and <our-eth-address> needs to be replaced with the Ethereum address that we imported in the previous step.

$ ETH_RPC_URL=<rpc-url> ETH_FROM=<our-eth-address> seth send --create output/GuestBook/GuestBook.bin

What follows is the actual command and the response that was used when writing the tutorial.

$ ETH_RPC_URL= ETH_FROM=0x4E14AaF86CF0759d6Ec8C7433acd66F07D093293 seth send --create output/GuestBook/GuestBook.bin
seth-send: warning: `ETH_GAS' not set; using default gas amount
Ethereum account passphrase (not echoed): seth-send: Published transaction with 681 bytes of calldata.
seth-send: 0x241ac045170d0612b67b2319fa08ed8be8b79568e00090c4f84146897b83760b
seth-send: Waiting for transaction receipt...............................
seth-send: Transaction included in block 4858224.

As we can see in the output, our transaction 0x241ac045170d0612b67b2319fa08ed8be8b79568e00090c4f84146897b83760b to deploy the contract is now included in the Görli blockchain. At the very end of the response we find 0xcecd2be6d4d01ed7906f502be6321c3721f38bc6 which is the address where our contract is now deployed.

Signing the guest book

Now that the guest book is live on the Görli network, everyone can send a transaction to sign it. We will sign it from the same address that was used to deploy the contract but there is nothing preventing anyone to sign it from any other address.

The following command will send a transaction to call sign(string) with the message "We <3 Fe".

ETH_RPC_URL=<rpc-url> ETH_FROM=<our-eth-address> seth send <contract-address> "sign(string)" '"We <3 Fe"'

What follows is again the actual command and the response that was used when writing the tutorial.

$ ETH_RPC_URL= ETH_FROM=0x4E14AaF86CF0759d6Ec8C7433acd66F07D093293 seth send 0xcecd2be6d4d01ed7906f502be6321c3721f38bc6 "sign(string)" '"We <3 Fe"'
seth-send: warning: `ETH_GAS' not set; using default gas amount
Ethereum account passphrase (not echoed): seth-send: Published transaction with 100 bytes of calldata.
seth-send: 0xf61c042064a501939769b802d1455124b0f8665eb1b070c75c2815ca52bd8706
seth-send: Waiting for transaction receipt.............
seth-send: Transaction included in block 4858368.

Just as before, the response tells us the transaction hash 0xf61c042064a501939769b802d1455124b0f8665eb1b070c75c2815ca52bd8706 which we can inspect on Etherscan.

Reading the signatures

The get_msg(address) API let's us read any signature for any address but it will give us an response of 100 zero bytes for any address that simply hasn't signed the guestbook.

Since reading the messages doesn't change any state within the blochchain, we don't have to send and actual transaction. Instead we just perform a call against the local state of the node that we are querying.

To do that run:

$ ETH_RPC_URL=<rpc-url> seth call <contract-address> "get_msg(address)" <address-to-check> | seth --to-ascii

Notice that the command doesn't need to provide ETH_FROM simply because we are not sending an actual transaction.

$ ETH_RPC_URL= seth call 0xcecd2be6d4d01ed7906f502be6321c3721f38bc6 "get_msg(address)" 0x4E14AaF86CF0759d6Ec8C7433acd66F07D093293 | seth --to-ascii
We <3 Fe

As we can see in the last line of the output the signature for address 0x4E14AaF86CF0759d6Ec8C7433acd66F07D093293 is in fact We <3 Fe.

Congratulations! You've deployed real Fe code to a live network 🤖


Read how to become a Fe developer.

Build and test

Please make sure Rust is installed.


The following commands only build the Fe -> Yul compiler components.

  • build the CLI: cargo build
  • test: cargo test --workspace


The Fe compiler depends on the Solidity compiler for transforming Yul IR to EVM bytecode. We currently use solc-rust to perform this. In order to compile solc-rust, the following must be installed on your system:

  • cmake
  • libboost
  • libclang

Once these have been installed, you may run the full build. This is enabled using the solc-backend feature.

  • build the CLI: cargo build --features solc-backend
  • test: cargo test --workspace --features solc-backend



Make sure that version follows semver rules e.g (0.2.0-alpha).

For the time being, ALWAYS specify the -alpha suffix.

Generate Release Notes

Prerequisite: Release notes are generated with towncrier.Ensure to have towncrier installed and the command is available.

Run make notes version=<version> where <version> is the version we are generating the release notes for e.g. 0.2.0-alpha.


make notes version=0.2.0-alpha

Examine the generated release notes and if needed perform and commit any manual changes.

Generate the release

Run make release version=<version>.


make release version=0.2.0-alpha

This will also run the tests again as the last step because some of them may need to be adjusted because of the changed version number.

Tag and push the release

Prerequisite: Make sure the central repository is configured as upstream, not origin.

After the tests were adjusted run make push-tag to create the tag and push it to Github.

Manually edit the release on GitHub

Running the previous command will push a new tag to Github and cause CI to create a release with the Fe binaries attached. We may want to edit the release afterwards to put in some verbiage about the release.

Fe Language Specification

Warning: This is a work in progress document. It is incomplete and specifications aren't stable yet.

1. Notation

1.1 Grammar

The following notations are used by the Lexer and Syntax grammar snippets:

CAPITALKW_IFA token produced by the lexer
ItalicCamelCaseItemA syntactical production
stringx, while, *The exact character(s)
\x\n, \r, \t, \0The character represented by this escape
An optional item
0 or more of x
1 or more of x
a to b repetitions of x
|u8 | u16, Block | ItemEither one or another
[ ][b B]Any of the characters listed
[ - ][a-z]Any of the characters in the range
~[ ]~[b B]Any characters, except those listed
~string~\n, ~*/Any characters, except this sequence
( )(, Parameter)Groups items

2. Lexical structure

2.1. Keywords

Fe divides keywords into two categories:

2.1.1. Strict keywords

These keywords can only be used in their correct contexts. They cannot be used as the names of:

**Lexer: **
KW_AS : as
KW_BREAK : break
KW_CONST : const
KW_CONTINUE : continue
KW_CONST : contract
KW_DEF : def
KW_ELIF : elif
KW_ELSE : else
KW_EMIT : emit
KW_ENUM : enum
KW_EVENT : event
KW_FALSE : false
KW_FOR : for
KW_IDX : idx
KW_IF : if
KW_IN : in
KW_LET : let
KW_NONPAYABLE : nonpayable
KW_PASS : pass
KW_PAYABLE : payable
KW_PUB : pub
KW_RETURN : return
KW_REVERT : revert
KW_STRUCT : struct
KW_TRUE : true
KW_WHILE : while
KW_ADDRESS : address

2.1.2. Reserved keywords

These keywords aren't used yet, but they are reserved for future use. They have the same restrictions as strict keywords. The reasoning behind this is to make current programs forward compatible with future versions of Fe by forbidding them to use these keywords.

**Lexer **
KW_ABSTRACT : abstract
KW_DO : do
KW_EXTERNAL : external
KW_FINAL : final
KW_IMPL : impl
KW_MACRO : macro
KW_MATCH : match
KW_MUT : mut
KW_OVERRIDE : override
KW_PURE : pure
KW_STATIC : static
KW_SUPER : super
KW_TRAIT : trait
KW_TYPE : type
KW_TYPEOF : typeof
KW_USE : use
KW_VIEW : view
KW_VIRTUAL : virtual
KW_WHERE : where
KW_YIELD : yield

2.2. Identifiers

**Lexer: **
      [a-z A-Z] [a-z A-Z 0-9 _]*

   | _ [a-z A-Z 0-9 _]+

Except a strict or reserved keyword

An identifier is any nonempty ASCII string of the following form:


  • The first character is a letter.
  • The remaining characters are alphanumeric or _.


  • The first character is _.
  • The identifier is more than one character. _ alone is not an identifier.
  • The remaining characters are alphanumeric or _.



2.4. End of header

**Lexer: **
EndOfHeader :

2.5. Block Expression

**Lexer: **
BlockExpression :

The contents of a block expression are indented from the parent context following Python indentation rules.

3. Items

3.1. Functions

**Syntax **
Function :
   FunctionQualifiers def IDENTIFIER
      ( FunctionParameters? )


FunctionQualifiers :

FunctionDecorators :

FunctionDecorator :

FunctionParameters :
   FunctionParam (, FunctionParam)* ,?

FunctionParam :

FunctionReturnType :
   -> Type

A function consists of a [block], along with a name and a set of parameters. Other than a name, all these are optional. Functions are declared with the keyword def. Functions may declare a set of input [variables][variables] as parameters, through which the caller passes arguments into the function, and the output type of the value the function will return to its caller on completion.

When referred to, a function yields a first-class value of the corresponding zero-sized [function item type], which when called evaluates to a direct call to the function.

A function header ends with a colon (:) after which the function body begins.

For example, this is a simple function:

def answer_to_life_the_universe_and_everything() -> u256:
    return 42;

3.1.1 Visibility and Privacy

These two terms are often used interchangeably, and what they are attempting to convey is the answer to the question "Can this item be used at this location?"

Fe knows two different types of visibility for functions and state variables: public and private. Visibility of private is the default and is used if no other visiblity is specified.

Public: External functions are part of the contract interface, which means they can be called from other contracts and via transactions.

Private: Those functions and state variables can only be accessed internally from within the same contract. This is the default visibility.

For example, this is a function that can be called externally from a transaction:

pub def answer_to_life_the_universe_and_everything() -> u256:
    return 42;

3.2. Structs

**Syntax **
Struct :
   struct IDENTIFIER     EndOfHeader

StructField :

A struct is a nominal struct type defined with the keyword struct.

An example of a struct item and its use:

struct Point:
    x: u256
    y: u256

p = Point {x: 10, y: 11}
px: u256 = p.x;

Builtin functions:

  • abi_encode() encodes the struct as an ABI tuple and returns the encoded data as a fixed-size byte array that is equal in size to the encoding.

3.3. Events

**Syntax **
Event :
   event IDENTIFIER     EndOfHeader

EventField :
   EventIndexability IDENTIFIER : Type

EventIndexability :

An event is a nominal event type defined with the keyword event. It is emitted with the keyword emit.

An example of a event item and its use:

event Transfer:
    idx sender: address
    idx receiver: address
    value: u256

def transfer(to : address, value : u256):
   # Heavy logic here
   # All done, log the event for listeners
   emit Transfer(msg.sender, _to, _value)

3.4. Enumeration

**Syntax **
Enumeration :
   enum IDENTIFIER     EndOfHeader

EnumField :

An enumeration, also referred to as enum is a simultaneous definition of a nominal enumerated type, that can be used to create or pattern-match values of the corresponding enumerated type.

Enumerations are declared with the keyword enum.

An example of an enum item and its use:

enum Animal:

barker = Animal.Dog

3.5. Type aliases

**Syntax **
TypeAlias :
   type IDENTIFIER = Type

A type alias defines a new name for an existing type. Type aliases are declared with the keyword type.

For example, the following defines the type BookMsg as a synonym for the type bytes[100], a sequence of 100 bytes:

type BookMsg = bytes[100]

3.6. Contracts

**Syntax **
Contract :
   contract IDENTIFIER     EndOfHeader


      | [Function]
      | [Struct]
      | [Event]
      | [Enumeration]

Visibility :

ContractField :

A contract in Fe is a collection of code that resides at a specific address on the Ethereum blockchain. It is defined with the keyword contract.

An example of a contract:

contract GuestBook:
    pub guest_book: Map<address, bytes[100]>

    event Signed:
        idx book_msg: bytes[100]

    pub def sign(book_msg: bytes[100]):
        self.guest_book[msg.sender] = book_msg

        emit Signed(book_msg=book_msg)

    pub def get_msg(addr: address) -> bytes[100]:
        return self.guest_book[addr]

4. Statements and Expressions

4.1. Statements

4.1.1 pragma statement

**Syntax **
PragmaStatement :
   pragma [VersionRequirementExpression]

The pragma statement is denoted with the keyword pragma. Evaluating a pragma statement will cause the compiler to reject compilation if the version of the compiler does not conform to the given version requirement.

An example of a pragma statement:

pragma ^0.1.0

The version requirement syntax is identical to the one that is used by cargo (more info).

4.1.2 revert statement

**Syntax **
RevertStatement :

The revert statement is denoted with the keyword revert. Evaluating a revert statement will cause to revert all state changes made by the call and return with an revert error to the caller.

An example of a revert statement:

def transfer(to : address, value : u256):
    if not self.in_whitelist(to):
    # more logic here

### 4.2 Expressions

### 4.2.1 Arithmetic Operators

> **<sup>Syntax</sup>**\
> _ArithmeticExpression_ :\
> &nbsp;&nbsp;&nbsp;&nbsp; [_Expression_] `+` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `-` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `*` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `/` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `%` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `**` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `&` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `|` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `^` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `<<` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `>>` [_Expression_]

Binary operators expressions are all written with [infix notation](
This table summarizes the behavior of arithmetic and logical binary operators on
primitive types.

| Symbol | Integer                 | Status      | Discussions    |
| `+`    | Addition                | IMPLEMENTED |                |
| `-`    | Subtraction             | IMPLEMENTED |                |
| `*`    | Multiplication          | IMPLEMENTED |                |
| `/`    | Division*               | IMPLEMENTED |                |
| `%`    | Remainder               | IMPLEMENTED |                |
| `**`   | Exponentiation          | IMPLEMENTED |                |
| `&`    | Bitwise AND             | IMPLEMENTED |                |
| <code>&#124;</code> | Bitwise OR | IMPLEMENTED |                |
| `^`    | Bitwise XOR             | IMPLEMENTED |                |
| `<<`   | Left Shift              | IMPLEMENTED |                |
| `>>`   | Right Shift             | IMPLEMENTED |                |

\* Integer division rounds towards zero.

Here are examples of these operators being used.

3 + 6 == 9 6 - 3 == 3 2 * 3 == 6 6 / 3 == 2 TODO: Rest 5 % 4 == 1 2 ** 4 == 16 12 & 25 == 8 12 | 25 == 29 12 ^ 25 == 21 212 << 1 == 424 212 >> 1 == 106

### 4.2.2 Comparision Operators

> **<sup>Syntax</sup>**\
> _ComparisonExpression_ :\
> &nbsp;&nbsp; &nbsp;&nbsp; [_Expression_] `==` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `!=` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `>` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `<` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `>=` [_Expression_]\
> &nbsp;&nbsp; | [_Expression_] `<=` [_Expression_]

| Symbol | Meaning                  |     Status                 |
| `==`   | Equal                    |         IMPLEMENTED        |
| `!=`   | Not equal                |         IMPLEMENTED        |
| `>`    | Greater than             |         IMPLEMENTED        |
| `<`    | Less than                |         IMPLEMENTED        |
| `>=`   | Greater than or equal to |         IMPLEMENTED        |
| `<=`   | Less than or equal to    |         IMPLEMENTED        |

Here are examples of the comparison operators being used.

123 == 123 23 != -12 12 > 11 11 >= 11 11 < 12 11 <= 11

## 5. Types system

### 5.1 Types

#### 5.1.1 Types

Every variable, item, and value in a Fe program has a type. The _type_ of a
*value* defines the interpretation of the memory holding it and the operations
that may be performed on the value.

Built-in types are tightly integrated into the language, in nontrivial ways
that are not possible to emulate in user-defined types. User-defined types have
limited capabilities.

The list of types is:

* Data types
    * Base types:
        * [Boolean] — `true` or `false`
        * [Address] - Ethereum address
        * [Numeric] — integer
    * Reference types:
        * Sequence types
            * [Tuple]
            * [Array]
            * [Bytes]
            * [String]
            * [Struct]
            * [Enum]
        * [HashMap]
* Other types:
    * [Event]
    * [Contract]
    * [Function]

### Boolean type

The `bool` type is a data type which can be either `true` or `false`.


x = true Contract type

An contract type is the type denoted by the name of an contract item.

A value of a given contract type carries the contract's public interface as attribute functions. A new contract value can be created by either casting an address to a contract type or by creating a new contract using the type attribute functions create or create2.


 contract Foo:
    pub def get_my_num() -> u256:
        return 42

contract FooFactory:
    pub def create2_foo() -> address:
        # `0` is the value being sent and `52` is the address salt
        foo: Foo = Foo.create2(0, 52)
        return address(foo) Numeric types

The unsigned integer types consist of:


The signed two's complement integer types consist of:

i256-(2255)2255-1 Textual types

MISSING Tuple types

MISSING Array types

MISSING Struct types

An struct type is the type denoted by the name of an struct item. Enumerated types

An enum type is the type denoted by the name of an enum item. Function item types

MISSING Address type

MISSING HashMap type

Maps a key to a value.



Where TKey is a base type and TValue is any data type. Bytes types

MISSING String types

MISSING Event types

An event type is the type denoted by the name of an event item.

6. Data Layout

There are three places where data can be stored on the EVM:

  • stack: 256-bit values placed on the stack that are loaded using DUP operations.
  • storage: 256-bit address space where 256-bit values can be stored. Accessing higher storage slots does not increase gas cost.
  • memory: 256-bit address space where 256-bit values can be stored. Accessing higher memory slots increases gas cost.

Each data type described in section 5 can be stored in these locations. How data is stored is described in this section.

6.1. Stack

The following can be stored on the stack:

  • base type values
  • pointers to sequence type values

The size of each value stored on the stack must not exceed 256 bits. Since all base types are less than or equal to 256 bits in size, we store them on the stack. Pointers to values stored in memory may also be stored on the stack.


# function scope
foo: u256 = 42 # foo is stored on the stack
bar: u256[100] # bar is a memory pointer stored on the stack

6.2. Storage

All data types can be stored in storage.

6.2.1. Constant size values in storage

Storage pointers for constant size values are determined at compile time.


# contract scope
foo: u256 # foo is assigned a static pointer by the compiler

The value of a base type in storage is found by simply loading the value from storage at the given pointer.

To find an element inside of a sequence type, the relative location of the element is added to the given pointer.

6.2.2. Maps in storage

Maps are not assigned pointers, because they do not have a location in storage. They are instead assigned a nonce that is used to derive the location of keyed values during runtime.


# contract scope
bar: Map<address, u256> # bar is assigned a static nonce by the compiler
baz: Map<address, Map<address, u256>> # baz is assigned a static nonce by the compiler

The expression bar[0x00] would resolve to the hash of both bar's nonce and the key value .i.e. keccak256(<bar nonce>, 0x00). Similarly, the expression baz[0x00][0x01] would resolve to a nested hash i.e. keccak256(keccak256(<baz nonce>, 0x00), 0x01).

6.2.3. The to_mem function

Reference type values can be copied from storage and into memory using the to_mem function.


my_array_var: u256[10] = self.my_array_field.to_mem()

6.3. Memory

Only sequence types can be stored in memory.

The first memory slot (0x00) is used to keep track of the lowest available memory slot. Newly allocated segments begin at the value given by this slot. When more memory has been allocated, the value stored in 0x00 is increased.

We do not free memory after it is allocated.

6.3.1. Sequence types in memory

Sequence type values may exceed the 256-bit stack slot size, so we store them in memory and reference them using pointers kept on the stack.


# function scope
foo: u256[100] # foo is a pointer that references 100 * 256 bits in memory.

To find an element inside of a sequence type, the relative location of the element is added to the given pointer.

6.2.3. The clone function

Reference type values in memory can be cloned using the clone function.


# with clone
foo: u256[10] = bar.clone() # `foo` points to a new segment of memory
assert foo[1] == bar[1] 
foo[1] = 42
assert foo[1] != bar[1] # modifying `foo` does not modify bar

# without clone
foo: u256[10] = bar # `foo` and `bar` point to the same segment of memory
assert foo[1] == bar[1]
foo[1] = 42
assert foo[1] == bar[1] # modifying `foo` also modifies `bar`

6.4. Function calls

Constant size values stored on the stack or in memory can be passed into and returned by functions.

Release Notes

🖥️ Download Binaries 📄 Draft Spec ℹ️ Getting Started

Fe is moving fast. Read up on all the latest improvements.

WARNING: All Fe releases are alpha releases and only meant to share the development progress with developers and enthusiasts. It is NOT yet ready for production usage.

0.6.0-alpha "Feldspar" (2021-06-10)


  • Support for pragma statement

    Example: pragma ^0.1.0 (#361)

  • Add support for tuple destructuring


    my_tuple: (u256, bool) = (42, true)
    (x, y): (u256, bool) = my_tuple


    1. Call expression can now accept generic arguments
    2. Replace stringN to String<N> Example:
    s: String<10> = String<10>("HI")


    • Many analyzer errors now include helpful messages and underlined code.
    • Event and struct constructor arguments must now be labeled and in the order specified in the definition.
    • The analyzer now verifies that the left-hand side of an assignment is actually assignable. (#398)
  • Types of integer literal are now inferred, rather than defaulting to u256.

    contract C:
      def f(x: u8) -> u16:
        y: u8 = 100   # had to use u8(100) before
        z: i8 = -129  # "literal out of range" error
        return 1000   # had to use `return u16(1000)` before
      def g():

    Similar inference is done for empty array literals. Previously, empty array literals caused a compiler crash, because the array element type couldn't be determined.

    contract C:
      def f(xs: u8[10]):
      def g():

    (Note that array length mismatch is still a type error, so this code won't actually compile.) (#429)

  • The Map type name is now capitalized. Example:

    contract GuestBook:
        guests: Map<address, String<100>>


  • Convert all remaining errors to use the new advanced error reporting system (#432)

  • Analyzer throws an error if __init__ is not public. (#435)

Internal Changes - for Fe Contributors

  • Refactored front-end "not implemented" errors into analyzer errors and removed questionable variants. Any panic is now considered to be a bug. (#437)

0.5.0-alpha (2021-05-27)


  • Add support for hexadecimal/octal/binary numeric literals.


    value_hex: u256 = 0xff
    value_octal: u256 = 0o77
    value_binary: u256 = 0b11


  • Added support for list expressions.


    values: u256[3] = [10, 20, 30]
    # or anywhere else where expressions can be used such as in a call
    sum: u256 = self.sum([10, 20, 30])


  • Contracts, events, and structs can now be empty.


    event MyEvent:
    contract MyContract:
    struct MyStruct:


  • External calls can now handle dynamically-sized return types. (#415)


  • The analyzer will return an error if a tuple attribute is not of the form item<index>. (#401)

Improved Documentation

  • Created a landing page for Fe at (#394)
  • Provide a Quickstart chapter in Fe Guide (#403)

Internal Changes - for Fe Contributors

  • Using insta to validate Analyzer outputs. (#387)

  • Analyzer now disallows using context.add_ methods to update attributes. (#392)

  • () now represents a distinct type internally called the unit type, instead of an empty tuple.

    The lowering pass now does the following: Valueless return statements are given a () value and functions without a return value are given explicit () returns. (#406)

  • Add CI check to ensure fragment files always end with a new line (#4711)

0.4.0-alpha (2021-04-28)


  • Support for revert messages in assert statements


    assert a == b, "my revert statement"

    The provided string is abi-encoded as if it were a call to a function Error(string). For example, the revert string "Not enough Ether provided." returns the following hexadecimal as error return data:

    0x08c379a0                                                         // Function selector for Error(string)
    0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
    0x000000000000000000000000000000000000000000000000000000000000001a // String length
    0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data


  • Added support for augmented assignments.


    contract Foo:
        pub def add(a: u256, b: u256) -> u256:
            a += b
            return a
        pub def sub(a: u256, b: u256) -> u256:
            a -= b
            return a
        pub def mul(a: u256, b: u256) -> u256:
            a *= b
            return a
        pub def div(a: u256, b: u256) -> u256:
            a /= b
            return a
        pub def mod(a: u256, b: u256) -> u256:
            a %= b
            return a
        pub def pow(a: u256, b: u256) -> u256:
            a **= b
            return a
        pub def lshift(a: u8, b: u8) -> u8:
            a <<= b
            return a
        pub def rshift(a: u8, b: u8) -> u8:
            a >>= b
            return a
        pub def bit_or(a: u8, b: u8) -> u8:
            a |= b
            return a
        pub def bit_xor(a: u8, b: u8) -> u8:
            a ^= b
            return a
        pub def bit_and(a: u8, b: u8) -> u8:
            a &= b
            return a


  • A new parser implementation, which provides more helpful error messages with fancy underlines and code context. (#346)

  • Added support for tuples with base type items.


    contract Foo:
        my_num: u256
        pub def bar(my_num: u256, my_bool: bool) -> (u256, bool):
            my_tuple: (u256, bool) = (my_num, my_bool)
            self.my_num = my_tuple.item0
            return my_tuple



  • Properly reject invalid emit (#211)

  • Properly tokenize numeric literals when they start with 0 (#331)

  • Reject non-string assert reasons as type error (#335)

  • Properly reject code that creates a circular dependency when using create or create2.

    Example, the follwing code is now rightfully rejected because it tries to create an instance of Foo from within the Foo contract itself.

    contract Foo:
      pub def bar()->address:
        return address(foo)


Internal Changes - for Fe Contributors

  • AST nodes use Strings instead of &strs. This way we can perform incremental compilation on the AST. (#332)
  • Added support for running tests against solidity fixtures. Also added tests that cover how solidity encodes revert reason strings. (#342)
  • Refactoring of binary operation type checking. (#347)

0.3.0-alpha "Calamine" (2021-03-24)


  • Add over/underflow checks for multiplications of all integers (#271)

  • Add full support for empty Tuples. (#276)

    All functions in Fe implicitly return an empty Tuple if they have no other return value. However, before this change one was not able to use the empty Tuple syntax () explicitly.

    With this change, all of these are treated equally:

    contract Foo:
      pub def explicit_return_a1():
      pub def explicit_return_a2():
        return ()
      pub def explicit_return_b1() ->():
      pub def explicit_return_b2() ->():
        return ()
      pub def implicit_a1():
      pub def implicit_a2() ->():
  • The JSON ABI builder now supports structs as both input and output. (#296)

  • Make subsequently defined contracts visible.

    Before this change:

    # can't see Bar
    contract Foo:
    # can see Foo
    contract Bar:

    With this change the restriction is lifted and the following becomes possible. (#298)

    contract Foo:
        bar: Bar
        pub def external_bar() -> u256:
    contract Bar:
        foo: Foo
        pub def external_foo() -> u256:
  • Perform checks for divison operations on integers (#308)

  • Support for msg.sig to read the function identifier. (#311)

  • Perform checks for modulo operations on integers (#312)

  • Perform over/underflow checks for exponentiation operations on integers (#313)


  • Properly reject emit not followed by an event invocation (#212)

  • Properly reject octal number literals (#222)

  • Properly reject code that tries to emit a non-existing event. (#250)

    Example that now produces a compile time error:

    emit DoesNotExist()
  • Contracts that create other contracts can now include __init__ functions.

    See (#304)

  • Prevent multiple types with same name in one module. (#317)

    Examples that now produce compile time errors:

    type bar = u8
    type bar = u16


    struct SomeStruct:
        some_field: u8
    struct SomeStruct:
        other: u8


    contract SomeContract:
        some_field: u8
    contract SomeContract:
        other: u8

    Prevent multiple fields with same name in one struct.

    Example that now produces a compile time error:

    struct SomeStruct:
        some_field: u8
        some_field: u8

    Prevent variable definition in child scope when name already taken in parent scope.

    Example that now produces a compile time error:

    pub def bar():
        my_array: u256[3]
        sum: u256 = 0
        for i in my_array:
            sum: u256 = 0
  • The CLI was using the overwrite flag to enable Yul optimization.


    # Would both overwite output files and run the Yul optimizer. 
    $ fe my_contract.fe --overwrite

    Using the overwrite flag now only overwrites and optimization is enabled with the optimize flag. (#320)

  • Ensure analyzer rejects code that uses return values for __init__ functions. (#323)

    An example that now produces a compile time error:

    contract C:
        pub def __init__() -> i32:
            return 0
  • Properly reject calling an undefined function on an external contract (#324)

Internal Changes - for Fe Contributors

  • Added the Uniswap demo contracts to our testing fixtures and validated their behaviour. (#179)
  • IDs added to AST nodes. (#315)
  • Failures in the Yul generation phase now panic; any failure is a bug. (#327)

0.2.0-alpha "Borax" (2021-02-27)


  • Add support for string literals.


    def get_ticker_symbol() -> string3:
        return "ETH"

    String literals are stored in and loaded from the compiled bytecode. (#186)

  • The CLI now compiles every contract in a module, not just the first one. (#197)

    Sample compiler output with all targets enabled:

    |-- Bar
    |   |-- Bar.bin
    |   |-- Bar_abi.json
    |   `-- Bar_ir.yul
    |-- Foo
    |   |-- Foo.bin
    |   |-- Foo_abi.json
    |   `-- Foo_ir.yul
    |-- module.ast
    `-- module.tokens
  • Add support for string type casts (#201)


    val: string100 = string100("foo")
  • Add basic support for structs. (#203)


    struct House:
        price: u256
        size: u256
        vacant: bool
    contract City:
        pub def get_price() -> u256:
            building: House = House(300, 500, true)
            assert building.size == 500
            assert building.price == 300
            assert building.vacant
            return building.price
  • Added support for external contract calls. Contract definitions now add a type to the module scope, which may be used to create contract values with the contract's public functions as callable attributes. (#204)


    contract Foo:
        pub def build_array(a: u256, b: u256) -> u256[3]:
            my_array: u256[3]
            my_array[0] = a
            my_array[1] = a * b
            my_array[2] = b
            return my_array
    contract FooProxy:
        pub def call_build_array(
            foo_address: address,
            a: u256,
            b: u256,
        ) -> u256[3]:
            foo: Foo = Foo(foo_address)
            return foo.build_array(a, b)
  • Add support for block, msg, chain, and tx properties: (#208)

    block.coinbase: address
    block.difficulty: u256
    block.number: u256
    block.timestamp: u256 u256
    msg.value: u256
    tx.gas_price: u256
    tx.origin: address

    (Note that msg.sender: address was added previously.)


    def post_fork() -> bool:
        return block.number > 2675000
  • The CLI now panics if an error is encountered during Yul compilation. (#218)

  • Support for contract creations.

    Example of create2, which takes a value and address salt as parameters.

    contract Foo:
        pub def get_my_num() -> u256:
            return 42
    contract FooFactory:
        pub def create2_foo() -> address:
            # value and salt
            foo: Foo = Foo.create2(0, 52)
            return address(foo)

    Example of create, which just takes a value parameter.

    contract Foo:
        pub def get_my_num() -> u256:
            return 42
    contract FooFactory:
        pub def create_foo() -> address:
            # value and salt
            foo: Foo = Foo.create(0)
            return address(foo)

    Note: We do not yet support init parameters. (#239)

  • Support updating individual struct fields in storage. (#246)


     pub def update_house_price(price: u256):
            self.my_house.price = price
  • Implement global keccak256 method. The method expects one parameter of bytes[n] and returns the hash as an u256. In a future version keccak256 will most likely be moved behind an import so that it has to be imported (e.g. from std.crypto import keccak256). (#255)


    pub def hash_single_byte(val: bytes[1]) -> u256:
        return keccak256(val)
  • Require structs to be initialized using keyword arguments.


    struct House:
        vacant: bool
        price: u256

    Previously, House could be instantiated as House(true, 1000000). With this change it is required to be instantiated like House(vacant=true, price=1000000)

    This ensures property assignment is less prone to get mixed up. It also makes struct initialization visually stand out more from function calls. (#260)

  • Implement support for boolean not operator. (#264)


    if not covid_test.is_positive(person):
  • Do over/underflow checks for additions (SafeMath).

    With this change all additions (e.g x + y) for signed and unsigned integers check for over- and underflows and revert if necessary. (#265)

  • Added a builtin function abi_encode() that can be used to encode stucts. The return type is a fixed-size array of bytes that is equal in size to the encoding. The type system does not support dynamically-sized arrays yet, which is why we used fixed. (#266)


    struct House:
        price: u256
        size: u256
        rooms: u8
        vacant: bool
    contract Foo:
        pub def hashed_house() -> u256:
            house: House = House(
            return keccak256(house.abi_encode())
  • Perform over/underflow checks for subtractions (SafeMath). (#267)

    With this change all subtractions (e.g x - y) for signed and unsigned integers check for over- and underflows and revert if necessary.

  • Support for the boolean operations and and or. (#270)


    contract Foo:
        pub def bar(x: bool, y: bool) -> bool:
            return x and y
    contract Foo:
        pub def bar(x: bool, y: bool) -> bool:
            return x or y

    Support for self.address.

    This expression returns the address of the current contract.


    contract Foo:
        pub def bar() -> address:
            return self.address


  • Perform type checking when calling event constructors

    Previously, the following would not raise an error even though it should:

    contract Foo:
        event MyEvent:
            val_1: string100
            val_2: u8
        pub def foo():
            emit MyEvent("foo", 1000)

    Wit this change, the code fails with a type error as expected. (#202)

  • Fix bug where compilation of contracts without public functions would result in illegal YUL. (#219)

    E.g without this change, the following doesn't compile to proper YUL

    contract Empty:
      lonely: u256
  • Ensure numeric literals can't exceed 256 bit range. Previously, this would result in a non user friendly error at the YUL compilation stage. With this change it is caught at the analyzer stage and presented to the user as a regular error. (#225)

  • Fix crash when return is used without value.

    These two methods should both be treated as returning ()

      pub def explicit_return():
      pub def implicit():

    Without this change, the explicit_return crashes the compiler. (#261)

Internal Changes - for Fe Contributors

  • Renamed the fe-semantics library to fe-analyzer. (#207)
  • Runtime testing utilities. (#243)
  • Values are stored more efficiently in storage. (#251)

0.1.0-alpha "Amethyst" (2021-01-20)

WARNING: This is an alpha version to share the development progress with developers and enthusiasts. It is NOT yet intended to be used for anything serious. At this point Fe is missing a lot of features and has a lot of bugs instead.

This is the first alpha release and kicks off our release schedule which will be one release every month in the future. Since we have just started tracking progress on changes, the following list of changes is incomplete, but will appropriately document progress between releases from now on.


  • Added support for for loop, allows iteration over static arrays. (#134)

  • Enforce bounds on numeric literals in type constructors.

    For instance calling u8(1000) or i8(-250) will give an error because the literals 1000 and -250 do not fit into u8 or i8. (#145)

  • Added builtin copying methods clone() and to_mem() to reference types. (#155)


    # copy a segment of storage into memory and assign the new pointer
    my_mem_array = self.my_sto_array.to_mem()
    # copy a segment of memory into another segment of memory and assign the new pointer
    my_other_mem_array = my_mem_array.clone()
  • Support emitting JSON ABI via --emit abi. The default value of --emit is now abi,bytecode. (#160)

  • Ensure integer type constructor reject all expressions that aren't a numeric literal. For instance, previously the compiler would not reject the following code even though it could not be guaranteed that val would fit into an u16.

    pub def bar(val: u8) -> u16:
            return u16(val)

    Now such code is rejected and integer type constructor do only work with numeric literals such as 1 or -3. (#163)

  • Support for ABI decoding of all array type. (#172)

  • Support for value assignments in declaration.

    Previously, this code would fail:

    another_reference: u256[10] = my_array

    As a workaround declaration and assignment could be split apart.

    another_reference: u256[10]
    another_reference = my_array

    With this change, the shorter declaration with assignment syntax is supported. (#173)

Improved Documentation

  • Point to examples in the README (#162)
  • Overhaul README page to better reflect the current state of the project. (#177)
  • Added descriptions of the to_mem and clone functions to the spec. (#195)

Internal Changes - for Fe Contributors

  • Updated the Solidity backend to v0.8.0. (#169)
  • Run CI tests on Mac and support creating Mac binaries for releases. (#178)

Contributor Covenant Code of Conduct

Our Pledge

We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.

Our Standards

Examples of behavior that contributes to a positive environment for our community include:

  • Demonstrating empathy and kindness toward other people
  • Being respectful of differing opinions, viewpoints, and experiences
  • Giving and gracefully accepting constructive feedback
  • Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
  • Focusing on what is best not just for us as individuals, but for the overall community

Examples of unacceptable behavior include:

  • The use of sexualized language or imagery, and sexual attention or advances of any kind
  • Trolling, insulting or derogatory comments, and personal or political attacks
  • Public or private harassment
  • Publishing others' private information, such as a physical or email address, without their explicit permission
  • Other conduct which could reasonably be considered inappropriate in a professional setting

Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.

Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.


This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.


Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the reporter of any incident.

Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:

1. Correction

Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.

Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.

2. Warning

Community Impact: A violation through a single incident or series of actions.

Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.

3. Temporary Ban

Community Impact: A serious violation of community standards, including sustained inappropriate behavior.

Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.

4. Permanent Ban

Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.

Consequence: A permanent ban from any sort of public interaction within the community.


This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at

Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder.

For answers to common questions about this code of conduct, see the FAQ at Translations are available at