Syntax Specification¶
Formal Specification¶
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
orfalse
.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 allsize
elements set toe
, where T can be any type. Notesize
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(a, 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 it (including itself), 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 | |
2 | [] |
left | |
3 | ++ -- - ! ~ |
right | |
4 | * / % |
left | |
5 | + - |
left | |
6 | << >> |
left | The number of bits to shift must be non-negative, otherwise evaluation fails immediately |
7 | < <= > >= |
left | |
8 | == != |
left | |
9 | & |
left | 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 | Same as & |
11 | | |
left | Same as & |
12 | && |
left | |
13 | || |
left | |
14 | ? : |
right | |
15 | += -= *= /= %= &= |= ^= <<= >>= |
right |
Note
- Operator
&&
,||
, and? :
use short-circuit evaluation. - After performing bitwise on integers, such as operator
&
,|
,^
, and~
, the compiler usesOP_BIN2NUM
to compress the results. - Regardless of whether it is a positive number or a negative number, both are is stored in the Sign-Magnitude format,
which is different from the two’s complement format used by the computer.
If the operands participating in the operation are all positive numbers, the result of the operation is consistent with two’s complement’s bitwise operator.
(except
~
). Otherwise, the operation results may be inconsistent.
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.