Releases: ethereum/fe
v0.26.0
0.26.0 "Zircon" (2023-11-03)
Features
-
Give option to produce runtime bytecode as compilation artifact
Previously, the compiler could only produce the bytecode that is used
for the deployment of the contract. Now it can also produce the runtime
bytecode which is the bytecode that is saved to storage.Being able to obtain the runtime bytecode is useful for contract
verification.To obtain the runtime bytecode use the
runtime-bytecode
option
of the--emit
flag (multiple options allowed).Example Output:
- mycontract.bin (bytecode for deployment)
- mycontract.runtime.bin (runtime bytecode) (#947)
-
New
verify
command to verify onchain contracts against local source code.People need to be able to verify that a deployed contract matches the source code
that the author claims was used to deploy it. Previously, there was no simple
way to achieve this.These are the steps to verify a contract with the
verify
command:- Obtain the project's source code locally.
- Ensure it is the same source code that was used to deploy the contract. (e.g. check out a specific tag)
- From the project directory run
fe verify <contract-address> <json-rpc-url>
Example:
$ fe verify 0xf0adbb9ed4135d1509ad039505bada942d18755f https://example-eth-mainnet-rpc.com It's a match!✨ Onchain contract: Address: 0xf0adbb9ed4135d1509ad039505bada942d18755f Bytecode: 0x60008..76b90 Local contract: Contract name: SimpleDAO Source file: /home/work/ef/simple_dao/fe_contracts/simpledao/src/main.fe Bytecode: 0x60008..76b90 Hint: Run with --verbose to see the contract's source code.
(#948)
Improved Documentation
-
Added a new page on EVM precompiles (#944)
v0.25.0
0.25.0 "Yoshiokaite" (2023-10-26)
Features
-
Use the project root as default path for
fe test
Just run
fe test
from any directory of the project. (#913) -
Completed
std::buf::MemoryBuffer
refactor. (#917) -
Allow filtering tests to run via
fe test --filter <some-filter
E.g. Running
fe test --filter foo
will run all tests that containfoo
in their name. (#919) -
Logs for successfully ran tests can be printed with the
--logs
parameter.example:
// test_log.fe use std::evm::log0 use std::buf::MemoryBuffer struct MyEvent { pub foo: u256 pub baz: bool pub bar: u256 } #test fn test_log(mut ctx: Context) { ctx.emit(MyEvent(foo: 42, baz: false, bar: 26)) unsafe { log0(buf: MemoryBuffer::new(len: 42)) } }
$ fe test --logs test_log.fe executing 1 test in test_log: test_log ... passed test_log produced the following logs: MyEvent emitted by 0x0000…002a with the following parameters [foo: 2a, baz: false, bar: 1a] Log { address: 0x000000000000000000000000000000000000002a, topics: [], data: b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x1a\0\0\0\0\0\0\0\0\0\0" } 1 test passed; 0 tests failed; 1 test executed
Note: Logs are not collected for failing tests. (#933)
-
Adds 'functions' section to docs with information on
self
andContext
. (#937)
Bugfixes
-
Yul codegen was failing to include string literals used in test assertions. This resulted in a compiler error.
Example:
#test fn foo() { assert false, "oops" }
The example code above was failing to compile, but now it compiles and executes as expected. (#926)
Improved Documentation
- Added a new tutorial: Open Auction (#930)
v0.24.0
0.24.0 "Xenotime" (2023-08-10)
Features
-
Added support for project manifests and project dependencies.
Example:
my_project ├── fe.toml └── src └── main.fe
# fe.toml name = "my_project" version = "1.0" [dependencies] my_lib = { path = "../path/to/my_lib", version = "1.0" } my_other_lib = "../path/to/my_other_lib"
Note: The current implementation supports circular dependencies. (#908)
Performance improvements
MemoryBuffer
now allocates an extra 31 bytes. This removes the need for runtime checks and bitshifting needed to ensure safe writing to aMemoryBuffer
's region. (#898)
Improved Documentation
- Link to vs-code extension in Quickstart Guide (#910)
v0.23.0
0.23.0 "Wiluite" (2023-06-01)
Features
-
Fixed an issue where generic parameters that were
mut
could not be satisfied at callsite.For instance, the following code would previously cause a compile error but now works as expected:
struct Runner { pub fn run<T: Computable>(self, mut _ val: T) -> u256 { return val.compute(val: 1000) } } contract Example { pub fn run_test(self) { let runner: Runner = Runner(); let mut mac: Mac = Mac(); assert runner.run(mac) == 1001 } }
(#865)
-
The
ctx
parameter can now be passed into test functions.example:
#test fn my_test(ctx: Context) { assert ctx.block_number() == 0 }
(#880)
-
The following has been added to the standard library:
Memory buffer abstraction
example:
use std::buf::{MemoryBuffer, MemoryBufferReader, MemoryBufferWriter} use std::traits::Max #test fn test_buf_rw() { let mut buf: MemoryBuffer = MemoryBuffer::new(len: 161) let mut writer: MemoryBufferWriter = buf.writer() let mut reader: MemoryBufferReader = buf.reader() writer.write(value: 42) writer.write(value: 42) writer.write(value: 26) writer.write(value: u8(26)) writer.write(value: u256::max()) writer.write(value: u128::max()) writer.write(value: u64::max()) writer.write(value: u32::max()) writer.write(value: u16::max()) writer.write(value: u8::max()) writer.write(value: u8(0)) assert reader.read_u256() == 42 assert reader.read_u256() == 42 assert reader.read_u256() == 26 assert reader.read_u8() == 26 assert reader.read_u256() == u256::max() assert reader.read_u128() == u128::max() assert reader.read_u64() == u64::max() assert reader.read_u32() == u32::max() assert reader.read_u16() == u16::max() assert reader.read_u8() == u8::max() assert reader.read_u8() == 0 }
Precompiles
example:
use std::precompiles use std::buf::{MemoryBuffer, MemoryBufferReader, MemoryBufferWriter} #test fn test_ec_recover() { let result: address = precompiles::ec_recover( hash: 0x456e9aea5e197a1f1af7a3e85a3212fa4049a3ba34c2289b4c860fc0b0c64ef3, v: 28, r: 0x9242685bf161793cc25603c231bc2f568eb630ea16aa137d2664ac8038825608, s: 0x4f8ae3bd7535248d0bd448298cc2e2071e56992d0774dc340c368ae950852ada ) assert result == address(0x7156526fbd7a3c72969b54f64e42c10fbb768c8a) }
ctx.raw_call()
example:
use std::buf::{ RawCallBuffer, MemoryBufferReader, MemoryBufferWriter } use std::evm contract Foo { pub unsafe fn __call__() { if evm::call_data_load(offset: 0) == 42 { evm::mstore(offset: 0, value: 26) evm::return_mem(offset: 0, len: 32) } else if evm::call_data_load(offset: 0) == 26 { revert } } } #test fn test_raw_call(mut ctx: Context) { let foo: Foo = Foo.create(ctx, 0) let mut buf: RawCallBuffer = RawCallBuffer::new( input_len: 32, output_len: 32 ) let mut writer: MemoryBufferWriter = buf.writer() writer.write(value: 42) assert ctx.raw_call(addr: address(foo), value: 0, buf) let mut reader: MemoryBufferReader = buf.reader() assert reader.read_u256() == 26 assert not ctx.raw_call(addr: address(foo), value: 0, buf) }
(#885)
Bugfixes
-
Fixed an ICE when using aggregate types with aggregate type fields in public functions
This code would previously cause an ICE:
struct Tx { pub data: Array<u8, 320> } contract Foo { pub fn bar(mut tx: Tx) {} }
(#867)
-
Fixed a regression where the compiler would not reject a method call on a struct in storage.
E.g. the follwing code should be rejected as it is missing a
to_mem()
call:struct Bar { pub x: u256 pub fn get_x(self) -> u256{ return self.x } } contract Foo { bar: Bar pub fn __init__(mut self) { self.bar = Bar( x: 2 ) } fn yay(self) { self.bar.get_x() } }
The compiler will now reject the code and suggest a
to_mem()
before callingget_x()
. (#881)
v0.22.0
0.22.0 "Vulcanite" (2023-04-05)
This is the first non-alpha release of Fe. Read our announcement for more details.
Features
-
Support for tests.
example:
#test fn my_test() { assert 26 + 16 == 42 }
Tests can be executed using the
test
subcommand.example:
$ fe test foo.fe
(#807) -
Fixed broken trait orphan rule
Fe has an orphan rule for Traits similar to Rust's that requires
that either the trait or the type that we are implementing the trait for
are located in the same ingot as theimpl
. This rule was implemented
incorrectly so that instead of requiring them to be in the same ingot,
they were required to be in the same module. This change fixes this
so that the orphan rule is enforced correctly.
(#863) -
address
values can now be specified with a number literal, with no explicit
cast. So instead oflet t: address = address(0xfe)
, one can now write
let t: address = 0xfe
. This also means that it's possible to defineconst
addresses:const SOME_KNOWN_CONTRACT: address = 0xfefefefe
(#864)
Bugfixes
-
Fixed resolving of generic arguments to associated functions.
For example, this code would previously crash the compiler:
... // This function doesn't take self pub fn run_static<T: Computable>(_ val: T) -> u256 { return val.compute(val: 1000) } ... // Invoking it would previously crash the compiler Runner::run_static(Mac()) ...
(#861)
Improved Documentation
- Changed the Deployment tutorial to use foundry and the Sepolia network (#853)
v0.21.0-alpha
0.21.0-alpha "Ussingite" (2023-02-28)
Features
-
Support for
Self
typeWith this change
Self
(with capitalS
) can be used to refer
to the enclosing type in contracts, structs, impls and traits.E.g.
trait Min { fn min() -> Self; } impl Min for u8 { fn min() -> u8 { // Both `u8` or `Self` are valid here return 0 } }
Usage:
u8::min()
(#803) -
Added
Min
andMax
traits to the std library.
The std library implements the traits for all numeric types.Example
use std::traits::{Min, Max} ... assert u8::min() < u8::max() ``` ([#836](https://github.com/ethereum/fe/issues/836))
-
Upgraded underlying solc compiler to version
0.8.18
Bugfixes
- the release contains minor bugfixes
v0.20.0-alpha
0.20.0-alpha "Tokyoite" (2022-12-05)
Features
-
Removed the
event
type as well as theemit
keyword.
Instead thestruct
type now automatically implements
theEmittable
trait and can be emitted viactx.emit(..)
.Indexed fields can be annotated via the
#indexed
attribute.E.g.
struct Signed { book_msg: String<100> } contract GuestBook { messages: Map<address, String<100>> pub fn sign(mut self, mut ctx: Context, book_msg: String<100>) { self.messages[ctx.msg_sender()] = book_msg ctx.emit(Signed(book_msg)) } }
(#717)
-
Allow to call trait methods on types when trait is in scope
So far traits were only useful as bounds for generic functions.
With this change traits can also be used as illustrated with
the following example:trait Double { fn double(self) -> u256; } impl Double for (u256, u256) { fn double(self) -> u256 { return (self.item0 + self.item1) * 2 } } contract Example { pub fn run_test(self) { assert (0, 1).double() == 2 } }
If a call turns out to be ambigious the compiler currently asks the
user to disambiguate via renaming. In the future we will likely
introduce a syntax to allow to disambiguate at the callsite. (#757) -
Allow contract associated functions to be called via
ContractName::function_name()
syntax. (#767) -
Add
enum
types andmatch
statement.enum
can now be defined, e.g.,pub enum MyEnum { Unit Tuple(u32, u256, bool) fn unit() -> MyEnum { return MyEnum::Unit } }
Also,
match
statement is introduced, e.g.,pub fn eval_enum() -> u256{ match MyEnum { MyEnum::Unit => { return 0 } MyEnum::Tuple(a, _, false) => { return u256(a) } MyEnum::Tuple(.., true) => { return u256(1) } } }
For now, available patterns are restricted to
- Wildcard(
_
), which matches all patterns:_
- Named variable, which matches all patterns and binds the value to make the value usable in the arm. e.g.,
a
,b
andc
inMyEnum::Tuple(a, b, c)
- Boolean literal(
true
andfalse
) - Enum variant. e.g.,
MyEnum::Tuple(a, b, c)
- Tuple pattern. e.g.,
(a, b, c)
- Struct pattern. e.g.,
MyStruct {x: x1, y: y1, b: true}
- Rest pattern(
..
), which matches the rest of the pattern. e.g.,MyEnum::Tuple(.., true)
- Or pattern(|). e.g., MyEnum::Unit | MyEnum::Tuple(.., true)
Fe compiler performs the exhaustiveness and usefulness checks for
match
statement.
So the compiler will emit an error when all patterns are not covered or an unreachable arm are detected. (#770) - Wildcard(
-
Changed comments to use
//
instead of#
(#776) -
Added the
mut
keyword, to mark things as mutable. Any variable or function parameter
not markedmut
is now immutable.contract Counter { count: u256 pub fn increment(mut self) -> u256 { // `self` is mutable, so storage can be modified self.count += 1 return self.count } } struct Point { pub x: u32 pub y: u32 pub fn add(mut self, _ other: Point) { self.x += other.x self.y += other.y // other.x = 1000 // ERROR: `other` is not mutable } } fn pointless() { let origin: Point = Point(x: 0, y: 0) // origin.x = 10 // ERROR: origin is not mutable let x: u32 = 10 // x_coord = 100 // ERROR: `x_coord` is not mutable let mut y: u32 = 0 y = 10 // OK let mut p: Point = origin // copies `origin` p.x = 10 // OK, doesn't modify `origin` let mut q: Point = p // copies `p` q.x = 100 // doesn't modify `p` p.add(q) assert p.x == 110 }
Note that, in this release, primitive type function parameters
can't bemut
. This restriction might be lifted in a future release.For example:
fn increment(mut x: u256) { // ERROR: primitive type parameters can't be mut x += 1 }
(#777)
-
The contents of the
std::prelude
module (currently just theContext
struct)
are now automaticallyuse
d by every module, souse std::context::Context
is
no longer required. (#779) -
When the Fe compiler generates a JSON ABI file for a contract, the
"stateMutability" field for each function now reflects whether the function can
read or modify chain or contract state, based on the presence or absence of the
self
andctx
parameters, and whether those parameters aremut
able.If a function doesn't take
self
orctx
, it's "pure".
If a function takesself
orctx
immutably, it can read state but not mutate
state, so it's a "view"
If a function takesmut self
ormut ctx
, it can mutate state, and is thus
marked "payable".Note that we're following the convention set by Solidity for this field, which
isn't a perfect fit for Fe. The primary issue is that Fe doesn't currently
distinguish between "payable" and "nonpayable" functions; if you want a function
to revert when Eth is sent, you need to do it manually
(egassert ctx.msg_value() == 0
). (#783) -
Trait associated functions
This change allows trait functions that do not take a
self
parameter.
The following demonstrates a possible trait associated function and its usage:trait Max { fn max(self) -> u8; } impl Max for u8 { fn max() -> u8 { return u8(255) } } contract Example { pub fn run_test(self) { assert u8::max() == 255 } }
(#805)
Bugfixes
-
Fix issue where calls to assiciated functions did not enforce visibility rules.
E.g the following code should be rejected but previously wasn't:
struct Foo { fn do_private_things() { } } contract Bar { fn test() { Foo::do_private_things() } }
With this change, the above code is now rejected because
do_private_things
is notpub
. (#767) -
Padding on
bytes
andstring
ABI types is zeroed out. (#769) -
Ensure traits from other modules or even ingots can be implemented (#773)
-
Certain cases where the compiler would not reject pure functions
being called on instances are now properly rejected. (#775) -
Reject calling
to_mem()
on primitive types in storage (#801) -
Disallow importing private type via
use
The following was previously allowed but will now error:
use foo::PrivateStruct
(#815)
v0.19.1-alpha
0.19.1-alpha "Sunstone" (2022-07-06)
Features
-
Support returning nested struct.
Example:
pub struct InnerStruct { pub inner_s: String<10> pub inner_x: i256 } pub struct NestedStruct { pub inner: InnerStruct pub outer_x: i256 } contract Foo { pub fn return_nested_struct() -> NestedStruct { ... } }
(#635)
-
Made some small changes to how the
Context
object is used.ctx
is not required when casting an address to a contract type. Eglet foo: Foo = Foo(address(0))
ctx
is required when calling an external contract function that requires ctx
Example:
use std::context::Context # see issue #679 contract Foo { pub fn emit_stuff(ctx: Context) { emit Stuff(ctx) # will be `ctx.emit(Stuff{})` someday } } contract Bar { pub fn call_foo_emit_stuff(ctx: Context) { Foo(address(0)).emit_stuff(ctx) } } event Stuff {}
(#703)
-
Braces! Fe has abandoned python-style significant whitespace in favor of the
trusty curly brace.In addition,
elif
is now spelledelse if
, and thepass
statement no longer exists.Example:
pub struct SomeError {} contract Foo { x: u8 y: u16 pub fn f(a: u8) -> u8 { if a > 10 { let x: u8 = 5 return a + x } else if a == 0 { revert SomeError() } else { return a * 10 } } pub fn noop() {} }
(#707)
-
traits and generic function parameter
Traits can now be defined, e.g:
trait Computable { fn compute(self, val: u256) -> u256; }
The mechanism to implement a trait is via an
impl
block e.g:struct Linux { pub counter: u256 pub fn get_counter(self) -> u256 { return self.counter } pub fn something_static() -> u256 { return 5 } } impl Computable for Linux { fn compute(self, val: u256) -> u256 { return val + Linux::something_static() + self.get_counter() } }
Traits can only appear as bounds for generic functions e.g.:
struct Runner { pub fn run<T: Computable>(self, _ val: T) -> u256 { return val.compute(val: 1000) } }
Only
struct
functions (notcontract
functions) can have generic parameters.
Therun
method ofRunner
can be called with any type that implementsComputable
e.g.contract Example { pub fn generic_compute(self) { let runner: Runner = Runner(); assert runner.run(Mac()) == 1001 assert runner.run(Linux(counter: 10)) == 1015 } }
(#710)
-
Generate artifacts for all contracts of an ingot, not just for contracts that are defined in
main.fe
(#726) -
Allow using complex type as array element type.
Example:
contract Foo { pub fn bar() -> i256 { let my_array: Array<Pair, 3> = [Pair::new(1, 0), Pair::new(2, 0), Pair::new(3, 0)] let sum: i256 = 0 for pair in my_array { sum += pair.x } return sum } } struct Pair { pub x: i256 pub y: i256 pub fn new(_ x: i256, _ y: i256) -> Pair { return Pair(x, y) } }
(#730)
-
The
fe
CLI now has subcommands:fe new myproject
- creates a new project structure
fe check .
- analyzes fe source code and prints errors
fe build .
- builds a fe project (#732) -
Support passing nested struct types to public functions.
Example:
pub struct InnerStruct { pub inner_s: String<10> pub inner_x: i256 } pub struct NestedStruct { pub inner: InnerStruct pub outer_x: i256 } contract Foo { pub fn f(arg: NestedStruct) { ... } }
(#733)
-
Added support for repeat expressions (
[VALUE; LENGTH]
).e.g.
let my_array: Array<bool, 42> = [bool; 42]
Also added checks to ensure array and struct types are initialized. These checks are currently performed at the declaration site, but will be loosened in the future. (#747)
Bugfixes
-
Fix a bug that incorrect instruction is selected when the operands of a comp instruction are a signed type. (#734)
-
Fix issue where a negative constant leads to an ICE
E.g. the following code would previously crash the compiler but shouldn't:
const INIT_VAL: i8 = -1 contract Foo { pub fn init_bar() { let x: i8 = INIT_VAL } }
(#745)
-
Fix a bug that causes ICE when nested if-statement has multiple exit point.
E.g. the following code would previously crash the compiler but shouldn't:
pub fn foo(self) { if true { if self.something { return } } if true { if self.something { return } } }
(#749)
v0.18.0-alpha
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
v0.17.0-alpha "Quartz"
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.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
topayable
in ABIThe 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
andmut ctx
we will be able to derive that information from the function signature. In the meantime we now default topayable
. (#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. Nowstructs
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 previouslyrevert
but that would leave us without a
way to send ETH to a contract so instead it will cause areturn
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)