Introduction

Simfony is a high-level language for writing Bitcoin smart contracts. In other words, Simfony is a language for expressing spending conditions of UTXOs on the Bitcoin blockchain.

Simfony looks and feels like Rust. Developers write Simfony, Bitcoin full nodes run Simplicity.

Types and Values

Simfony mostly uses a subset of Rust's types. It extends Rust in some ways to make it work better with Simplicity and with the blockchain.

Boolean Type

TypeDescriptionValues
boolBooleanfalse, true

Values of type bool are truth values, which are either true or false.

Integer Types

TypeDescriptionValues
u11-bit integer0, 1
u22-bit integer0, 1, 2, 3
u44-bit integer0, 1, …, 15
u88-bit integer0, 1, …, 255
u1616-bit integer0, 1, …, 65535
u3232-bit integer0, 1, …, 4294967295
u6464-bit integer0, 1, …, 18446744073709551615
u128128-bit integer0, 1, …, 340282366920938463463374607431768211455
u256256-bit integer0, 1, …, 2256 - 11

Unsigned integers range from 1 bit to 256 bits. u8 to u128 are also supported in Rust. u1, u2, u4 and u256 are new to Simfony. Integer values can be written in decimal notation 123456, binary notation2 0b10101010 or hexadecimal notation3 0xdeadbeef. There are no signed integers.

1

The maximal value of type u256 is 115792089237316195423570985008687907853269984665640564039457584007913129639935.

2

The number of bits must be equal to the bit width of the type.

3

The number of hex digits must correspond to the bit width of the type.

Tuple Types

TypeDescriptionValues
()0-tuple()
(A)1-tuple(a0,), (a1,), …
(A, B)2-tuple(a0, b0), (a1, b1), (a2, b2), (a3, b3), …
(A, B, …)n-tuple(a0, b0, …), …

Tuples work just like in Rust.

The empty tuple () contains no information. It is also called the "unit". It is mostly used as the return type of functions that don't return anything.

Singletons (a0,) must be written with an extra comma , to differentiate them from function calls.

Bigger tuples (a0, b0, …) work like in pretty much any other programming language. Each tuple type (A1, A2, …, AN) defines a sequence A1, A2, …, AN of types. Values of that type must mirror the sequence of types: A tuple value (a1, a2, …, aN) consists of a sequence a1, a2, …, aN of values, where a1 is of type A1, a2 is of type A2, and so on. Tuples are always finite in length.

Tuples are different from arrays: Each element of a tuple can have a different type. Each element of an array must have the same type.

Array Types

TypeDescriptionValues
[A; 0]0-array[]
[A; 1]1-array[a0], [a1], …
[A; 2]2-array[a0, a1], [a2, a3], [a4, a5], [a6, a7], …
[A; N]n-array[a0, …, aN], …

Arrays work just like in Rust.

The empty array [] is basically useless, but I included it for completeness.

Arrays [a0, …, aN] work like in pretty much any other programming language. Each array type [A; N] defines an element type A and a length N. An array value [a0, …, aN] of that type consists of N many elements a0, …, aN that are each of type A. Arrays are always of finite length.

Arrays are different from tuples: Each element of an array must have the same type. Each element of a tuple can have a different type.

List Types

TypeDescriptionValues
List<A, 2><2-listlist![], list![a1]
List<A, 4><4-listlist![], …, list![a1, a2, a3]
List<A, 8><8-listlist![], …, list![a1, …, a7]
List<A, 16><16-listlist![], …, list![a1, …, a15]
List<A, 32><32-listlist![], …, list![a1, …, a31]
List<A, 64><64-listlist![], …, list![a1, …, a62]
List<A, 128><128-listlist![], …, list![a1, …, a127]
List<A, 256><256-listlist![], …, list![a1, …, a255]
List<A, 512><512-listlist![], …, list![a1, …, a511]
List<A,2N><2N-listlist![], …, list![a1, …, a(2N - 1)]

Lists hold a variable number of elements of the same type. This is similar to Rust vectors, but Simfony doesn't have a heap. In Simfony, lists exists on the stack, which is why the maximum list length is bounded.

<2-lists hold fewer than 2 elements, so zero or one element. <4-lists hold fewer than 4 elements, so zero to three elements. <8-lists hold fewer than 8 elements, so zero to seven elements. And so on. For technical reasons, the list bound is always a power of two. The bound 1 is not supported, because it would only allow empty lists, which is useless.

Lists are different from arrays: List values hold a variable number of elements. Array values hold a fixed number of elements.

On the blockchain, you pay for every byte that you use. If you use an array, then you pay for every single element. For example, values of type [u8; 512] cost roughly as much as 512 many u8 values. However, if you use a list, then you only pay for the elements that you actually use. For example, the type List<u8, 512> allows for up to 511 elements. If you only use three elements list![1, 2, 3], then you pay for exactly three elements. You don't pay for the remaining 508 unused elements.

Option Types

TypeValues
Option<A>None, Some(a0), Some(a1), …

Options represent values that might not be present. They work just like in Rust.

An option type is generic over a type A. The value None is empty. The value Some(a) contains an inner value a of type A.

In Rust, we implement options as follows.

#![allow(unused)]
fn main() {
enum Option<A> {
    None,
    Some(A),
}
}

Either Types

TypeValues
Either<A, B>Left(a0), Left(a1), …, Right(b0), Right(b1), …

Sum types represent values that are of some "left" type in some cases and that are of another "right" type in other cases. They work just like in the either crate. The Result type from Rust is very similar, too.

A sum type type is generic over two types, A and B. The value Left(a) contains an inner value a of type A. The value Right(b) contains an inner value b of type B.

In Rust, we implement sum types as follows.

#![allow(unused)]
fn main() {
enum Either<A, B> {
    Left(A),
    Right(B),
}
}

Type Aliases

Simfony currently doesn't support Rust-like structs for organizing data.

#![allow(unused)]
fn main() {
struct User {
  active: bool,
  id: u256,
  sign_in_count: u64,
}
}

Simfony programmers have to handle long tuples of unlabeled data, which can get messy.

#![allow(unused)]
fn main() {
(bool, u256, u64)
}

To help with the situation, programmers can define custom type aliases. Aliases define a new name for an existing type. In contrast, structs define an entirely new type, so aliases are different from structs. However, aliases still help us to make the code more readable.

#![allow(unused)]
fn main() {
type User = (bool, u256, u64);
}

There is also a list of builtin type aliases. These aliases can be used without defining them.

Builtin AliasDefinition
Amount1Either<(u1, u256), u64>
Asset1Either<(u1, u256), u256>
Confidential1(u1, u256)
Ctx8(List<u8, 64>, (u64, u256))
Distanceu16
Durationu16
ExplicitAmountu256
ExplicitAssetu256
ExplicitNonceu256
Feu256
Ge(u256, u256)
Gej((u256, u256), u256)
Heightu32
Locku32
Messageu256
Message64[u8; 64]
NonceEither<(u1, u256), u256>
Outpoint(u256, u32)
Point(u1, u256)
Pubkeyu256
Scalaru256
Signature[u8; 64]
Timeu32
TokenAmount1Either<(u1, u256), u64>

Casting

A Simfony type can be cast into another Simfony type if both types share the same structure. The structure of a type has to do with how the type is implemented on the Simplicity "processor". I will spare you the boring details.

Below is a table of types that can be cast into each other.

TypeCasts To (And Back)
boolEither<(), ()>
Option<A>Either<(), A>
u1bool
u2(u1, u1)
u4(u2, u2)
u8(u4, u4)
u16(u8, u8)
u32(u16, u16)
u64(u32, u32)
u128(u64, u64)
u256(u128, u128)
(A)A
(A, B, C)(A, (B, C))
(A, B, C, D)((A, B), (C, D))
[A; 0]()
[A; 1]A
[A; 2](A, A)
[A; 3](A, (A, A))
[A; 4]((A, A), (A, A))
List<A, 2>Option<A>
List<A, 4>(Option<[A; 2]>, List<A, 2>)
List<A, 8>(Option<[A; 4]>, List<A, 4>)
List<A, 16>(Option<[A; 8]>, List<A, 8>)
List<A, 32>(Option<[A; 16]>, List<A, 16>)
List<A, 64>(Option<[A; 32]>, List<A, 32>)
List<A, 128>(Option<[A; 64]>, List<A, 64>)
List<A, 256>(Option<[A; 128]>, List<A, 128>)
List<A, 512>(Option<[A; 256]>, List<A, 256>)

Casting Rules

Type A can be cast into itself (reflexivity).

If type A can be cast into type B, then type B can be cast into type A (symmetry).

If type A can be cast into type B and type B can be cast into type C, then type A can be cast into type C (transitivity).

Casting Expression

All casting in Simfony happens explicitly through a casting expression.

#![allow(unused)]
fn main() {
<Input>::into(input)
}

The above expression casts the value input of type Input into some output type. The input type of the cast is explict while the output type is implicit.

In Simfony, the output type of every expression is known.

#![allow(unused)]
fn main() {
let x: u32 = 1;
}

In the above example, the meaning of the expression 1 is clear because of the type u32 of variable x. Here, 1 means a string of 31 zeroes and 1 one. In other contexts, 1 could mean something different, like a string of 255 zeroes and 1 one.

The Simfony compiler knows the type of the outermost expression, and it tries to infer the types of inner expressions based on that. When it comes to casting expressions, the compiler has no idea about the input type of the cast. The programmer needs to supply this information by annotating the cast with its input type.

#![allow(unused)]
fn main() {
let x: u32 = <(u16, u16)>::into((0, 1));
}

In the above example, we cast the tuple (0, 1) of type (u16, u16) into type u32. Feel free to consult the table above to verify that this is valid cast.

Let Statement

Variables are defined in let statements, just like in Rust.

#![allow(unused)]
fn main() {
let x: u32 = 1;
}

The above let statement defines a variable called x. The variable is of type u32 and it is assigned the value 1.

#![allow(unused)]
fn main() {
let x: u32 = f(1337);
}

Variables can be assigned to the output value of any expression, such as function calls.

Explicit typing

In Simfony, the type of a defined variable always has to be written. This is different from Rust, which has better type inference.

Immutability

Simfony variables are always immutable. There are no mutable variables.

Redefinition and scoping

The same variable can be defined twice in the same scope. The later definition overrides the earlier definition.

#![allow(unused)]
fn main() {
let x: u32 = 1;
let x: u32 = 2;
assert!(jet::eq_32(x, 2)); // x == 2
}

Normal scoping rules apply: Variables from outer scopes are available inside inner scopes. A variable defined in an inner scope shadows a variable of the same name from an outer scope.

#![allow(unused)]
fn main() {
let x: u32 = 1;
let y: u32 = 2;
let z: u32 = {
    let x: u32 = 3;
    assert!(jet::eq_32(y, 2)); // y == 2
    x
};
assert!(jet::eq_32(x, 3)); // z == 3
}

Pattern matching

There is limited pattern matching support inside let statements.

#![allow(unused)]
fn main() {
let (x, y, _): (u8, u16, u32) = (1, 2, 3);
let [x, _, z]: [u32; 3] = [1, 2, 3];
}

In the first line, the tuple (1, 2, 3) is deconstructed into the values 1, 2 and 3. These values are assigned to the variable names x, y and _. The variable name _ is a special name that ignores its value. In the end, two variables are created: x: u32 = 1 and y: u16 = 2.

Similarly, arrays can be deconstructed element by element and assigned to a variable each.

Match Expression

A match expression conditionally executes code branches. Which branch is executed depends on the input to the match expression.

#![allow(unused)]
fn main() {
let result: u32 = match f(42) {
    Left(x: u32) => x,
    Right(x: u16) => jet::left_pad_low_16_32(x),
};
}

In the above example, the output of the function call f(42) is matched. f returns an output of type Either<u32, u16>. If f(42) returns a value that matches the pattern Left(x: u32), then the first match arm is executed. This arm simply returns the value x. Alternatively, if f(42) returns a value that matches the pattern Right(x: u16), then the second match arm is executed. This arm extends the 16-bit number x to a 32-bit number by padding its left with zeroes. Because of type constraints, the output of f must match one of these two patterns. The whole match expression returns a value of type u32, from one of the two arms.

Explicit typing

In Simfony, the type of variables inside match arms must always be written. This is different from Rust, which has better type inference.

Pattern matching

There is limited support for pattern matching inside match expressions.

Boolean values can be matched. The Boolean match expression is the replacement for an "if-then-else" in Simfony.

#![allow(unused)]
fn main() {
let bit_flip: bool = match false {
    false => true,
    true => false,
};
}

Optional values can be matched. The Some arm introduces a variable which must be explicitly typed.

#![allow(unused)]
fn main() {
let unwrap_or_default: u32 = match Some(42) {
    None => 0,
    Some(x: u32) => x,
};
}

Finally, Either values can be matched. Again, variables that are introduced in match arms must be explicitly typed.

#![allow(unused)]
fn main() {
let map_either: u32 = match Left(1337) {
    Left(x: u32) => f(x),
    Right(y: u32) => f(y),
};
}

Match expressions don't support further pattern matching, in contrast to Rust.

#![allow(unused)]
fn main() {
let unwrap_or_default: u32 = match Some((4, 2)) {
    None => 0,
    // this doesn't compile
    Some((y, z): (u16, u16)) => <(u16, u16)>::into((y, z)),
};
}

However, the match arm can contain code that performs the deconstruction. For example, the tuple x of type (u16, u16) can be deconstructed into two integers y and z of type u16.

#![allow(unused)]
fn main() {
let unwrap_or_default: u32 = match Some((4, 2)) {
    None => 0,
    Some(x: (u16, u16)) => {
        let (y, z): (u16, u16) = x;
        <(u16, u16)>::into((y, z))
    }
};
}

The match arm can also contain match expressions for further deconstruction. For example, the sum value x of type Either<u32, u32> can be matched as either Left(y: u32) or Right(z: u32).

#![allow(unused)]
fn main() {
let unwrap_or_default: u32 = match Some(Left(42)) {
    None => 0,
    Some(x: Either<u32, u32>) => match x {
        Left(y: u32) => y,
        Right(z: u32) => z,
    },
};
}

Functions

Functions are defined and called just like in Rust.

#![allow(unused)]
fn main() {
fn add(x: u32, y: u32) -> u32 {
    let (carry, sum): (bool, u32) = jet::add_32(x, y);
    match carry {
        true => panic!(), // overflow
        false => {}, // ok
    };
    sum
}
}

The above example defines a function called add that takes two parameters: variable x of type u32 and variable y of type u32. The function returns a value of type u32.

The body of the function is a block expression { ... } that is executed from top to bottom. The function returns on the final line (note the missing semicolon ;). In the above example, x and y are added via the add_32 jet. The function then checks if the carry is true, signaling an overflow, in which case it panics. On the last line, the value of sum is returned.

The above function is called by writing its name add followed by a list of arguments (40, 2). Each parameter needs an argument, so the list of arguments is as long as the list of arguments. Here, x is assigned the value 40 and y is assigned the value 2.

#![allow(unused)]
fn main() {
let z: u32 = add(40, 2);
}

No early returns

Simfony has no support for an early return via a "return" keyword. The only branching that is available is via match expressions.

No recursion

Simfony has no support for recursive function calls. A function can be called inside a function body if it has been defined before. This means that a function cannot call itself. Loops, where f calls g and g calls f, are also impossible.

What is possible are stratified function definitions, where level-0 functions depend on nothing, level-1 functions depend on level-0 functions, and so on.

#![allow(unused)]
fn main() {
fn level_0() -> u32 {
    0
}

fn level_1() -> u32 {
    let (_, next) = jet::increment_32(level_0());
    next
}

fn level_2() -> u32 {
    let (_, next) = jet::increment_32(level_1));
    next
}
}

Order matters

If function g calls function f, then f must be defined before g.

#![allow(unused)]
fn main() {
fn f() -> u32 {
    42
}

fn g() -> u32 {
    f()
}
}

Main function

The main function is the entry point of each Simfony program. Running a program means running its main function. Other functions are called from the main function.

fn main() {
    // ...
}

The main function is a reserved name and must exist in every program. Simfony programs are always "binaries". There is no support for "libraries".

Jets

Jets are predefined and optimized functions for common usecases.

#![allow(unused)]
fn main() {
jet::add_32(40, 2)
}

Jets live inside the namespace jet, which is why they are prefixed with jet::. They can be called without defining them manually.

It is usually more efficient to call a jet than to manually compute a value.

The jet documentation lists each jet and explains what it does.

Programs

A Simfony program consists of a main function.

A program may also have type aliases or custom function definitions. The main function comes last in the program, because everything it calls must be defined before it.

type Furlong = u32;
type Mile = u32;

fn to_miles(distance: Either<Furlong, Mile>) -> Mile {
    match distance {
        Left(furlongs: Furlong) => jet::divide_32(furlongs, 8),
        Right(miles: Mile) => miles,
    }
}

fn main() {
    let eight_furlongs: Either<Furlong, Mile> = Left(8);
    let one_mile: Either<Furlong, Mile> = Right(1);
    assert!(jet::eq_32(1, to_miles(eight_furlongs)));
    assert!(jet::eq_32(1, to_miles(one_mile)));
}