Releases: ProvableHQ/leo
v3.4.0
Release Notes
Empty arrays and empty loop
Arrays of size 0 and loops over empty ranges are now supported in Leo. While these constructs do not produce any instructions in the compiled Aleo bytecode, they enable more generic or pattern-based programming styles in Leo, especially when writing code that abstracts over sizes, iterates conditionally, or uses compile-time parameters. For example:
inline build_default::[N: u32]() -> [u8; N] {
let xs: [u8; N] = [0u8; N];
// When N = 0 this loop is simply skipped
for i:u32 in 0..N {
xs[i] = 1u8;
}
return xs;
}
let xs = build_default::[0](); // yields []Stability improvements
- Improved identifier validation in Leo, resulting in clearer and more precise error messages, especially when Leo identifiers conflict with reserved Aleo identifiers.
- Fixed an issue where local
constvalues failed to compile correctly when used in loop ranges. - Resolved a crash in the common subexpression elimination optimization pass that occurred for certain patterns of function calls.
Breaking Changes
- The
*hash_native*core functions have been renamed to*hash_to_bits*.
v3.3.1
Patch
- Fixes error in common subexpression elimination.
v3.3.0
Release Notes
Optional Types (T?)
Leo now supports first-class optional types using the T? syntax (e.g., u8?, Foo?, [u64?; 2]).
Optional values can be compared with none, assigned, passed into inline functions, and stored in arrays and structs.
program optionals.aleo {
struct Point { x: u32, y: u32 }
transition main() {
// Optional integers
let x: u8? = 42u8;
let y = x.unwrap(); // Returns 42u8
let z: u8? = none;
let a = z.unwrap_or(99u8); // Returns 99u8
// Array of optionals
let arr: [u16?; 2] = [1u16, none];
let first_val = arr[0].unwrap(); // Returns 1u16
let second_val = arr[1].unwrap_or(0u16); // Returns 0u16
// Optional struct
let p: Point? = none;
let p_val = p.unwrap_or(Point { x: 0u32, y: 0u32 }); // Returns default Point
}
}Type coercion from T to T? is supported in variable definitions, inline function calls, and intermediate expressions.
Explicit unwrapping is required to go from T? → T.
New Storage System
Leo now supports persistent storage variables and storage vectors using the storage keyword.
Storage variables and vectors are declared at program scope, similar to mappings.
program storage_ops.aleo {
struct Point { x: field, y: field }
storage counter: u32; // singleton storage variable
storage points: [Point]; // storage vector of `Point`s
transition main() -> Future {
return async {
counter = 5u32;
let old = counter.unwrap_or(0u32); // returns optional
points.push(Point { x: 1field, y: 2field });
let first = points.get(0u32).unwrap();
points.set(0u32, Point { x: 3field, y: 4field });
counter = none; // unset
}
}
}Storage vector supported operations:
vec.push(10u32); // Push 10u32 at the end of vector `vec`
let x = vec.pop(); // Pop and return the last element of `vec`
let y = vec.get(5); // Get element at index 5
vec.set(3, 5u32); // Set element at index 3 to `5u32`
let y = vec.len(); // Return the number of elements in `vec`
vec.swap_remove(3); // Remove element at index 3 and replace with lastInternally, the compiler rewrites these high-level constructs into mappings and mapping operations.
ECDSA Signature Verification
ECDSA signature verification is now supported with 20 variants covering different hash algorithms and address formats.
// Verify with digest (pre-hashed message)
let valid: bool = ECDSA::verify_digest(sig, addr, digest);
let valid: bool = ECDSA::verify_digest_eth(sig, eth_addr, digest);
// Verify with Keccak256 hashing
let valid: bool = ECDSA::verify_keccak256(sig, addr, msg);
let valid: bool = ECDSA::verify_keccak256_raw(sig, addr, msg);
let valid: bool = ECDSA::verify_keccak256_eth(sig, eth_addr, msg);Also available: keccak384, keccak512, sha3_256, sha3_384, sha3_512.
Parameters:
| Parameter | Type | Description |
|---|---|---|
sig |
[u8; 65] |
ECDSA signature (r, s, v) |
addr |
[u8; 33] or [u8; 20] |
Public key or Ethereum-style address |
digest |
[u8; 32] |
Pre-hashed message |
msg |
Any |
Message to hash and verify |
Raw Hash Operations
Leo now supports raw hash variants. These omit metadata and directly hash input bits, useful for interoperability with external (especially EVM) systems.
Inputs for raw variants of Keccak* and Sha3* must be byte-aligned (number of bits is a multiple of 8).
// Raw hash functions
let h: field = Keccak256::hash_to_field_raw(input);
let h: group = BHP256::hash_to_group_raw(input);
let h: address = Pedersen64::hash_to_address_raw(input);
// Native variants
let bits: [bool; 256] = Keccak256::hash_native(input);
let bits: [bool; 256] = Keccak256::hash_native_raw(input);Available for:
BHP256, BHP512, BHP768, BHP1024, Pedersen64, Pedersen128, Poseidon2, Poseidon4, Poseidon8, Keccak256, Keccak384, Keccak512, SHA3_256, SHA3_384, SHA3_512.
Serialization / Deserialization Operations
Leo now supports serialize and deserialize operations to and from bits, with both metadata-inclusive and raw variants.
The compiler checks that bit sizes match.
// Standard serialization (includes metadata)
let bits: [bool; 58] = Serialize::to_bits(value);
let value: u32 = Deserialize::from_bits::[u32](bits);
// Raw serialization (no metadata)
let bits: [bool; 32] = Serialize::to_bits_raw(value);
let value: u32 = Deserialize::from_bits_raw::[u32](bits);
// Arrays
let bits: [bool; 128] = Serialize::to_bits_raw([1u32, 2u32, 3u32, 4u32]);
let arr: [u32; 4] = Deserialize::from_bits_raw::[[u32; 4]](bits);Bit Lengths
| Type | Raw Bits | Notes |
|---|---|---|
u32 |
32 | |
field |
253 | |
scalar |
251 | |
address |
253 | |
| Non-raw | + metadata overhead |
leo synthesize Command
Generate proving and verifying keys for all transitions in a local or remote Leo program, along with circuit metadata:
leo synthesize credits.aleo --save keys \
--endpoint https://api.explorer.provable.com/v1 \
--network mainnetOutput includes:
- Public inputs
- Variables
- Constraints
- Non-zero entries in matrices
- Circuit ID
- Proving and verifying keys saved to disk
This enables better understanding of program size and key management.
Lossless Syntax Tree Parser
A new lossless syntax tree parser has been added.
While not user-facing yet, it lays the foundation for a future code formatter.
Common Subexpression Elimination (CSE)
New optimization pass reduces bytecode size by eliminating redundant expressions.
Enhanced Error Messages
Error messages now display multiple related source locations.
Example with duplicate struct members:
struct Person {
name: field,
age: u8,
name: field,
}Error:
Error [ETYC0372017]: the name `name` is defined multiple times in struct `Person`
--> src/main.leo:3:9
|
3 | name: field,
| ^^^^^^^^^^^ previous definition here
...
5 | name: field,
| ^^^^^^^^^^^ redefined here
Remaining Stability Improvements
- Various fixes to the interpreter related to hashing correctness
- Fixed broken
leo query committeeendpoint - Validates program names in
leo newagainst reserved SnarkVM keywords - Enforces test isolation for native tests
- Speeds up
leo testby running native tests in parallel - Supports
CheatCode::set_signer("APrivateKey1...")for test signer switching
v3.2.0
🦁 Leo 3.2.0 Release Notes
📦 Modules
Leo 3.2.0 introduces a module system.
Example: Defining a Module
Given a file other_module.leo containing struct, const, and inline definitions:
const X: u32 = 2u32;
struct S {
a: field
}
inline increment(x: field) -> field {
return 1field;
}You may refer to contents of the module as:
other_module::Xother_module::Sother_module::increment
📁 Example Directory Structure
src
├── common.leo
├── main.leo
├── outer.leo
└── outer
└── inner.leo
📦 Module Access Paths
Given the structure above, the following modules are defined:
-
common.leo→ defines modulecommon
Accessible frommain.leovia:common::<item>
-
outer.leo→ defines moduleouter
Accessible frommain.leovia:outer::<item>
-
outer/inner.leo→ defines submoduleouter::inner
Accessible from:-
main.leo:outer::inner::<item>
-
outer.leo:inner::<item>
-
📏 Module Layout Rules
New rules enforce consistency and prevent ambiguity:
-
Leaf modules (modules without submodules) must be defined in a single file:
foo.leo -
Modules with submodules must be defined by an optional top-level file and a subdirectory:
foo.leo # defines module `foo` (optional) foo/ └── bar.leo # defines submodule `foo::bar`
For example, items in outer.leo cannot be accessed from items in inner.leo. This limitation will be removed once absolute paths are implemented.
🔄 Leo Update Command
The leo update command now supports selecting a specific version:
leo update --name v3.0.0🔐 Other Updates
- Decrypt records via CLI: You can now decrypt records directly from the command line.
- Pass ciphertexts to
leo execute: Record ciphertexts may be passed as inputs to execution.
🐞 Bug Fixes & Improvements
- Fixed outputting code regarding parenthesizing operands of the ternary conditional.
- Removed redundant type errors in async function calls.
- Correctly handle shorthand struct initializers that are consts.
leo cleanno longer errors whenoutputs/orbuild/directory is missing.- Correctly type-check
returnstatements inside constructors. - Added a check for the snarkOS installation location.
- Ensure const arguments are evaluated before monomorphization.
- Improved error messages for unresolved generics.
v3.1.0
- Upgradability: See the SnarkOS notes for more about how upgradability works in general, but in Leo code you will now include a constructor to be run during upgrades. The constructor may use an annotation to indicate use of a standard constructor, such as the following to forbid upgrades:
@noupgrade
async constructor() {}
Or you may create a custom constructor:
@custom
async constructor() {
assert_eq(true, true);
}
- Async blocks allow you to elide defining separate async functions, and write your on-chain code in the same location as the rest of the transition:
async transition main(public a: u32, b: u32) -> Future {
let f1: Future = other.aleo/main_inner(0u32, 0u32);
let f: Future = async {
f1.await();
};
return f;
}
- You can use a
.aleofile as a local dependency (previously dependencies were programs already deployed to the network, or local Leo projects):
leo add --local this_dependency.aleo
- leo devnet allows you to conveniently start a local devnet, optionally installing snarkos if desired:
leo devnet --install --snarkos ~/bin/snarkos --snarkos-features test_network
-
Identifiers may no longer contain double underscores, so for instance
this__nameis now disallowed. -
Bug fix:: we no longer try to fetch the edition of
credits.aleo.
v3.0.0
- Array repeater syntax:
[0u8; 3]; - input files are gone;
- numerous small fixes.
v2.7.3
- Fix to type resolution in interpreter (allowing tests to work correctly)
v2.7.2
Fix: Any 2XX HTTP status is success.
v2.7.1
Version bump to 2.7.1
v2.7.0
-
Basic type inference;
-
Clearer error reporting for
leo executeandleo deploy.