The Fe Guide

Fe logo

Quickstart

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

Installation

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 or ./fe_mac -h and an output that should be roughly comparable to:

Fe 0.4.0-alpha
Compiler for the Fe language

USAGE:
    fe_amd64 [FLAGS] [OPTIONS] <input>

FLAGS:
    -h, --help         Prints help information
        --overwrite    Overwrite contents of output directory`
    -V, --version      Prints version information

OPTIONS:
    -e, --emit <emit>                Comma separated compile targets e.g. -e=bytecode,yul [default: abi,bytecode]
                                     [possible values: abi, bytecode, ast, tokens, yul, loweredAst]
        --optimize <optimize>        Whether the Yul optimizer should be used or not e.g. --optimize=false [default: true]
    -o, --output-dir <output-dir>    The directory to store the compiler output e.g /tmp/output [default: output]

ARGS:
    <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 {
  messages: Map<address, String<100>>
}

Here we're using a map to associate messages with Ethereum addresses. The messages will simply be a string of a maximum length of 100 written as String<100>. The addresses are represented by the builtin address type.

Execute ./fe guest_book.fe to compile the file. 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.

use std::context::Context

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

  pub fn sign(self, ctx: Context, book_msg: String<100>) {
      self.messages[ctx.msg_sender()] = book_msg
  }
}

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 what 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.

use std::context::Context

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

  pub fn sign(self, ctx: Context, book_msg: String<100>) {
      self.messages[ctx.msg_sender()] = book_msg
  }

  pub fn get_msg(self, addr: address) -> String<100> {
      return self.messages[addr]
  }
}

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

Unable to compile guest_book.fe.
error: value must be copied to memory
  ┌─ guest_book.fe:10:14
  │
8 │       return self.messages[addr]
  │              ^^^^^^^^^^^^^^^^^^^ this value is in storage
  │
  = Hint: values located in storage can be copied to memory using the `to_mem` function.
  = Example: `self.my_array.to_mem()`

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.

use std::context::Context

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

  pub fn sign(self, ctx: Context, book_msg: String<100>) {
      self.messages[ctx.msg_sender()] = book_msg
  }

  pub fn get_msg(self, addr: address) -> String<100> {
      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 alive 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 dapp.tools

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 dapp.tools 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 these faucets: faucet1, and faucet2. You can also check a list of faucets here.

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.

use std::context::Context

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

  pub fn sign(self, ctx: Context, book_msg: String<100>) {
    self.messages[ctx.msg_sender()] = book_msg
  }

  pub fn get_msg(self, 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 or Alchemy to use one of their nodes or find an open public node such as https://goerli-light.eth.linkpool.io 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=https://goerli-light.eth.linkpool.io 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.
0xcecd2be6d4d01ed7906f502be6321c3721f38bc6

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=https://goerli-light.eth.linkpool.io 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 an 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=https://goerli-light.eth.linkpool.io 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 🤖

Development

Read how to become a Fe developer.

Build and test

Please make sure Rust is installed.

Basic

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

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

Full

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

Release

Versioning

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.

Example:

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

Example:

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 are 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 the 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.

Updating Docs & Website

A release of a new Fe compiler should usually go hand in hand with updating the website and documentation. For one, the front page of fe-lang.org links to the download of the compiler but won't automatically pick up the latest release without a fresh deployment. Furthermore, if code examples and other docs needed to be updated along with compiler changes, these updates are also only reflected online when the site gets redeployed. This is especially problematic since our docs do currently not have a version switcher to view documentation for different compiler versions (See GitHub issue #543).

Preview the sites locally

Run make serve-website and visit http://0.0.0.0:8000 to preview it locally. Ensure the front page displays the correct compiler version for download and that the docs render correctly.

Deploy website & docs

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

Run make deploy-website and validate that fe-lang.org renders the updated sites (Can take up to a few minutes).

Fe Language Specification

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

Notation

Grammar

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

NotationExamplesMeaning
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
x?pub?An optional item
x*OuterAttribute*0 or more of x
x+MacroMatch+1 or more of x
xa..bHEX_DIGIT1..6a 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

Lexical Structure

Keywords

Fe divides keywords into two categories:

Strict keywords

These keywords can only be used in their correct contexts. They cannot be used as the identifiers.

Lexer:
KW_AS : as
KW_BREAK : break
KW_CONST : const
KW_CONTINUE : continue
KW_CONST : contract
KW_FN : fn
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_PAYABLE : payable
KW_PUB : pub
KW_RETURN : return
KW_REVERT : revert
KW_SELFVALUE : self
KW_STRUCT : struct
KW_TRUE : true
KW_WHILE : while
KW_ADDRESS : address

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_ABSTRACT : async
KW_ABSTRACT : await
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_SELFTYPE : Self
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

Identifiers

Lexer:
IDENTIFIER_OR_KEYWORD :
      [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:

Either

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

Or

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

Tokens

NEWLINE

A token that represents a new line.

Literals

A literal is an expression consisting of a single token, rather than a sequence of tokens, that immediately and directly denotes the value it evaluates to, rather than referring to it by name or some other evaluation rule. A literal is a form of constant expression, so is evaluated (primarily) at compile time.

Examples

Strings

ExampleCharactersEscapes
String"hello"ASCII subsetQuote & ASCII

ASCII escapes

Name
\nNewline
\rCarriage return
\tTab
\\Backslash

Quote escapes

Name
\"Double quote

Numbers

Number literals*Example
Decimal integer98_222
Hex integer0xff
Octal integer0o77
Binary integer0b1111_0000

* All number literals allow _ as a visual separator: 1_234

String literals

Lexer
STRING_LITERAL :
   " (
      PRINTABLE_ASCII_CHAR
      | QUOTE_ESCAPE
      | ASCII_ESCAPE
   )* "

PRINTABLE_ASCII_CHAR :
   Any ASCII character between 0x1F and 0x7E

QUOTE_ESCAPE :
   \"

ASCII_ESCAPE :
   | \n | \r | \t | \\

A string literal is a sequence of any characters that are in the set of printable ASCII characters as well as a set of defined escape sequences.

Line breaks are allowed in string literals.

Integer literals

Lexer
INTEGER_LITERAL :
   ( DEC_LITERAL | BIN_LITERAL | OCT_LITERAL | HEX_LITERAL )

DEC_LITERAL :
   DEC_DIGIT (DEC_DIGIT|_)*

BIN_LITERAL :
   0b (BIN_DIGIT|_)* BIN_DIGIT (BIN_DIGIT|_)*

OCT_LITERAL :
   0o (OCT_DIGIT|_)* OCT_DIGIT (OCT_DIGIT|_)*

HEX_LITERAL :
   0x (HEX_DIGIT|_)* HEX_DIGIT (HEX_DIGIT|_)*

BIN_DIGIT : [0-1]

OCT_DIGIT : [0-7]

DEC_DIGIT : [0-9]

HEX_DIGIT : [0-9 a-f A-F]

An integer literal has one of four forms:

  • A decimal literal starts with a decimal digit and continues with any mixture of decimal digits and underscores.
  • A hex literal starts with the character sequence U+0030 U+0078 (0x) and continues as any mixture (with at least one digit) of hex digits and underscores.
  • An octal literal starts with the character sequence U+0030 U+006F (0o) and continues as any mixture (with at least one digit) of octal digits and underscores.
  • A binary literal starts with the character sequence U+0030 U+0062 (0b) and continues as any mixture (with at least one digit) of binary digits and underscores.

Examples of integer literals of various forms:

123                      # type u256
0xff                     # type u256
0o70                     # type u256
0b1111_1111_1001_0000    # type u256
0b1111_1111_1001_0000i64 # type u256

Comments

Lexer
LINE_COMMENT :
      # *

Items

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 visibility 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 fn answer_to_life_the_universe_and_everything() -> u256 {
    return 42
}

Top-level definitions in a Fe source file can also be specified as pub if the file exists within the context of an Ingot. Declaring a definition as pub enables other modules within an Ingot to use the definition.

For example, given an Ingot with the following structure:

example_ingot
└── src
    ├── ding
    │   └── dong.fe
    └── main.fe

With ding/dong.fe having the following contents:

pub struct Dang {
    pub my_address: address
    pub my_u256: u256
    pub my_i8: i8
}

Then main.fe can use the Dang struct since it is pub-qualified:

use ding::dong::Dang

contract Foo {
    pub fn hot_dang() -> Dang {
        return Dang(
            my_address: address(8),
            my_u256: 42,
            my_i8: -1
        )
    }
}

Functions

Syntax
Function :
   FunctionQualifiers fn IDENTIFIER
      ( FunctionParameters? )
      FunctionReturnType?
      {
      FunctionStatements*
      }

FunctionQualifiers :
   pub?

FunctionStatements :
         ReturnStatement
      | VariableDeclarationStatement
      | AssignStatement
      | AugmentedAssignStatement
      | ForStatement
      | WhileStatement
      | IfStatement
      | AssertStatement
      | EmitStatement
      | BreakStatement
      | ContinueStatement
      | RevertStatement
      | Expression

FunctionParameters :
   self? | self,? FunctionParam (, FunctionParam)* ,?

FunctionParam :
   FunctionParamLabel? IDENTIFIER : Types

FunctionParamLabel :
   _ | IDENTIFIER

FunctionReturnType :
   -> Types

A function definition consists of name and code block along with an optional list of parameters and return value. Functions are declared with the keyword fn. Functions may declare a set of input 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 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:

fn add(x: u256, y: u256) -> u256 {
    return x + y
}

Functions can be defined inside of a contract, inside of a struct, or at the "top level" of a module (that is, not nested within another item).

Example:

fn add(_ x: u256, _ y: u256) -> u256 {
    return x + y
}

contract CoolCoin {
    balance: Map<address, u256>

    fn transfer(self, from sender: address, to recipient: address, value: u256) -> bool {
        if self.balance[sender] < value {
            return false
        }
        self.balance[sender] -= value
        self.balance[recipient] += value
        return true
    }
    pub fn demo(self) {
        let ann: address = address(0xaa)
        let bob: address = address(0xbb)
        self.balance[ann] = 100

        let bonus: u256 = 2
        let value: u256 = add(10, bonus)
        let ok: bool = self.transfer(from: ann, to: bob, value)
    }
}

Function parameters have optional labels. When a function is called, the arguments must be labeled and provided in the order specified in the function definition.

The label of a parameter defaults to the parameter name; a different label can be specified by adding an explicit label prior to the parameter name. For example:

fn encrypt(msg cleartext: u256, key: u256) -> u256 {
    return cleartext ^ key
}

fn demo() {
    let out: u256 = encrypt(msg: 0xdecafbad, key: 0xfefefefe)
}

Here, the first parameter of the encrypt function has the label msg, which is used when calling the function, while the parameter name is cleartext, which is used inside the function body. The parameter name is an implementation detail of the function, and can be changed without modifying any function calls, as long as the label remains the same.

When calling a function, a label can be omitted when the argument is a variable with a name that matches the parameter label. Example:

let msg: u256 = 0xdecafbad
let cyf: u256 = encrypt(msg, key: 0x1234)

A parameter can also be specified to have no label, by using _ in place of a label in the function definition. In this case, when calling the function, the corresponding argument must not be labeled. Example:

fn add(_ x: u256, _ y: u256) -> u256 {
    return x + y
}
fn demo() {
    let sum: u256 = add(16, 32)
}

Functions defined inside of a contract or struct may take self as a parameter. This gives the function the ability to read and write contract storage or struct fields, respectively. If a function takes self as a parameter, the function must be called via self. For example:

let ok: bool = self.transfer(from, to, value)

Structs

Syntax
Struct :
   struct IDENTIFIER {
   StructField*
   }

StructField :
   pub? IDENTIFIER : Type

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

An example of a struct item and its use:

struct Point {
    pub x: u256
    pub y: u256
}

fn pointy_stuff() {
    let p: Point = Point(x: 10, y: 11)
    let 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.

Events

Syntax
Event :
   event IDENTIFIER {    EventField*
   }

EventField :
   EventIndexability IDENTIFIER : Type

EventIndexability :
   idx?

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:

use std::context::Context

contract Foo {
    event Transfer {
        idx sender: address
        idx receiver: address
        value: u256
    }

    fn transfer(ctx: Context, to: address, value: u256) {
        # Heavy logic here
        # All done, log the event for listeners
        emit Transfer(ctx, sender: ctx.msg_sender(), receiver: to, value)
    }
}

Enum

Syntax
Enumeration :
   enum IDENTIFIER {
   EnumField*
   }

EnumField :
   IDENTIFIER

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 {
    Dog
    Cat
}

fn f() {
    let barker: Animal = Animal::Dog
}

NOTE: Enums are not yet implemented.

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 u8[100], a sequence of 100 u8 numbers which is how sequences of bytes are represented in Fe.

type BookMsg = Array<u8, 100>

Contracts

Syntax
Contract :
   contract IDENTIFIER {
  ContractMember*
  _}

ContractMember:
   Visibility?
   (
         ContractField
      | Function
      | Struct
      | Event
      | Enumeration
   )

Visibility :
   pub?

ContractField :
   IDENTIFIER : Type

A contract is a piece of EVM Code associated with an Account. See Appendix A. in the Yellow Paper for more info. In Fe, a contract is denoted using the contract keyword. A contract definition adds a new contract type to the module. This contract type may be used for calling existing contracts with the same interface or initializing new contracts with the create methods.

An example of a contract:

use std::context::Context

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

    event Signed {
        book_msg: String<100>
    }

    pub fn sign(self, ctx: Context, book_msg: String<100>) {
        self.messages[ctx.msg_sender()] = book_msg
        emit Signed(ctx, book_msg: book_msg)
    }

    pub fn get_msg(self, addr: address) -> String<100> {
        return self.messages[addr].to_mem()
    }
}

Statements

pragma statement

Syntax
PragmaStatement :
   pragma VersionRequirement

VersionRequirement :Following the semver implementation by cargo

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).

const statement

Syntax
ConstStatement :
   const IDENTIFIER: Type = Expression

A const statement introduces a named constant value. Constants are either directly inlined wherever they are used or loaded from the contract code depending on their type.

Example:

const TEN: u256 = 10
const HUNDO: u256 = TEN * TEN

contract Foo {
  pub fn bar() -> u256 {
    return HUNDO
  }
}

let statement

Syntax
LetStatement :
   let IDENTIFIER | TupleTarget : Type = Expression

TupleTarget :
   ( TupleTargetItem (, TupleTargetItem) + )

TupleTargetItem :
   IDENTIFIER | TupleTarget

A let statement introduces a new set of variables. Any variables introduced by a variable declaration are visible from the point of declaration until the end of the enclosing block scope.

Note: Support for nested tuples isn't yet implemented but can be tracked via this GitHub issue.

Example:

contract Foo {

  pub fn bar() {
    let val1: u256 = 1
    let (val2):(u256) = (1,)
    let (val3, val4):(u256, bool) = (1, false)
    let (val5, val6, (val7, val8)):(u256, bool, (u256, u256)) = (1, false, (2, 4))
  }
}

Assignment statement

Syntax
AssignmentStatement :
   Expression = Expression

An assignment statement moves a value into a specified place. An assignment statement consists of an expression that holds a mutable place, followed by an equals sign (=) and a value expression.

Example:

contract Foo {
  some_array: Array<u256, 10>

  pub fn bar(self) {
    let val1: u256 = 10
    # Assignment of stack variable
    val1 = 10

    let values: (u256, u256) = (1, 2)
    # Assignment of tuple item
    values.item0 = 3

    # Assignment of storage array slot
    self.some_array[5] = 1000
  }
}

Augmenting Assignment statement

Syntax
AssignmentStatement :
      Expression = Expression
   | Expression += Expression
   | Expression -= Expression
   | Expression %= Expression
   | Expression **= Expression
   | Expression <<= Expression
   | Expression >>= Expression
   | Expression |= Expression
   | Expression ^= Expression
   | Expression &= Expression

Augmenting assignment statements combine arithmetic and logical binary operators with assignment statements.

An augmenting assignment statement consists of an expression that holds a mutable place, followed by one of the arithmetic or logical binary operators, followed by an equals sign (=) and a value expression.

Example:

contract Foo {

    pub fn add(a: u256, b: u256) -> u256 {
        a += b
        return a
    }

    pub fn sub(a: u256, b: u256) -> u256 {
        a -= b
        return a
    }

    pub fn mul(a: u256, b: u256) -> u256 {
        a *= b
        return a
    }

    pub fn div(a: u256, b: u256) -> u256 {
        a /= b
        return a
    }

    pub fn mod(a: u256, b: u256) -> u256 {
        a %= b
        return a
    }

    pub fn pow(a: u256, b: u256) -> u256 {
        a **= b
        return a
    }

    pub fn lshift(a: u8, b: u8) -> u8 {
        a <<= b
        return a
    }

    pub fn rshift(a: u8, b: u8) -> u8 {
        a >>= b
        return a
    }

    pub fn bit_or(a: u8, b: u8) -> u8 {
        a |= b
        return a
    }

    pub fn bit_xor(a: u8, b: u8) -> u8 {
        a ^= b
        return a
    }

    pub fn bit_and(a: u8, b: u8) -> u8 {
        a &= b
        return a
    }
}

revert statement

Syntax
RevertStatement :
   revert Expression?

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. A revert statement may be followed by an expression that evaluates to a struct in which case the struct is encoded as revert data as defined by EIP-838.

An example of a revert statement without revert data:

contract Foo {
    fn transfer(self, to: address, value: u256) {
        if not self.in_whitelist(addr: to) {
            revert
        }
        # more logic here
    }

    fn in_whitelist(self, addr: address) -> bool {
        return false
    }
}

An example of a revert statement with revert data:

struct ApplicationError {
    pub code: u8
}

contract Foo {
    pub fn transfer(self, to: address, value: u256) {
        if not self.in_whitelist(addr: to) {
            revert ApplicationError(code: 5)
        }
        # more logic here
    }

    fn in_whitelist(self, addr: address) -> bool {
        return false
    }
}

return statement

Syntax
ReturnStatement :
   return Expression?

The return statement is denoted with the keyword return. A return statement leaves the current function call with a return value which is either the value of the evaluated expression (if present) or () (unit type) if return is not followed by an expression explicitly.

An example of a return statement without explicit use of an expression:

contract Foo {
    fn transfer(self, to: address, value: u256) {
        if not self.in_whitelist(to) {
            return
        }
    }

    fn in_whitelist(self, to: address) -> bool {
        # revert used as placeholder for actual logic
        revert
    }
}

The above can also be written in a slightly more verbose form:

contract Foo {
    fn transfer(self, to: address, value: u256) -> () {
        if not self.in_whitelist(to) {
            return ()
        }
    }

    fn in_whitelist(self, to: address) -> bool {
        # revert used as placeholder for actual logic
        revert
    }
}

emit statement

Syntax
EmitStatement :
   emit EventLiteral ( CallParams? )

EventLiteral :
   IDENTIFIERName of event defined in current module

CallParams :
   CallArg ( , CallArg )* ,?

CallArg :
   (CallArgLabel :)? Expression

CallArgLabel :
   IDENTIFIERLabel must correspond to the name of the event property at the given position. It can be omitted if parameter name and event property name are equal.

The emit statement is used to create log entries in the blockchain. The emit keyword is followed by a literal that corresponds to a defined event, followed by a parenthesized comma-separated list of expressions.

Examples:

use std::context::Context

contract Foo {
    event Mix {
        num1: u256
        idx addr: address
        num2: u256
        my_bytes: Array<u8, 100>
    }
    pub fn emit_mix(ctx: Context, addr: address, my_bytes: Array<u8, 100>) {
        emit Mix(ctx, num1: 26, addr, num2: 42, my_bytes)
    }
}

if statement

Syntax
IfStatement :
   if Expression{    (Statement | Expression)+
   }
   (else {
   (Statement | Expression)+
   })?

Example:

contract Foo {

    pub fn bar(val: u256) -> u256 {
        if val > 5 {
            return 1
        } else {
            return 2
        }
    }
}

The if statement is used for conditional execution.

for statement

Syntax
ForStatement :
   for IDENTIFIER in Expression {
   (Statement | Expression)+
   }

A for statement is a syntactic construct for looping over elements provided by an array type.

An example of a for loop over the contents of an array:

Example:

contract Foo {

    pub fn bar(values: Array<u256, 10>) -> u256 {
        let sum: u256
        for i in values {
            sum = sum + i
        }
        return sum
    }
}

while statement

Syntax
WhileStatement :
   while Expression {
   (Statement | Expression)+
   }

A while loop begins by evaluation the boolean loop conditional expression. If the loop conditional expression evaluates to true, the loop body block executes, then control returns to the loop conditional expression. If the loop conditional expression evaluates to false, the while expression completes.

Example:

contract Foo {

    pub fn bar() -> u256 {
        let sum: u256
        while sum < 10 {
            sum += 1
        }
        return sum
    }
}

break statement

Syntax
BreakStatement :
   break

The break statement can only be used within a for or while loop and causes the immediate termination of the loop.

If used within nested loops the break statement is associated with the innermost enclosing loop.

An example of a break statement used within a while loop.

contract Foo {

    pub fn bar() -> u256 {
        let sum: u256
        while sum < 10 {
            sum += 1

            if some_abort_condition() {
                break
            }
        }
        return sum
    }

    fn some_abort_condition() -> bool {
        # some complex logic
        return true
    }
}

An example of a break statement used within a for loop.

contract Foo {

    pub fn bar(values: Array<u256, 10>) -> u256 {
        let sum: u256
        for i in values {
            sum = sum + i

            if some_abort_condition() {
                break
            }
        }
        return sum
    }

    fn some_abort_condition() -> bool {
        # some complex logic
        return true
    }
}

continue statement

Syntax
ContinueStatement :
   continue

The continue statement can only be used within a for or while loop and causes the immediate termination of the current iteration, returning control to the loop head.

If used within nested loops the continue statement is associated with the innermost enclosing loop.

An example of a continue statement used within a while loop.

contract Foo {

    pub fn bar() -> u256 {
        let sum: u256
        while sum < 10 {
            sum += 1

            if some_skip_condition() {
                continue
            }
        }

        return sum
    }

    fn some_skip_condition() -> bool {
        # some complex logic
        return true
    }
}

An example of a continue statement used within a for loop.

contract Foo {

    pub fn bar(values: Array<u256, 10>) -> u256 {
        let sum: u256
        for i in values {
            sum = sum + i

            if some_skip_condition() {
                continue
            }
        }
        return sum
    }

    fn some_skip_condition() -> bool {
        # some complex logic
        return true
    }
}

assert statement

Syntax
AssertStatement :
   assert Expression (, Expression)?

The assert statement is used express invariants in the code. It consists of a boolean expression optionally followed by a comma followed by a string expression.

If the boolean expression evaluates to false, the code reverts with a panic code of 0x01. In the case that the first expression evaluates to false and a second string expression is given, the code reverts with the given string as the error code.

Warning: The current implementation of assert is under active discussion and likely to change.

An example of a assert statement without the optional message:

contract Foo {
    fn bar(val: u256) {
        assert val > 5
    }
}

An example of a assert statement with an error message:

contract Foo {

    fn bar(val: u256) {
        assert val > 5, "Must be greater than five"
    }
}

Expressions

Call expressions

Syntax
CallExpression :
   Expression GenericArgs? ( CallParams? )

GenericArgs :
   < IDENTIFIER | INTEGER_LITERAL (, IDENTIFIER | INTEGER_LITERAL)* >

CallParams :
   CallArg ( , CallArg )* ,?

CallArg :
   (CallArgLabel =)? Expression

CallArgLabel :
   IDENTIFIERLabel must correspond to the name of the called function argument at the given position. It can be omitted if parameter name and the name of the called function argument are equal.

A call expression calls a function. The syntax of a call expression is an expression, followed by a parenthesized comma-separated list of call arguments. Call arguments are expressions, and must be labeled and provided in the order specified in the function definition. If the function eventually returns, then the expression completes.

Example:

contract Foo {

    pub fn demo(self) {
        let ann: address = address(0xaa)
        let bob: address = address(0xbb)
        self.transfer(from: ann, to: bob, 25)
    }

    pub fn transfer(self, from: address, to: address, _ val: u256) {}
}

Tuple expressions

Syntax
TupleExpression :
   ( TupleElements? )

TupleElements :
   ( Expression , )+ Expression?

A tuple expression constructs tuple values.

The syntax for tuple expressions is a parenthesized, comma separated list of expressions, called the tuple initializer operands. The number of tuple initializer operands is the arity of the constructed tuple.

1-ary tuple expressions require a comma after their tuple initializer operand to be disambiguated with a parenthetical expression.

Tuple expressions without any tuple initializer operands produce the unit tuple.

For other tuple expressions, the first written tuple initializer operand initializes the field item0 and subsequent operands initializes the next highest field.

For example, in the tuple expression (true, false, 1), true initializes the value of the field item0, false field item1, and 1 field item2.

Examples of tuple expressions and their types:

ExpressionType
()() (unit)
(0, 4)(u256, u256)
(true, )(bool, )
(true, -1, 1)(bool, i256, u256)

A tuple field can be accessed via an attribute expression.

Example:

contract Foo {
    pub fn bar() {
        # Creating a tuple via a tuple expression
        let some_tuple: (u256, bool) = (1, false)

        # Accessing the first tuple field via the `item0` field
        baz(input: some_tuple.item0)
    }
    pub fn baz(input: u256) {}
}

List expressions

Syntax
ListExpression :
   [ ListElements? ]

ListElements :
   Expression (, Expression)* ,?

A list expression constructs array values.

The syntax for list expressions is a parenthesized, comma separated list of expressions, called the list initializer operands. The number of list initializer operands must be equal to the static size of the array type. The types of all list initializer operands must conform to the type of the array.

Examples of tuple expressions and their types:

ExpressionType
[1, self.get_number()]u256[2]
[true, false, false]bool[3]

An array item can be accessed via an index expression.

Example:

contract Foo {

    fn get_val3() -> u256 {
        return 3
    }

    pub fn baz() {
        let val1: u256 = 2
        # A list expression
        let foo: Array<u256, 3> = [1, val1, get_val3()]
    }
}

Struct expressions

TBW

Index expressions

Syntax
IndexExpression :
   Expression [ Expression ]

Array and Map types can be indexed by by writing a square-bracket-enclosed expression after them. For arrays, the type of the index key has to be u256 whereas for Map types it has to be equal to the key type of the map.

Example:

contract Foo {

    balances: Map<address, u256>

    pub fn baz(self, values: Array<u256, 10>) {
        # Assign value at slot 5
        values[5] = 1000
        # Read value at slot 5
        let val1: u256 = values[5]

        # Assign value for address zero
        self.balances[address(0)] = 10000

        # Read balance of address zero
        let bal: u256 = self.balances[address(0)]
    }
}

Attribute expressions

Syntax
AttributeExpression :
   Expression . IDENTIFIER

An attribute expression evaluates to the location of an attribute of a struct, tuple or contract.

The syntax for an attribute expression is an expression, then a . and finally an identifier.

Examples:

struct Point {
    pub x: u256
    pub y: u256
}

contract Foo {
    some_point: Point
    some_tuple: (bool, u256)

    fn get_point() -> Point {
        return Point(x: 100, y: 500)
    }

    pub fn baz(some_point: Point, some_tuple: (bool, u256)) {
        # Different examples of attribute expressions
        let bool_1: bool = some_tuple.item0
        let x1: u256 = some_point.x
        let point1: u256 = get_point().x
        let point2: u256 = some_point.x
    }
}

Name expressions

Syntax
NameExpression :
   IDENTIFIER

A name expression resolves to a local variable.

Example:

contract Foo {
    pub fn baz(foo: u256) {
        # name expression resolving to the value of `foo`
        foo
    }
}

Literal expressions

Syntax
LiteralExpression :
   | STRING_LITERAL
   | INTEGER_LITERAL
   | BOOLEAN_LITERAL

A literal expression consists of one of the literal forms described earlier. It directly describes a number, string or boolean value.

"hello"   # string type
5         # integer type
true      # boolean type

Arithmetic Operators

Syntax
ArithmeticExpression :
     Expression + Expression
   | Expression - Expression
   | Expression * Expression
   | Expression / Expression
   | Expression % Expression
   | Expression ** Expression
   | Expression & Expression
   | Expression | Expression
   | Expression ^ Expression
   | Expression << Expression
   | 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.

SymbolInteger
+Addition
-Subtraction
*Multiplication
/Division*
%Remainder
**Exponentiation
&Bitwise AND
|Bitwise OR
^Bitwise XOR
<<Left Shift
>>Right Shift

* 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
5 % 4 == 1
2 ** 4 == 16
12 & 25 == 8
12 | 25 == 29
12 ^ 25 == 21
212 << 1 == 424
212 >> 1 == 106

Comparison Operators

Syntax
ComparisonExpression :
      Expression == Expression
   | Expression != Expression
   | Expression > Expression
   | Expression < Expression
   | Expression >= Expression
   | Expression <= Expression

SymbolMeaning
==Equal
!=Not equal
>Greater than
<Less than
>=Greater than or equal to
<=Less than or equal to

Here are examples of the comparison operators being used.

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

Boolean Operators

Syntax
BooleanExpression :
     Expression or Expression
   | Expression and Expression

The operators or and and may be applied to operands of boolean type. The or operator denotes logical 'or', and the and operator denotes logical 'and'.

Example:

const x: bool = false or true # true

Unary Operators

Syntax
UnaryExpression :
     not Expression
   | - Expression
   | ~ Expression

The unary operators are used to negate expressions. The unary - (minus) operator yields the negation of its numeric argument. The unary ~ (invert) operator yields the bitwise inversion of its integer argument. The unary not operator yields the inversion of its boolean argument.

Example:

fn f() {
  let x: bool = not true  # false
  let y: i256 = -1
  let z: i256 = i256(~1)  # -2
}

Type System

Types

Every variable, item, and value in a Fe program has a type. The _ 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:

Boolean type

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

Example:

let x: bool = true

Contract types

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.

Example:

use std::context::Context

contract Foo {
    pub fn get_my_num() -> u256 {
        return 42
    }
}

contract FooFactory {
    pub fn create2_foo(ctx: Context) -> address {
        # `0` is the value being sent and `52` is the address salt
        let foo: Foo = Foo.create2(ctx, 0, 52)
        return address(foo)
    }
}

Numeric types

The unsigned integer types consist of:

TypeMinimumMaximum
u8028-1
u160216-1
u320232-1
u640264-1
u12802128-1
u25602256-1

The signed two's complement integer types consist of:

TypeMinimumMaximum
i8-(27)27-1
i16-(215)215-1
i32-(231)231-1
i64-(263)263-1
i128-(2127)2127-1
i256-(2255)2255-1

Tuple Types

Syntax
TupleType :
      ( )
   | ( ( Type , )+ Type? )

Tuple types are a family of structural types1 for heterogeneous lists of other types.

The syntax for a tuple type is a parenthesized, comma-separated list of types.

A tuple type has a number of fields equal to the length of the list of types. This number of fields determines the arity of the tuple. A tuple with n fields is called an n-ary tuple. For example, a tuple with 2 fields is a 2-ary tuple.

Fields of tuples are named using increasing numeric names matching their position in the list of types. The first field is item0. The second field is item1. And so on. The type of each field is the type of the same position in the tuple's list of types.

For convenience and historical reasons, the tuple type with no fields (()) is often called unit or the unit type. Its one value is also called unit or the unit value.

Some examples of tuple types:

  • () (also known as the unit or zero-sized type)
  • (u8, u8)
  • (bool, i32)
  • (i32, bool) (different type from the previous example)

Values of this type are constructed using a tuple expression. Furthermore, various expressions will produce the unit value if there is no other meaningful value for it to evaluate to. Tuple fields can be accessed via an attribute expression.

1

Structural types are always equivalent if their internal types are equivalent.

Array types

Syntax
ArrayType :
   Array<Type, INTEGER_LITERAL>

An array is a fixed-size sequence of N elements of type T. The array type is written as Array<T, N>. The size is an integer literal.

Arrays are either stored in storage or memory but are never stored directly on the stack.

Examples:

contract Foo {
  # An array in storage
  bar: Array<u8, 10>

  fn do_something() {
    # An array in memory
    let values: Array<u256, 3> = [10, 100, 100]
  }
}

All elements of arrays are always initialized, and access to an array is always bounds-checked in safe methods and operators.

Struct types

A struct type is the type denoted by the name of an struct item. A struct type is a heterogeneous product of other types, called the fields of the type.

New instances of a struct can be constructed with a struct expression.

Struct types are either stored in storage or memory but are never stored directly on the stack.

Examples:

struct Rectangle {
  pub width: u256
  pub length: u256
}

contract Example {
  # A Rectangle in storage
  area: Rectangle

  fn do_something() {
    let length: u256 = 20
    # A rectangle in memory
    let square: Rectangle = Rectangle(width: 10, length)
  }
}

All fields of struct types are always initialized.

The data layout of a struct is not part of its external API and may be changed in any release.

The fields of a struct may be qualified by visibility modifiers, to allow access to data in a struct outside a module.

Enum types

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

Address Type

The address type represents a 20 byte Ethereum address.

Example:

contract Example {
  # An address in storage
  someone: address

  fn do_something() {
    # A plain address (not part of a tuple, struct etc) remains on the stack
    let dai_contract: address = address(0x6b175474e89094c44da98b954eedeac495271d0f)
  }
}

Map type

The type Map<K, V> is used to associate key values with data.

The following types can be used as key:

The values can be of any type including other maps, structs, tuples or arrays.

Example:

contract Foo {
    bar: Map<address, Map<address, u256>>
    baz: Map<address, Map<u256, bool>>

    pub fn read_bar(self, a: address, b: address) -> u256 {
        return self.bar[a][b]
    }

    pub fn write_bar(self, a: address, b: address, value: u256) {
        self.bar[a][b] = value
    }

    pub fn read_baz(self, a: address, b: u256) -> bool {
        return self.baz[a][b]
    }

    pub fn write_baz(self, a: address, b: u256, value: bool) {
        self.baz[a][b] = value
    }
}

String Type

A value of type String<N> represents a sequence of unsigned bytes with the assumption that the data is valid UTF-8.

The String<N> type is generic over a constant value that has to be an integer literal. That value N constraints the maximum number of bytes that are available for storing the string's characters.

Note that the value of N does not restrict the type to hold exactly that number of bytes at all times which means that a type such as String<10> can hold a short word such as "fox" but it can not hold a full sentence such as "The brown fox jumps over the white fence".

Example:

contract Foo {

  fn bar() {
    let single_byte_string: String<1> = "a"
    let longer_string: String<100> = "foo"
  }
}

Unit type

TBW

Event types

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

Function Types

TBW

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 can be stored in these locations. How data is stored is described in this section.

Stack

The following can be stored on the stack:

  • base type values
  • pointers to reference 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 or storage may also be stored on the stack.

Example:

fn f() {
    let foo: u256 = 42 # foo is stored on the stack
    let bar: Array<u256, 100> # bar is a memory pointer stored on the stack
}

Storage

All data types can be stored in storage.

Constant size values in storage

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

Example:

contract Cats {
   population: u256 # assigned a static location 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.

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.

Example:

contract Foo {
  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).

The to_mem function

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

Example:

let my_array_var: Array<u256, 10> = self.my_array_field.to_mem()

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.

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.

Example:

fn f() {
    let foo: Array<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.

The clone function

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

Example:

fn f() {
    # with clone
    let bar: Array<u256, 4> = [1, 2, 3, 4]
    let foo: Array<u256, 4> = 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
}

fn g() {
    # without clone
    let bar: Array<u256, 4> = [1, 2, 3, 4]
    let foo: Array<u256, 4> = 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`
}

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.18.0-alpha "Ruby" (2022-05-27)

Features

  • Added support for parsing of attribute calls with generic arguments (e.g. foo.bar<Baz>()). (#719)

Bugfixes

  • Fix a regression where the stateMutability field would not be included in the generated ABI (#722)
  • Fix two regressions introduced in 0.17.0
    • Properly lower right shift operation to yul's sar if operand is signed type
    • Properly lower negate operation to call safe_sub (#723)

0.17.0-alpha "Quartz" (2022-05-26)

Features

  • Support for underscores in numbers to improve readability e.g. 100_000.

    Example

        let num: u256 = 1000_000_000_000
    

    (#149)

  • Optimized access of struct fields in storage (#249)

  • Unit type () is now ABI encodable (#442)

  • Temporary default stateMutability to payable in ABI

    The ABI metadata that the compiler previously generated did not include the stateMutability field. This piece of information is important for tooling such as hardhat because it determines whether a function needs to be called with or without sending a transaction.

    As soon as we have support for mut self and mut ctx we will be able to derive that information from the function signature. In the meantime we now default to payable. (#705)

Bugfixes

  • Fixed a crash caused by certain memory to memory assignments.

    E.g. the following code would previously lead to a compiler crash:

    my_struct.x = my_struct.y
    

    (#590)

  • Reject unary minus operation if the target type is an unsigned integer number.

    Code below should be reject by fe compiler:

    contract Foo:
        pub fn bar(self) -> u32:
            let unsigned: u32 = 1
            return -unsigned
      
        pub fn foo():
            let a: i32 = 1
            let b: u32 = -a
    

    (#651)

  • Fixed crash when passing a struct that contains an array

    E.g. the following would previously result in a compiler crash:

    struct MyArray:
        pub x: Array<i32, 2>
      
      
    contract Foo:
        pub fn bar(my_arr: MyArray):
            pass
    

    (#681)

  • reject infinite size struct definitions.

    Fe structs having infinite size due to recursive definitions were not rejected earlier and would cause ICE in the analyzer since they were not properly handled. Now structs having infinite size are properly identified by detecting cycles in the dependency graph of the struct field definitions and an error is thrown by the analyzer. (#682)

  • Return instead of revert when contract is called without data.

    If a contract is called without data so that no function is invoked, we would previously revert but that would leave us without a way to send ETH to a contract so instead it will cause a return now. (#694)

  • Resolve compiler crash when using certain reserved YUL words as struct field names.

    E.g. the following would previously lead to a compiler crash because numer is a reserved keyword in YUL.

    struct Foo:
      pub number: u256
    
    contract Meh:
    
      pub fn yay() -> Foo:
        return Foo(number:2)
    

    (#709)

0.16.0-alpha (2022-05-05)

Features

  • Change static function call syntax from Bar.foo() to Bar::foo() (#241)
  • Added support for retrieving the base fee via ctx.base_fee() (#503)

Bugfixes

  • Resolve functions on structs via path (e.g. bi::ba::bums()) (#241)

0.15.0-alpha (2022-04-04)

Features

  • Labels are now required on function arguments. Labels can be omitted if the argument is a variable with a name that matches the label, or if the function definition specifies that an argument should have no label. Functions often take several arguments of the same type; compiler-checked labels can help prevent accidentally providing arguments in the wrong order.

    Example:

    contract CoolCoin:
      balance: Map<address, i256>
      loans: Map<(address, address), i256>
    
      pub fn demo(self, ann: address, bob: address):
        let is_loan: bool = false
        self.give(from: ann, to: bob, 100, is_loan)
    
      fn transfer(self, from sender: address, to recipient: address, _ val: u256, is_loan: bool):
        self.cred[sender] -= val
        self.cred[recipient] += val
        if is_loan:
          self.loans[(sender, recipient)] += val
    

    Note that arguments must be provided in the order specified in the function definition.

    A parameter's label defaults to the parameter name, but can be changed by specifying a different label to the left of the parameter name. Labels should be clear and convenient for the caller, while parameter names are only used in the function body, and can thus be longer and more descriptive. In the example above, we choose to use sender and recipient as identifiers in the body of fn transfer, but use labels from: and to:.

    In cases where it's ideal to not have labels, e.g. if a function takes a single argument, or if types are sufficient to differentiate between arguments, use _ to specify that a given parameter has no label. It's also fine to require labels for some arguments, but not others.

    Example:

    fn add(_ x: u256, _ y: u256) -> u256:
      return x + y
    
    contract Foo:
      fn transfer(self, _ to: address, wei: u256):
        pass
    
      pub fn demo(self):
        transfer(address(0), wei: add(1000, 42))
    ``` ([#397](https://github.com/ethereum/fe/issues/397))
    
    
    

Bugfixes

  • The region of memory used to compute the slot of a storage map value was not being allocated. (#684)

0.14.0-alpha (2022-03-02)

Features

  • Events can now be defined outside of contracts.

    Example:

    event Transfer:
        idx sender: address
        idx receiver: address
        value: u256
    
    contract Foo:
        fn transferFoo(to: address, value: u256):
            emit Transfer(sender: msg.sender, receiver: to, value)
    
    contract Bar:
        fn transferBar(to: address, value: u256):
            emit Transfer(sender: msg.sender, receiver: to, value)
    ``` ([#80](https://github.com/ethereum/fe/issues/80))
    
  • The Fe standard library now includes a std::evm module, which provides functions that perform low-level evm operations. Many of these are marked unsafe, and thus can only be used inside of an unsafe function or an unsafe block.

    Example:

    use std::evm::{mstore, mload}
    
    fn memory_shenanigans():
      unsafe:
        mstore(0x20, 42)
        let x: u256 = mload(0x20)
        assert x == 42
    

    The global functions balance and balance_of have been removed; these can now be called as std::evm::balance(), etc. The global function send_value has been ported to Fe, and is now available as std::send_value. (#629)

  • Support structs that have non-base type fields in storage.

    Example:

    struct Point:
        pub x: u256
        pub y: u256
    
    struct Bar:
        pub name: String<3>
        pub numbers: Array<u256, 2>
        pub point: Point
        pub something: (u256, bool)
    
    
    contract Foo:
        my_bar: Bar
    
        pub fn complex_struct_in_storage(self) -> String<3>:
            self.my_bar = Bar(
                name: "foo",
                numbers: [1, 2],
                point: Point(x: 100, y: 200),
                something: (1, true),
            )
    
            # Asserting the values as they were set initially
            assert self.my_bar.numbers[0] == 1
            assert self.my_bar.numbers[1] == 2
            assert self.my_bar.point.x == 100
            assert self.my_bar.point.y == 200
            assert self.my_bar.something.item0 == 1
            assert self.my_bar.something.item1
    
            # We can change the values of the array
            self.my_bar.numbers[0] = 10
            self.my_bar.numbers[1] = 20
            assert self.my_bar.numbers[0] == 10
            assert self.my_bar.numbers[1] == 20
            # We can set the array itself
            self.my_bar.numbers = [1, 2]
            assert self.my_bar.numbers[0] == 1
            assert self.my_bar.numbers[1] == 2
    
            # We can change the values of the Point
            self.my_bar.point.x = 1000
            self.my_bar.point.y = 2000
            assert self.my_bar.point.x == 1000
            assert self.my_bar.point.y == 2000
            # We can set the point itself
            self.my_bar.point = Point(x=100, y=200)
            assert self.my_bar.point.x == 100
            assert self.my_bar.point.y == 200
    
            # We can change the value of the tuple
            self.my_bar.something.item0 = 10
            self.my_bar.something.item1 = false
            assert self.my_bar.something.item0 == 10
            assert not self.my_bar.something.item1
            # We can set the tuple itself
            self.my_bar.something = (1, true)
            assert self.my_bar.something.item0 == 1
            assert self.my_bar.something.item1
    
            return self.my_bar.name.to_mem()
    ``` ([#636](https://github.com/ethereum/fe/issues/636))
    
  • Features that read and modify state outside of contracts are now implemented on a struct named "Context". Context is included in the standard library and can be imported with use std::context::Context. Instances of Context are created by calls to public functions that declare it in the signature or by unsafe code.

    Basic example:

    use std::context::Context
    
    contract Foo:
        my_num: u256
    
        pub fn baz(ctx: Context) -> u256:
            return ctx.block_number()
    
        pub fn bing(self, new_num: u256) -> u256:
            self.my_num = new_num
            return self.my_num
    
    
    contract Bar:
    
        pub fn call_baz(ctx: Context, foo_addr: address) -> u256:
            # future syntax: `let foo = ctx.load<Foo>(foo_addr)`
            let foo: Foo = Foo(ctx, foo_addr)
            return foo.baz()
    
        pub fn call_bing(ctx: Context) -> u256:
            # future syntax: `let foo = ctx.create<Foo>(0)`
            let foo: Foo = Foo.create(ctx, 0)
            return foo.bing(42)
    

    Example with __call__ and unsafe block:

    use std::context::Context
    use std::evm
    
    contract Foo:
    
        pub fn __call__():
            unsafe:
                # creating an instance of `Context` is unsafe
                let ctx: Context = Context()
                let value: u256 = u256(bar(ctx))
    
                # return `value`
                evm::mstore(0, value)
                evm::return_mem(0, 32)
    
        fn bar(ctx: Context) -> address:
            return ctx.self_address()
    ``` ([#638](https://github.com/ethereum/fe/issues/638))
    
  • Features

    Support local constant

    Example:

    contract Foo:
        pub fn bar():
            const LOCAL_CONST: i32 = 1
    

    Support constant expression

    Example:

    const GLOBAL: i32 = 8
    
    contract Foo:
        pub fn bar():
            const LOCAL: i32 = GLOBAL * 8
    

    Support constant generics expression

    Example:

    const GLOBAL: u256= 8
    const USE_GLOBAL: bool = false
    type MY_ARRAY = Array<i32, { GLOBAL / 4 }>
    
    contract Foo:
        pub fn bar():
            let my_array: Array<i32, { GLOBAL if USE_GLOBAL else 4 }>
    

    Bug fixes

    Fix ICE when constant type is mismatch

    Example:

    const GLOBAL: i32 = "FOO"
    
    contract Foo:
        pub fn bar():
            let FOO: i32 = GLOBAL
    

    Fix ICE when assigning value to constant twice

    Example:

    const BAR: i32 = 1
    
    contract FOO:
        pub fn bar():
            BAR = 10
    ``` ([#649](https://github.com/ethereum/fe/issues/649))
    
  • Argument label syntax now uses : instead of =. Example:

    struct Foo:
      x: u256
      y: u256
    
    let x: MyStruct = MyStruct(x: 10, y: 11)
    # previously:     MyStruct(x = 10, y = 11)
    ``` ([#665](https://github.com/ethereum/fe/issues/665))
    
  • Support module-level pub modifier, now default visibility of items in a module is private.

    Example:

    # This constant can be used outside of the module.
    pub const PUBLIC:i32 = 1
    
    # This constant can NOT be used outside of the module.
    const PRIVATE: i32 = 1
    ``` ([#677](https://github.com/ethereum/fe/issues/677))
    
    
    

Internal Changes - for Fe Contributors

    • Source files are now managed by a (salsa) SourceDb. A SourceFileId now corresponds to a salsa-interned File with a path. File content is a salsa input function. This is mostly so that the future (LSP) language server can update file content when the user types or saves, which will trigger a re-analysis of anything that changed.
    • An ingot's set of modules and dependencies are also salsa inputs, so that when the user adds/removes a file or dependency, analysis is rerun.
    • Standalone modules (eg a module compiled with fe fee.fe) now have a fake ingot parent. Each Ingot has an IngotMode (Lib, Main, StandaloneModule), which is used to disallow ingot::whatever paths in standalone modules, and to determine the correct root module file.
    • parse_module now always returns an ast::Module, and thus a ModuleId will always exist for a source file, even if it contains fatal parse errors. If the parsing fails, the body will end with a ModuleStmt::ParseError node. The parsing will stop at all but the simplest of syntax errors, but this at least allows partial analysis of source file with bad syntax.
    • ModuleId::ast(db) is now a query that parses the module's file on demand, rather than the AST being interned into salsa. This makes handling parse diagnostics cleaner, and removes the up-front parsing of every module at ingot creation time. (#628)

0.13.0-alpha (2022-01-31)## 0.13.0-alpha (2022-01-31)

Features

  • Support private fields on structs

    Public fields now need to be declared with the pub modifier, otherwise they default to private fields. If a struct contains private fields it can not be constructed directly except from within the struct itself. The recommended way is to implement a method new(...) as demonstrated in the following example.

    struct House:
        pub price: u256
        pub size: u256
        vacant: bool
    
        pub fn new(price: u256, size: u256) -> House
          return House(price=price, size=size, vacant=true)
    
    contract Manager:
    
      house: House
    
      pub fn create_house(price: u256, size: u256):
        self.house = House::new(price, size)
        let can_access_price: u256 = self.house.price
        # can not access `self.house.vacant` because the field is private
    ``` ([#214](https://github.com/ethereum/fe/issues/214))
    
  • Support non-base type fields in structs

    Support is currently limited in two ways:

    • Structs with complex fields can not be returned from public functions
    • Structs with complex fields can not be stored in storage (#343)
  • Addresses can now be explicitly cast to u256. For example:

    fn f(addr: address) -> u256:
      return u256(addr)
    ``` ([#621](https://github.com/ethereum/fe/issues/621))
    
  • A special function named __call__ can now be defined in contracts.

    The body of this function will execute in place of the standard dispatcher when the contract is called.

    example (with intrinsics):

    contract Foo:
        pub fn __call__(self):
            unsafe:
                if __calldataload(0) == 1:
                    __revert(0, 0)
                else:
                    __return(0, 0)
    ``` ([#622](https://github.com/ethereum/fe/issues/622))
    
    
    

Bugfixes

  • Fixed a crash that happend when using a certain unprintable ASCII char (#551)

  • The argument to revert wasn't being lowered by the compiler, meaning that some revert calls would cause a compiler panic in later stages. For example:

    const BAD_MOJO: u256 = 0xdeaddead
    
    struct Error:
      code: u256
    
    fn fail():
      revert Error(code = BAD_MOJO)
    ``` ([#619](https://github.com/ethereum/fe/issues/619))
    
  • Fixed a regression where an empty list expression ([]) would lead to a compiler crash. (#623)

  • Fixed a bug where int array elements were not sign extended in their ABI encodings. (#633)

0.12.0-alpha (2021-12-31)## 0.12.0-alpha (2021-12-31)

Features

  • Added unsafe low-level "intrinsic" functions, that perform raw evm operations. For example:

    fn foo():
      unsafe:
        __mtore(0, 5000)
        assert __mload(0) == 5000
    

    The functions available are exactly those defined in yul's "evm dialect": https://docs.soliditylang.org/en/v0.8.11/yul.html#evm-dialect but with a double-underscore prefix. Eg selfdestruct -> __selfdestruct.

    These are intended to be used for implementing basic standard library functionality, and shouldn't typically be needed in normal contract code.

    Note: some intrinsic functions don't return a value (eg __log0); using these functions in a context that assumes a return value of unit type (eg let x: () = __log0(a, b)) will currently result in a compiler panic in the yul compilation phase. (#603)

  • Added an out of bounds check for accessing array items. If an array index is retrieved at an index that is not within the bounds of the array it now reverts with Panic(0x32). (#606)

Bugfixes

  • Ensure ternary expression short circuit.

    Example:

    contract Foo:
    
        pub fn bar(input: u256) -> u256:
            return 1 if input > 5 else revert_me()
    
        fn revert_me() -> u256:
            revert
            return 0
    

    Previous to this change, the code above would always revert no matter which branch of the ternary expressions it would resolve to. That is because both sides were evaluated and then one side was discarded. With this change, only the branch that doesn't get picked won't get evaluated at all.

    The same is true for the boolean operations and and or. (#488)

Internal Changes - for Fe Contributors

  • Added a globally available dummy std lib.

    This library contains a single get_42 function, which can be called using std::get_42(). Once low-level intrinsics have been added to the language, we can delete get_42 and start adding useful code. (#601)

0.11.0-alpha "Karlite" (2021-12-02)

Features

  • Added support for multi-file inputs.

    Implementation details:

    Mostly copied Rust's crate system, but use the the term ingot instead of crate.

    Below is an example of an ingot's file tree, as supported by the current implementation.

    `-- basic_ingot
        `-- src
            |-- bar
            |   `-- baz.fe
            |-- bing.fe
            |-- ding
            |   |-- dang.fe
            |   `-- dong.fe
            `-- main.fe
    

    There are still a few features that will be worked on over the coming months:

    • source files accompanying each directory module (e.g. my_mod.fe)
    • configuration files and the ability to create library ingots
    • test directories
    • module-level pub modifier (all items in a module are public)
    • mod statements (all fe files in the input tree are public modules)

    These things will be implemented in order of importance over the next few months. (#562)

  • The syntax for array types has changed to match other generic types. For example, u8[4] is now written Array<u8, 4>. (#571)

  • Functions can now be defined on struct types. Example:

    struct Point:
      x: u64
      y: u64
    
      # Doesn't take `self`. Callable as `Point.origin()`.
      # Note that the syntax for this will soon be changed to `Point::origin()`.
      pub fn origin() -> Point:
        return Point(x=0, y=0)
    
      # Takes `self`. Callable on a value of type `Point`.
      pub fn translate(self, x: u64, y: u64):
        self.x += x
        self.y += y
    
      pub fn add(self, other: Point) -> Point:
        let x: u64 = self.x + other.x
        let y: u64 = self.y + other.y
        return Point(x, y)
    
      pub fn hash(self) -> u256:
        return keccak256(self.abi_encode())
    
    pub fn do_pointy_things():
      let p1: Point = Point.origin()
      p1.translate(5, 10)
    
      let p2: Point = Point(x=1, y=2)
      let p3: Point = p1.add(p2)
    
      assert p3.x == 6 and p3.y == 12
    ``` ([#577](https://github.com/ethereum/fe/issues/577))
    
    
    

Bugfixes

  • Fixed a rare compiler crash.

    Example:

    let my_array: i256[1] = [-1 << 1] 
    

    Previous to this fix, the given example would lead to an ICE. (#550)

  • Contracts can now create an instance of a contract defined later in a file. This issue was caused by a weakness in the way we generated yul. (#596)

Internal Changes - for Fe Contributors

  • File IDs are now attached to Spans. (#587)

  • The fe analyzer now builds a dependency graph of source code "items" (functions, contracts, structs, etc). This is used in the yulgen phase to determine which items are needed in the yul (intermediate representation) output. Note that the yul output is still cluttered with utility functions that may or may not be needed by a given contract. These utility functions are defined in the yulgen phase and aren't tracked in the dependency graph, so it's not yet possible to filter out the unused functions. We plan to move the definition of many of these utility functions into fe; when this happens they'll become part of the dependency graph and will only be included in the yul output when needed.

    The dependency graph will also enable future analyzer warnings about unused code. (#596)

0.10.0-alpha (2021-10-31)

Features

  • Support for module level constants for base types

    Example:

    const TEN = 10
    
    contract
    
      pub fn do_moon_math(self) -> u256:
        return 4711 * TEN
    

    The values of base type constants are always inlined. (#192)

  • Encode revert errors for ABI decoding as Error(0x103) not Panic(0x99) (#492)

  • Replaced import statements with use statements.

    Example:

    use foo::{bar::*, baz as baz26}
    

    Note: this only adds support for parsing use statements. (#547)

  • Functions can no be defined outside of contracts. Example:

    fn add_bonus(x: u256) -> u256:
        return x + 10
    
    contract PointTracker:
        points: Map<address, u256>
    
        pub fn add_points(self, user: address, val: u256):
            self.points[user] += add_bonus(val)
    ``` ([#566](https://github.com/ethereum/fe/issues/566))
    
  • Implemented a send_value(to: address, value_in_wei: u256) function.

    The function is similar to the sendValue function by OpenZeppelin with the differences being that:

    1. It reverts with Error(0x100) instead of Error("Address: insufficient balance") to safe more gas.

    2. It uses selfbalance() instead of balance(address()) to safe more gas

    3. It reverts with Error(0x101) instead of Error("Address: unable to send value, recipient may have reverted") also to safe more gas. (#567)

  • Added support for unsafe functions and unsafe blocks within functions. Note that there's currently no functionality within Fe that requires the use of unsafe, but we plan to add built-in unsafe functions that perform raw evm operations which will only callable within an unsafe block or function. (#569)

  • Added balance() and balance_of(account: address) methods. (#572)

  • Added support for explicit casting between numeric types.

    Example:

    let a: i8 = i8(-1)
    let a1: i16 = i16(a)
    let a2: u16 = u16(a1)
    
    assert a2 == u16(65535)
    
    let b: i8 = i8(-1)
    let b1: u8 = u8(b)
    let b2: u16 = u16(b1)
    
    assert b2 == u16(255)
    

    Notice that Fe allows casting between any two numeric types but does not allow to change both the sign and the size of the type in one step as that would leave room for ambiguity as the example above demonstrates. (#576)

Bugfixes

  • Adjust numeric values loaded from memory or storage

    Previous to this fix numeric values that were loaded from either memory or storage were not properly loaded on the stack which could result in numeric values not treated as intended.

    Example:

    contract Foo:
    
        pub fn bar() -> i8:
            let in_memory: i8[1] = [-3]
            return in_memory[0]
    

    In the example above bar() would not return -3 but 253 instead. (#524)

  • Propagate reverts from external contract calls.

    Before this fix the following code to should_revert() or should_revert2() would succeed even though it clearly should not.

    contract A:
      contract_b: B
      pub fn __init__(contract_b: address):
        self.contract_b = B(contract_b)
    
      pub fn should_revert():
        self.contract_b.fail()
    
      pub fn should_revert2():
        self.contract_b.fail_with_custom_error()
    
    struct SomeError:
      pass
    
    contract B:
    
      pub fn fail():
        revert
    
      pub fn fail_with_custom_error():
        revert SomeError()
    

    With this fix the revert errors are properly passed upwards the call hierachy. (#574)

  • Fixed bug in left shift operation.

    Example:

    Let's consider the value 1 as an u8 which is represented as the following 256 bit item on the EVM stack 00..|00000001|. A left shift of 8 bits (val << 8) turns that into 00..01|00000000|.

    Previous to this fix this resulted in the compiler taking 256 as the value for the u8 when clearly 256 is not even in the range of u8 anymore. With this fix the left shift operations was fixed to properly "clean up" the result of the shift so that 00..01|00000000| turns into 00..00|00000000|. (#575)

  • Ensure negation is checked and reverts with over/underflow if needed.

    Example:

    The minimum value for an i8 is -128 but the maximum value of an i8 is 127 which means that negating -128 should lead to an overflow since 128 does not fit into an i8. Before this fix, negation operations where not checked for over/underflow resulting in returning the oversized value. (#578)

Internal Changes - for Fe Contributors

  • In the analysis stage, all name resolution (of variable names, function names, type names, etc used in code) now happens via a single resolve_name pathway, so we can catch more cases of name collisions and log more helpful error messages. (#555)

  • Added a new category of tests: differential contract testing.

    Each of these tests is pased on a pair of contracts where one implementation is written in Fe and the other one is written in Solidity. The implementations should have the same public APIs and are assumed to always return identical results given equal inputs. The inputs are randomly generated using proptest and hence are expected to discover unknown bugs. (#578)

0.9.0-alpha (2021-09-29)

Features

  • The self variable is no longer implicitly defined in code blocks. It must now be declared as the first parameter in a function signature.

    Example:

    contract Foo:
        my_stored_num: u256
    
        pub fn bar(self, my_num: u256):
            self.my_stored_num = my_num
          
        pub fn baz(self):
            self.bar(my_pure_func())
          
        pub fn my_pure_func() -> u256:
            return 42 + 26
    ``` ([#520](https://github.com/ethereum/fe/issues/520))
    
  • The analyzer now disallows defining a type, variable, or function whose name conflicts with a built-in type, function, or object.

    Example:

    error: type name conflicts with built-in type
    ┌─ compile_errors/shadow_builtin_type.fe:1:6
    │
    1 │ type u256 = u8
    │      ^^^^ `u256` is a built-in type
    ``` ([#539](https://github.com/ethereum/fe/issues/539))
    
    
    

Bugfixes

  • Fixed cases where the analyzer would correctly reject code, but would panic instead of logging an error message. (#534)
  • Non-fatal parser errors (eg missing parentheses when defining a function that takes no arguments: fn foo:) are no longer ignored if the semantic analysis stage succeeds. (#535)
  • Fixed issue #531 by adding a $ to the front of lowered tuple names. (#546)

Internal Changes - for Fe Contributors

  • Implemented pretty printing of Fe AST. (#540)

0.8.0-alpha "Haxonite" (2021-08-31)

Features

  • Support quotes, tabs and carriage returns in string literals and otherwise restrict string literals to the printable subset of the ASCII table. (#329)

  • The analyzer now uses a query-based system, which fixes some shortcomings of the previous implementation.

    • Types can now refer to other types defined later in the file. Example:
    type Posts = Map<PostId, PostBody>
    type PostId = u256
    type PostBody = String<140>
    
    • Duplicate definition errors now show the location of the original definition.
    • The analysis of each function, type definition, etc happens independently, so an error in one doesn't stop the analysis pass. This means fe can report more user errors in a single run of the compiler. (#468)
  • Function definitions are now denoted with the keyword fn instead of def. (#496)

  • Variable declarations are now preceded by the let keyword. Example: let x: u8 = 1. (#509)

  • Implemented support for numeric unary invert operator (~) (#526)

Bugfixes

  • Calling self.__init__() now results in a nice error instead of a panic in the yul compilation stage. (#468)

  • Fixed an issue where certain expressions were not being moved to the correct location. (#493)

  • Fixed an issue with a missing return statement not properly detected.

    Previous to this fix, the following code compiles but it should not:

    contract Foo:
        pub fn bar(val: u256) -> u256:
            if val > 1:
                return 5
    

    With this change, the compiler rightfully detects that the code is missing a return or revert statement after the if statement since it is not guaranteed that the path of execution always follows the arm of the if statement. (#497)

  • Fixed a bug in the analyzer which allowed tuple item accessor names with a leading 0, resulting in an internal compiler error in a later pass. Example: my_tuple.item001. These are now rejected with an error message. (#510)

  • Check call argument labels for function calls.

    Previously the compiler would not check any labels that were used when making function calls on self or external contracts.

    This can be especially problematic if gives developers the impression that they could apply function arguments in any order as long as they are named which is not the case.

    contract Foo:
    
        pub fn baz():
            self.bar(val2=1, doesnt_even_exist=2)
      
        pub fn bar(val1: u256, val2: u256):
            pass
    

    Code as the one above is now rightfully rejected by the compiler. (#517)

Improved Documentation

  • Various improvements and bug fixes to both the content and layout of the specification. (#489)

  • Document all remaining statements and expressions in the spec.

    Also added a CI check to ensure code examples in the documentation are validated against the latest compiler. (#514)

Internal Changes - for Fe Contributors

  • Separated Fe type traits between crates. (#485)

0.7.0-alpha "Galaxite" (2021-07-27)

Features

  • Enable the optimizer by default. The optimizer can still be disabled by supplying --optimize=false as an argument. (#439)

  • The following checks are now performed while decoding data:

    • The size of the encoded data fits within the size range known at compile-time.
    • Values are correctly padded.
      • unsigned integers, addresses, and bools are checked to have correct left zero padding
      • the size of signed integers are checked
      • bytes and strings are checked to have correct right padding
    • Data section offsets are consistent with the size of preceding values in the data section.
    • The dynamic size of strings does not exceed their maximum size.
    • The dynamic size of byte arrays (u8[n]) is equal to the size of the array. (#440)
  • Type aliases can now include tuples. Example:

    type InternetPoints = (address, u256)
    

    (#459)

  • Revert with custom errors

    Example:

    struct PlatformError:
      code: u256
    
    pub fn do_something():
      revert PlatformError(code=4711)
    

    Error encoding follows Solidity which is based on EIP-838. This means that custom errors returned from Fe are fully compatible with Solidity. (#464)

    • The builtin value msg.sig now has type u256.
    • Removed the bytes[n] type. The type u8[n] can be used in its placed and will be encoded as a dynamically-sized, but checked, bytes component. (#472)
  • Encode certain reverts as panics.

    With this change, the following reverts are encoded as Panic(uint256) with the following panic codes:

    • 0x01: An assertion that failed and did not specify an error message
    • 0x11: An arithmetic expression resulted in an over- or underflow
    • 0x12: An arithmetic expression divided or modulo by zero

    The panic codes are aligned with the panic codes that Solidity uses. (#476)

Bugfixes

  • Fixed a crash when trying to access an invalid attribute on a string.

    Example:

    contract Foo:
    
      pub fn foo():
        "".does_not_exist
    

    The above now yields a proper user error. (#444)

  • Ensure String<N> type is capitalized in error messages (#445)

  • Fixed ICE when using a static string that spans over multiple lines.

    Previous to this fix, the following code would lead to a compiler crash:

    contract Foo:
        pub fn return_with_newline() -> String<16>:
            return "foo
            balu"
    

    The above code now works as intended. (#448)

  • Fixed ICE when using a tuple declaration and specifying a non-tuple type. Fixed a second ICE when using a tuple declaration where the number of target items doesn't match the number of items in the declared type. (#469)

Internal Changes - for Fe Contributors

    • Cleaned up ABI encoding internals.
    • Improved yulc panic formatting. (#472)

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

Features

  • Support for pragma statement

    Example: pragma ^0.1.0 (#361)

  • Add support for tuple destructuring

    Example:

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

    (#376)

    1. Call expression can now accept generic arguments
    2. Replace stringN to String<N>

    Example:

    s: String<10> = String<10>("HI")
    

    (#379)

    • 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:
    
      fn 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
    
      fn g():
        self.f(50)
    

    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:
      fn f(xs: u8[10]):
        pass
    
      fn g():
        self.f([])
    

    (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>>
    

    (#431)

  • 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)

Features

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

    Example:

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

    (#333)

  • Added support for list expressions.

    Example:

    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])
    

    (#388)

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

    e.g.

    event MyEvent:
        pass
    
    ...
      
    contract MyContract:
        pass
     
    ...
      
    struct MyStruct:
        pass
    

    (#406)

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

Bugfixes

  • 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 https://fe-lang.org (#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)

Features

  • Support for revert messages in assert statements

    E.g

    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
    

    (#288)

  • Added support for augmented assignments.

    e.g.

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

    (#338)

  • 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.

    e.g.

    contract Foo:
        my_num: u256
    
        pub fn 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
    

    (#352)

Bugfixes

  • 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 following code is now rightfully rejected because it tries to create an instance of Foo from within the Foo contract itself.

    contract Foo:
      pub fn bar()->address:
        foo:Foo=Foo.create(0)
    
        return address(foo)
    

    (#362)

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)

Features

  • 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 fn explicit_return_a1():
        return
    
      pub fn explicit_return_a2():
        return ()
    
      pub fn explicit_return_b1() ->():
        return
    
      pub fn explicit_return_b2() ->():
        return ()
    
      pub fn implicit_a1():
        pass
    
      pub fn implicit_a2() ->():
        pass
    
  • 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 fn external_bar() -> u256:
            return self.bar.bar()
    contract Bar:
        foo: Foo
        pub fn external_foo() -> u256:
            return self.foo.foo()
    
  • 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)

Bugfixes

  • 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 https://github.com/ethereum/fe/issues/284 (#304)

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

    Examples that now produce compile time errors:

    type bar = u8
    type bar = u16
    

    or

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

    or

    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 fn 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.

    i.e.

    # Would both overwrite 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 fn __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)

Features

  • Add support for string literals.

    Example:

    fn 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:

    output
    |-- 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)

    Example:

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

    Example:

    struct House:
        price: u256
        size: u256
        vacant: bool
    
    contract City:
    
        pub fn 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)

    Example:

    contract Foo:
        pub fn 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 fn 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
    chain.id: u256
    msg.value: u256
    tx.gas_price: u256
    tx.origin: address
    

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

    Example:

    fn 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 fn get_my_num() -> u256:
            return 42
    
    contract FooFactory:
        pub fn 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 fn get_my_num() -> u256:
            return 42
    
    contract FooFactory:
        pub fn 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)

    Example:

     pub fn 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)

    Example:

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

    Example:

    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)

    Example:

    if not covid_test.is_positive(person):
        allow_boarding(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 structs. 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)

    Example:

    struct House:
        price: u256
        size: u256
        rooms: u8
        vacant: bool
      
    contract Foo:
        pub fn hashed_house() -> u256:
            house: House = House(
                price=300,
                size=500,
                rooms=u8(20),
                vacant=true
            )
            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)

    Examples:

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

    Support for self.address.

    This expression returns the address of the current contract.

    Example:

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

Bugfixes

  • 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 fn 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 fn explicit_return():
        return
    
      pub fn implicit():
        pass
    

    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.

Features

  • 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)

    usage:

    # 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 fn 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.

Scope

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.

Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at piper@pipermerriam.com. 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.

Attribution

This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.

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 https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.