Syntax Specification

Formal Specification

\[\begin{split}\begin{align*} program &::= [importDirective]^*\ [contract]^+\\ importDirective &::= \mathrm{import}\ "\mathrm{ID}";\\ contract &::= \mathrm{contract}\ \mathrm{ID}\ \{\ [var]^*\ [constructor]\ [function]^+\ \}\\ var &::= formal;\\ formal &::= \mathrm{TYPE}\ \mathrm{ID}\\ constructor &::= \mathrm{constructor}([formal[,\ formal]^*])\ \{\ [stmt]^*\ \}\\ function &::= \mathrm{[public|static]}\ \mathrm{function}\ \mathrm{ID}([formal[,\ formal]^*])\ \mathrm{[returns}\ (\mathrm{TYPE]})\ \{\ [stmt]^*\ \mathrm{[return}\ expr;]\ \}\\ stmt &::= \mathrm{TYPE}\ \mathrm{ID} = expr;\\ &\ \ \ |\ \ \mathrm{ID}\ \mathrm{ID} = \mathrm{new}\ \mathrm{ID}(expr^*);\\ &\ \ \ |\ \ \mathrm{ID} = expr;\\ &\ \ \ |\ \ \mathrm{require}(expr);\\ &\ \ \ |\ \ \mathrm{exit}(expr);\\ &\ \ \ |\ \ \mathrm{if}\ (expr)\ stmt\ [\mathrm{else}\ stmt]\\ &\ \ \ |\ \ \mathrm{loop}\ (intConst)\ stmt\\ &\ \ \ |\ \ \{\ [stmt]^*\ \}\\ &\ \ \ |\ \ \mathrm{CODESEPARATOR}\\ expr &::= \mathsf{UnaryOp}\ expr\\ &\ \ \ |\ \ expr\ \mathsf{BinaryOp}\ expr\\ &\ \ \ |\ \ \mathrm{ID}(expr[,\ expr]^*)\\ &\ \ \ |\ \ \mathrm{ID}.\mathrm{ID}\\ &\ \ \ |\ \ \mathrm{ID}.\mathrm{ID}(expr[,\ expr]^*)\\ &\ \ \ |\ \ \mathrm{ID}\mathbf{[}expr:expr\mathbf{]}\\ &\ \ \ |\ \ (expr)\\ &\ \ \ |\ \ \mathrm{ID}\\ &\ \ \ |\ \ boolConst \\ &\ \ \ |\ \ intConst \\ &\ \ \ |\ \ bytesConst \\ \end{align*}\end{split}\]

Most of the syntax is self explanatory. Syntax unique to sCrypt will be covered later.

Line comment starts with // and block comment comes between /* and */.

Types

Basic Types

  • bool - a boolean value true or false.

  • int - a signed integer of arbitrary length, whose literals are in decimal or hexadecimal format.

    int a1 = 42;
    int a2 = -4242424242424242;
    int a3 = 55066263022277343669578718895168534326250603453777594175500187360389116729240;
    int a4 = 0xFF8C;
    
  • bytes - a variable length array of bytes, whose literals are either in quoted hexadecimal format prefixed by b, or double quoted UTF8 string.

    bytes b1 = b'ffee1234';
    bytes b2 = b'414136d08c5ed2bf3ba048afe6dcaebafeffffffffffffffffffffffffffffff00';
    bytes b3 = b'1122' + b'eeff'; // b3 is b'1122eeff'
    bytes str = "hello world"; // utf8 string
    

Array Types

An array is a fixed-size list of values of the same basic type.

  • Array Literals - a comma-separated list of expressions, enclosed in square brackets. Array size must be an integer constant greater than zero.

    bool[3] b = [false, false && true || false, true || (1 > 2)];
    int[3] c = [72, -4 - 1 - 40, 833 * (99 + 9901) + 8888];
    bytes[3] a = [b'ffee', b'11', b'22'];
    int[2][3] d = [[11, 12, 13], [21, 22, 23]];
    // array demension can be omitted when declared
    int[] e = [1, 4, 2];  // e is of type int[3]
    int[][] f = [[11, 12, 13], [21, 22, 23]]; // f is of type int[2][3]
    
  • Initialize/set an array to the same value - Function T[size] repeat(T e, static const int size) returns an array with all size elements set to e, where T can be any type. Note size must be a compile time constant.

    // a == [0, 0, 0]
    int[3] a = repeat(0, 3);
    // arr2D == [[0, 0, 0], [0, 0, 0]]
    int[2][3] arr2D = repeat(0, 2);
    int[4] flags = [false, true, false, true]
    // set all flags to be false
    flags = repeat(false, 4);
    
  • Index Operator - index starting from 0. Out of bound access fails contract execution immediately.

    int[3] a = [1, 4, 2];
    int[2][3] arr2D = [[11, 12, 13], [21, 22, 23]];
    int d = a[2];
    a[1] = -4;
    int idx = 2;
    // read
    d = a[idx];
    d = arr2D[idx][1];
    // write
    a[idx] = 2;
    // assign to an array variable
    a = arr2D[1];
    // b is a new copy and the same as a
    int[3] b = a;
    // two arrays are equal if and only if they are of the same size and all elements are equal
    require(a == b);
    

Struct Types

A struct (or structure) is a collection of variables (can be of different basic types) under a single name.

  • Define Struct

    struct Point {
      int x;
      int y;
    }
    
    struct Line {
      // nested struct
      Point start;
      Point end;
    }
    
  • Use Struct
    Point p = {10, -10};
    int x = p.x;
    p.y = 20;
    // Define a variable q of type Point, and set members to the same values as those of p
    Point q = p;
    require(p == q); // true
    // nested
    Line l = {p, q};
    l.start.x = l.end.y + 1;
    

Type Inference

The auto keyword specifies that the type of the variable, of basic type, declared will be automatically deducted from its initializer.

auto a1 = b'36';      // bytes a1 = b'36';
auto a2 = 1 + 5 * 3;  // int a2 = 1 + 5 * 3;

Type Aliases

Type aliases create a new name for a type. It does not actually create a new type, it merely creates a new name to refer to that type. Note the right side of the declaration should not be an unspecified generic struct type.

type Age = int;
type Coordinate = int[2];
type A = ST<int>; // this is fine.
type B = ST; // this is not allowd.

Generics/Generic Types

A generic type is a parameterized type. It allows a library / struct to work over a variety of types rather than a single one. Users can consume these libraries / structs and use their own concrete types.

  • Declare Generic Types

Generic types can be declared at library level and used within the library’s scope, or struct level and used inside.

// declare a library with two generic type variables: K & V
library HashedMap<K, V> {

  // use them as function parameters' type
  function set(K k, V v, int idx) {
    ...
  }

}

// declare a struct with two generic type variables: T & P
struct ST<T, P> {
  T x;
  P y;
}
  • Instantiate Generic Types

    // initialize a library with generics
    HashedMap<bytes, int> map = new HashedMap();
    
    // initialize a struct with generics
    ST<int, bytes> st = {1, b'02'};
    

Domain Subtypes

There are several subtypes, specific to the Bitcoin context, used to further improve type safety.

Subtypes of bytes

To cast a supertype bytes to them, a function of the type name must be explicitly called.

  • PubKey - a public key type.

    PubKey pubKey = PubKey(b'0200112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100');
    
  • Sig - a signature type in DER format, including signature hash type, which is SIGHASH_ALL | SIGHASH_FORKID (0x41) in the below example.

    Sig sig = Sig(b'3045022100b71be3f1dc001e0a1ad65ed84e7a5a0bfe48325f2146ca1d677cf15e96e8b80302206d74605e8234eae3d4980fcd7b2fdc1c5b9374f0ce71dea38707fccdbd28cf7e41');
    
  • Ripemd160 - a RIPEMD-160 hash type.

    Ripemd160 r = Ripemd160(b'0011223344556677889999887766554433221100');
    
  • PubKeyHash - an alias for Ripemd160` type. Usually represent a bitcoin address.

    PubKeyHash aliceAddress = PubKeyHash(b'0011223344556677889999887766554433221100');
    
  • Sha1 - a SHA-1 hash type.

    Sha1 s = Sha1(b'0011223344556677889999887766554433221100');
    
  • Sha256 - a SHA-256 hash type.

    Sha256 s = Sha256(b'00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100');
    
  • SigHashType - a sighash type.

    SigHashType s = SigHashType(b'01');
    SigHashType s = SigHash.ALL | SigHash.ANYONECANPAY;
    
  • SigHashPreimage - a sighash preimage type.

    SigHashPreimage s = SigHashPreimage(b'0100000028bcef7e73248aa273db19d73');
    
  • OpCodeType - a OpCode type.

    OpCodeType s = OpCode.OP_DUP + OpCode.OP_ADD;
    

Subtypes of int

  • PrivKey - a private key type.

    PrivKey privKey = PrivKey(0x00112233445566778899aabbccddeeffffeeddccbbaa99887766554433221100);
    

const Variables

Variables declared const cannot be changed once initialized.

contract Test {
    const int x;

    constructor(int x) {
        this.x = x; // good, since this is initialization
    }

    public function equal(const int y) {
        y = 1; // <-- error

        const int a = 36;
        a = 11; // <-- error

        require(y == this.x);
    }
}

if statement

if condition can be of type int and bytes, besides bool. They are implicitly converted to bool as in C and Javascript. An int expression is evaluated to false if and only if it is 0 (including negative 0). A bytes expression is evaluated to false if and only if every of its byte is b'00' (including empty bytes b'').

int cond = 25; // true
int cond = 0;  // false
int cond = unpack(b'80') // false since it is negative 0
int cond = unpack(b'000080') // false since it is negative 0
if (cond) {} // equivalent to if (cond != 0) {}

bytes cond = b'00'; // false
bytes cond = b''; // false
bytes cond = b'80'; // true. Note b'80' is treated as false if converted to int
bytes cond = b'10' & b'73'; // true since it evaluates to b'10'
if (cond) {}

exit()

exit(bool status); statement terminates contract execution. If status is true, contract succeeds; otherwise, it fails.

contract TestPositiveEqual {
    int x;

    constructor(int x) {
        this.x = x;
    }

    public function equal(int y) {
        if (y <= 0) {
          exit(true);
        }
        require(y == this.x);
    }
}

Code Separator

Three or more * in a line inserts an OP_CODESEPARATOR. It is used to exclude what comes before (and including itself) it from being part of the signature. Note there is no ; at the end.

contract P2PKH_OCS {
    Ripemd160 pubKeyHash;

    public function unlock(Sig sig, PubKey pubKey) {
        // code separator 1
        ***
        require(hash160(pubKey) == this.pubKeyHash);
        // code separator 2
        *****
        require(checkSig(sig, pubKey));
    }
}

Access Modifiers

There are three types of access modifiers available to help restrict the scope of properties and functions of a contract:

  • Default: no keyword required
  • Private
  • Public: only applies to functions

Only public functions can be called externally by Bitcoin transactions.

  default private public
Same contract Yes Yes Yes
Other contract Yes No Yes
Externally No No Yes

Operators

Precedence Operator Associativity Note
1 () ++ -- . left-to-right  
2 [] left-to-right  
3 ++ -- - ! ~ right-to-left  
4 * / % left-to-right  
5 + - left-to-right  
6 << >> left-to-right The number of bits to shift must be non-negative, otherwise evaluation fails immediately
7 < <= > >= left-to-right  
8 == != left-to-right  
9 & left-to-right In the case of two integers with different length, the shorter one with be expanded first to be the same legnth with the longer one using num2bin
10 ^ left-to-right Same as &
11 | left-to-right Same as &
12 && left-to-right  
13 || left-to-right  
14 ? : right-to-left  
15 += -= *= /= %= &= |= ^= <<= >>= right-to-left  

Note

  • Operator &&, ||, and ? : use short-circuit evaluation.
  • After performing bitwise on integers, such as operator &, |, ^, and ~, the compiler uses OP_BIN2NUM to compress the results.

Scoping

Scoping in sCrypt follows the prevailing scoping rules of C99 and Solidity. Outer scope variable is shadowed by the inner scope variable of the same name.