UNPKG

namillum

Version:
1,161 lines (906 loc) 98.1 kB
--- title: Scilla in Depth --- ## Structure of a Scilla Contract The general structure of a Scilla contract is given in the code fragment below: - The contract starts with the declaration of `scilla_version`, which indicates which major Scilla version the contract uses. - Then follows the declaration of a `library` that contains purely mathematical functions, e.g., a function to compute the Boolean `AND` of two bits, or a function computing the factorial of a given natural number. - Then follows the actual contract definition declared using the keyword `contract`. - Within a contract, there are then four distinct parts: 1. The first part declares the immutable parameters of the contract. 2. The second part describes the contract\'s constraint, which must be valid when the contract is deployed. 3. The third part declares the mutable fields. 4. The fourth part contains all `transition` and `procedure` definitions. ```ocaml (* Scilla contract structure *) (***************************************************) (* Scilla version *) (***************************************************) scilla_version 1 (***************************************************) (* Associated library *) (***************************************************) library MyContractLib (* Library code block follows *) (***************************************************) (* Contract definition *) (***************************************************) contract MyContract (* Immutable contract parameters declaration *) (vname_1 : vtype_1, vname_2 : vtype_2) (* Contract constraint *) with (* Constraint expression *) => (* Mutable fields declaration *) field vname_1 : vtype_1 = init_val_1 field vname_2 : vtype_2 = init_val_2 (* Transitions and procedures *) (* Procedure signature *) procedure firstProcedure (param_1 : type_1, param_2 : type_2) (* Procedure body *) end (* Transition signature *) transition firstTransition (param_1 : type_1, param_2 : type_2) (* Transition body *) end (* Procedure signature *) procedure secondProcedure (param_1 : type_1, param_2 : type_2) (* Procedure body *) end transition secondTransition (param_1: type_1) (* Transition body *) end ``` ### Immutable Contract Parameters [Immutable parameters]{.title-ref} are the contract\'s initial parameters whose values are defined when the contract is deployed, and cannot be modified afterwards. Immutable parameters are declared using the following syntax: ```ocaml (vname_1 : vtype_1, vname_2 : vtype_2, ... ) ``` Each declaration consists of a parameter name (an identifier) and followed by its type, separated by `:`. Multiple parameter declarations are separated by `,`. The initialization values for parameters are to be specified when the contract is deployed. ::: {.note} ::: {.title} Note ::: In addition to the explicitly declared immutable parameters, a Scilla contract has the following implicitly declared immutable contract parameters > 1\. `_this_address` of type `ByStr20`, which is initialised to the address of > the contract when the contract is deployed. > > 2\. `_creation_block` of type `BNum`, which is initialized to the block number > at which the contract is / was deployed. These parameters can be freely read within the implementation without having to dereference it using `<-` and cannot be modified with `:=`. ::: ### Contract Constraints A [contract constraint]{.title-ref} is a requirement placed on the contract\'s immutable parameters. A contract constraint provides a way of establishing a contract invariant as soon as the contract is deployed, thus preventing the contract being deployed with nonsensical parameters. A contract constraint is declared using the following syntax: ```ocaml with ... => ``` The constraint must be an expression of type `Bool`. The constraint is checked when the contract is deployed. Contract deployment only succeeds if the constraint evaluates to `True`. If it evaluates to `False`, then the deployment fails. Here is a simple example of using contract constraints to make sure a contract with a limited period of functioning is not deployed [after]{.title-ref} that period: ```ocaml contract Mortal(end_of_life : BNum) with builtin blt _creation_block end_of_life => ``` The snippet above uses the implicit contract parameter `_creation_block` described in `immutable-contract-parameters`{.interpreted-text role="ref"}. ::: {.note} ::: {.title} Note ::: Declaring a contract constraint is optional. If no constraint is declared, then the constraint is assumed to simply be `True`. ::: ### Mutable Fields [Mutable fields]{.title-ref} represent the mutable state (mutable variables) of the contract. They are declared after the immutable parameters, with each declaration prefixed with the keyword `field`. ```ocaml field vname_1 : vtype_1 = expr_1 field vname_2 : vtype_2 = expr_2 ... ``` Each expression here is an initialiser for the field in question. The definitions complete the initial state of the contract, at the time of creation. As the contract executes a transition, the values of these fields get modified. ::: {.note} ::: {.title} Note ::: In addition to the explicitly declared mutable fields, a Scilla contract has an implicitly declared mutable field `_balance` of type `Uint128`, which is initialised to 0 when the contract is deployed. The `_balance` field keeps the amount of funds held by the contract, measured in QA (1 ZIL = 1,000,000,000,000 QA). This field can be freely read within the implementation, but can only be modified by explicitly transferring funds to other accounts (using `send`), or by accepting money from incoming messages (using `accept`). ::: ::: {.note} ::: {.title} Note ::: Both mutable fields and immutable parameters must be of a _storable_ type: - Messages, events and the special `Unit` type are not storable. All other primitive types like integers and strings are storable. - Function types are not storable. - Complex types involving uninstantiated type variables are not storable. - Maps and ADT are storable if the types of their subvalues are storable. For maps this means that the key type and the value type must both be storable, and for ADTs this means that the type of every constructor argument must be storable. ::: ### Units The Zilliqa protocol supports three basic tokens units - ZIL, LI (10\^-6 ZIL) and QA (10\^-12 ZIL). The base unit used in Scilla smart contracts is QA. Hence, when using money variables, it is important to attach the trailing zeroes that are needed to represent it in QAs. > ```ocaml > (* fee is 1 QA *) > let fee = Uint128 1 > > (* fee is 1 LI *) > let fee = Uint128 1000000 > > (* fee is 1 ZIL *) > let fee = Uint128 1000000000000 > ``` ### Transitions [Transitions]{.title-ref} are a way to define how the state of the contract may change. The transitions of a contract define the public interface for the contract, since transitions may be invoked by sending a message to the contract. Transitions are defined with the keyword `transition` followed by the parameters to be passed. The definition ends with the `end` keyword. ```ocaml transition foo (vname_1 : vtype_1, vname_2 : vtype_2, ...) ... end ``` where `vname : vtype` specifies the name and type of each parameter and multiple parameters are separated by `,`. ::: {.note} ::: {.title} Note ::: In addition to the parameters that are explicitly declared in the definition, each transition has the following implicit parameters: - `_amount : Uint128` : Incoming amount, in QA (see section above on the units), sent by the sender. To transfer the money from the sender to the contract, the transition must explicitly accept the money using the `accept` instruction. The money transfer does not happen if the transition does not execute an `accept`. - `_sender : ByStr20 with end` : The account address that triggered this transition. If the transition was called by a contract account instead of a user account, then `_sender` is the address of the contract that called this transition. In a chain call, this is the contract that sent the message invoking the current transition. - `_origin : ByStr20 with end` : The account address that initiated the current transaction (which can possibly be a chain call). This is always a user address, since contracts can never initiate transactions. The type `ByStr20 with end` is an [address type]{.title-ref}. Address types are explained in detail in the `Addresses <Addresses>`{.interpreted-text role="ref"} section. ::: ::: {.note} ::: {.title} Note ::: Transition parameters must be of a _serialisable_ type. Serialisable: - Byte strings, integers, strings, and block numbers are serialisable. - Addresses are serialisable only as `ByStr20` values. - ADT are serialisable if the types of their subvalues are serialisable. This means that the type of every constructor argument must be serialisable. Not Serialisable: - Messages, events and the special `Unit` type are not serialisable. - Function types and map types are not serialisable. - Complex types involving uninstantiated type variables are not serialisable. ::: ### Procedures [Procedures]{.title-ref} are another way to define now the state of the contract may change, but in contrast to transitions, procedures are not part of the public interface of the contract, and may not be invoked by sending a message to the contract. The only way to invoke a procedure is to call it from a transition or from another procedure. Procedures are defined with the keyword `procedure` followed by the parameters to be passed. The definition ends with the `end` keyword. ```ocaml procedure foo (vname_1 : vtype_1, vname_2 : vtype_2, ...) ... end ``` where `vname : vtype` specifies the name and type of each parameter and multiple parameters are separated by `,`. Once a procedure is defined it is available to be invoked from transitions and procedures in the rest of the contract file. It is not possible to invoke a procedure from transition or procedure defined earlier in the contract, nor is it possible for a procedure to call itself recursively. Procedures are invoked using the name of the procedure followed by the actual arguments to the procedure: ```ocaml v1 = ...; v2 = ...; foo v1 v2; ``` All arguments must be supplied when the procedure is invoked. A procedure does not return a result. ::: {.note} ::: {.title} Note ::: The implicit transition parameters `_sender`, `_origin` and `_amount` are implicitly passed to all the procedures that a transition calls. There is therefore no need to declare those parameters explicitly when defining a procedure. ::: ::: {.note} ::: {.title} Note ::: Procedure parameters cannot be (or contain) maps. If a procedure needs to access a map, it is therefore necessary to either make the procedure directly access the contract field containing the map, or use a library function to perform the necessary computations on the map. ::: ### Expressions [Expressions]{.title-ref} handle pure operations. Scilla contains the following types of expressions: - `let x = f` : Give `f` the name `x` in the contract. The binding of `x` to `f` is **global** and extends to the end of the contract. The following code fragment defines a constant `one` whose values is `1` of type `Int32` throughout the contract. ```ocaml let one = Int32 1 ``` - `let x = f in expr` : Bind `f` to the name `x` within expression `expr`. The binding here is **local** to `expr` only. The following example binds the value of `one` to `1` of type `Int32` and `two` to `2` of type `Int32` in the expression `builtin add one two`, which adds `1` to `2` and hence evaluates to `3` of type `Int32`. ```ocaml let sum = let one = Int32 1 in let two = Int32 2 in builtin add one two ``` - `{ <entry>_1 ; <entry>_2 ... }`: Message or event expression, where each entry has the following form: `b : x`. Here `b` is an identifier and `x` a variable, whose value is bound to the identifier in the message. - `fun (x : T) => expr` : A function that takes an input `x` of type `T` and returns the value to which expression `expr` evaluates. - `f x` : Apply the function `f` to the parameter `x`. - `tfun 'T => expr` : A type function that takes `'T` as a parametric type and returns the value to which expression `expr` evaluates. These are typically used to build library functions. See the implementation of [fst](#fst) for an example. ::: {.note} ::: {.title} Note ::: Shadowing of type variables is not currently allowed. E.g. `tfun 'T => tfun 'T => expr` is not a valid expression. ::: - `@x T`: Apply the type function `x` to the type `T`. This specialises the type function `x` by instantiating the first type variable of `x` to `T`. Type applications are typically used when a library function is about to be applied. See the example application of [fst](#fst) for an example. - `builtin f x`: Apply the built-in function `f` on `x`. - `match` expression: Matches a bound variable with patterns and evaluates the expression in that clause. The `match` expression is similar to the `match` expression in OCaml. The pattern to be matched can be an ADT constructor (see [ADTs](#ADTs)) with subpatterns, a variable, or a wildcard `_`. An ADT constructor pattern matches values constructed with the same constructor if the subpatterns match the corresponding subvalues. A variable matches anything, and binds the variable to the value it matches in the expression of that clause. A wildcard matches anything, but the value is then ignored. ```ocaml match x with | pattern_1 => expression_1 ... | pattern_2 => expression_2 ... | _ => (*Wildcard*) expression ... end ``` ::: {.note} ::: {.title} Note ::: A pattern-match must be exhaustive, i.e., every legal (type-safe) value of `x` must be matched by a pattern. Additionally, every pattern must be reachable, i.e., for each pattern there must be a legal (type-safe) value of `x` that matches that pattern, and which does not match any pattern preceding it. ::: ### Statements Statements in Scilla are operations with effect, and hence not purely mathematical. Scilla contains the following types of statements: - `x <- f` : Fetch the value of the contract field `f`, and store it into the local variable `x`. - `f := x` : Update the mutable contract field `f` with the value of `x`. `x` may be a local variable, or another contract field. - `x <- & BLOCKNUMBER` : Fetch the value of the blockchain state variable `BLOCKNUMBER`, and store it into the local variable `x`. - `x <- & CHAINID` : Fetch the value of the current chain ID, and store it into the local variable `x` of type `Uint32`. - `x <- & TIMESTAMP(block_num)` : If `block_num` refers to an earlier block than the current one, then fetch the timestamp `t` of that block, and store the value `Some t` into the local variable `x : Option Uint64`. If `block_num` refers to the current block or later, then store the value `None` into `x`. - `x <- & c.f` : Remote fetch. Fetch the value of the contract field `f` at address `c`, and store it into the local variable `x`. Note that the type of `c` must be an address type containing the field `f`. See the section on `Addresses <Addresses>`{.interpreted-text role="ref"} for details on address types. - `x <- & c as t`: Address type cast. Check whether `c` satisfies the type `t`. If it does, then store the value `Some v` into `x`, where `v` is the same value as `c`, but of type `t` rather than the type of `c`. If it does not, then store the value `None` into `x`. Note that `c` must be of type `ByStr20` or an address type, and that `t` must be an address type. See the section on `Addresses <Addresses>`{.interpreted-text role="ref"} for details on address types. - `v = e` : Evaluate the expression `e`, and assign the value to the local variable `v`. - `p x y z` : Invoke the procedure `p` with the arguments `x`, `y` and `z`. The number of arguments supplied must correspond to the number of arguments the procedure takes. - `forall ls p` : Invoke procedure `p` for each element in the list `ls`. `p` should be defined to take exactly one argument whose type is equal to an element of the list `ls`. - `match` : Pattern-matching at statement level: ```ocaml match x with | pattern_1 => statement_11; statement_12; ... | pattern_2 => statement_21; statement_22; ... | _ => (*Wildcard*) statement_n1; statement_n2; ... end ``` - `accept` : Accept the QA of the message that invoked the transition. The amount is automatically added to the `_balance` field of the contract. If a message contains QA, but the invoked transition does not accept the money, the money is transferred back to the sender of the message. Not accepting the incoming amount (when it is non-zero) is not an error. - `send` and `event` : Communication with the blockchain. See the next section for details. - In-place map operations : Operations on contract fields of type `Map`. See the [Maps](#Maps) section for details. A sequence of statements must be separated by semicolons `;`: ```ocaml transition T () statement_1; statement_2; ... statement_n end ``` Notice that the final statement does not have a trailing `;`, since `;` is used to separate statements rather than terminate them. ### Communication A contract can communicate with other contract and user accounts through the `send` instruction: - `send msgs` : Send a list of messages `msgs`. The following code snippet defines a `msg` with four entries `_tag`, `_recipient`, `_amount` and `param`. ```ocaml (*Assume contractAddress is the address of the contract being called and the contract contains the transition setHello*) msg = { _tag : "setHello"; _recipient : contractAddress; _amount : Uint128 0; param : Uint32 0 }; ``` A message passed to `send` must contain the mandatory fields `_tag`, `_recipient` and `_amount`. The `_recipient` field (of type `ByStr20`) is the blockchain address that the message is to be sent to, and the `_amount` field (of type `Uint128`) is the number of QA to be transferred to that account. The `_tag` field (of type `String`) is only used when the value of the `_recipient` field is the address of a contract. In this case, the value of the `_tag` field is the name of the transition that is to be invoked on the recipient contract. If the recipient is a user account, the `_tag` field is ignored. ::: {.note} ::: {.title} Note ::: To make it possible to transfer funds from a contract to both contracts and user accounts, use a standard transition name as per [ZRC-5](https://github.com/Zilliqa/ZRC/blob/master/zrcs/zrc-5.md), i.e. `AddFunds`. Please make sure to check if a contract to which you intend to send funds is implemented in adherence with ZRC-5 convention. ::: In addition to the compulsory fields the message may contain other fields (of any type), such as `param` above. However, if the message recipient is a contract, the additional fields must have the same names and types as the parameters of the transition being invoked on the recipient contract. Here\'s an example that sends multiple messages. > ```ocaml > msg1 = { _tag : "setFoo"; _recipient : contractAddress1; _amount : Uint128 0; foo : Uint32 101 }; > msg2 = { _tag : "setBar"; _recipient : contractAddress2; _amount : Uint128 0; bar : Uint32 100 }; > msgs = > let nil = Nil {Message} in > let m1 = Cons {Message} msg1 nil in > Cons msg2 m1 > ; > send msgs > ``` ::: {.note} ::: {.title} Note ::: A transition may execute a `send` at any point during execution (including during the execution of the procedures it invokes), but the messages will not sent onwards until after the transition has finished. More details can be found in the `Chain Calls <Chaincalls>`{.interpreted-text role="ref"} section. ::: ::: {.note} ::: {.title} Note ::: Messages sent to a user-defined library are ignored. They do not cause the transaction to fail, but they have no effect. ::: A contract can also communicate to the outside world by emitting events. An event is a signal that gets stored on the blockchain for everyone to see. If a user uses a client application invoke a transition on a contract, the client application can listen for events that the contract may emit, and alert the user. - `event e`: Emit a message `e` as an event. The following code emits an event with name `e_name`. > ```ocaml > e = { _eventname : "e_name"; <entry>_2 ; <entry>_3 }; > event e > ``` An emitted event must contain the compulsory field `_eventname` (of type `String`), and may contain other entries as well. The value of the `_eventname` entry must be a string literal. All events with the same name must have the same entry names and types. ::: {.note} ::: {.title} Note ::: As with the sending of messages, a transition may emit events at any point during execution (including during the execution of the procedures it invokes), but the event will not be visible on the blockchain before the transition has finished. More details can be found in the `Chain Calls <Chaincalls>`{.interpreted-text role="ref"} section. ::: ### Run-time Errors A transition may encounter a run-time error during execution, such as out-of-gas errors, integer overflows, or deliberately thrown exceptions. A run-time error causes the transition to terminate abruptly, and the entire transaction to be aborted. However, gas is still charged up until the point of the error. The syntax for throwing an exception is similar to that of events and messages. ```ocaml e = { _exception : "InvalidInput"; <entry>_2; <entry>_3 }; throw e ``` Unlike that for `event` or `send`, The argument to `throw` is optional and can be omitted. An empty throw will result in an error that just conveys the location of where the `throw` happened without more information. ::: {.note} ::: {.title} Note ::: If a run-time error occurs during the execution of a transition, then the entire transaction is aborted, and any state changes in both the current and other contracts are rolled back. (The state of other contracts may have changed due to a chain call). In particular: - All transferred funds are returned to their respective senders, even if an `accept` was executed before the error. - The message queue is cleared, so that as yet unprocessed messages will no longer be sent onwards even if a `send` was executed before the error. - The event list is cleared, so that no events are emitted even if an `event` was executed before the error. Gas is still charged for the transaction up until the point the run-time error occurs. ::: ::: {.note} ::: {.title} Note ::: Scilla does not have exception handlers. Throwing an exception always aborts the entire transaction. ::: ### Gas consumption in Scilla Deploying contracts and executing transitions in them cost gas. The detailed cost mechanism is explained [here](https://github.com/Zilliqa/scilla-docs/tree/master/docs/texsources/gas-costs/gas-doc.pdf). The [Nucleus Wallet](https://dev-wallet.zilliqa.com/calculate) page can be used to estimate gas costs for some transactions . ## Primitive Data Types & Operations ### Integer Types Scilla defines signed and unsigned integer types of 32, 64, 128, and 256 bits. These integer types can be specified with the keywords `IntX` and `UintX` where `X` can be 32, 64, 128, or 256. For example, the type of an unsigned integer of 32 bits is `Uint32`. The following code snippet declares a variable of type `Uint32`: ```ocaml let x = Uint32 43 ``` Scilla supports the following built-in operations on integers. Each operation takes two integers `IntX` / `UintX` (of the same type) as arguments. Exceptions are `pow` whose second argument is always `Uint32` and `isqrt` which takes in a single `UintX` argument. - `builtin eq i1 i2` : Is `i1` equal to `i2`? Returns a `Bool`. - `builtin add i1 i2`: Add integer values `i1` and `i2`. Returns an integer of the same type. - `builtin sub i1 i2`: Subtract `i2` from `i1`. Returns an integer of the same type. - `builtin mul i1 i2`: Integer product of `i1` and `i2`. Returns an integer of the same type. - `builtin div i1 i2`: Integer division of `i1` by `i2`. Returns an integer of the same type. - `builtin rem i1 i2`: The remainder of integer division of `i1` by `i2`. Returns an integer of the same type. - `builtin lt i1 i2`: Is `i1` less than `i2`? Returns a `Bool`. - `builtin pow i1 i2`: `i1` raised to the power of `i2`. Returns an integer of the same type as `i1`. - `builtin isqrt i`: Computes the integer square root of `i`, i.e. the largest integer `j` such that `j * j <= i`. Returns an integer of the same type as `i`. - `builtin to_nat i1`: Convert a value of type `Uint32` to the equivalent value of type `Nat`. - `builtin to_(u)int32/64/128/256`: Convert a `UintX` / `IntX` or a `String` (that represents a decimal number) value to the result of `Option UintX` or `Option IntX` type. Returns `Some res` if the conversion succeeded and `None` otherwise. The conversion may fail when - there is not enough bits to represent the result; - when converting a negative integer (or a string representing a negative integer) into a value of an unsigned type; - the input string cannot be parsed as an integer. Here is the list of concrete conversion builtins for better discoverability: `to_int32`, `to_int64`, `to_int128`, `to_int256`, `to_uint32`, `to_uint64`, `to_uint128`, `to_uint256`. Addition, subtraction, multiplication, pow, division and remainder operations may raise integer overflow, underflow and division_by_zero errors. This aborts the execution of the current transition and unrolls all the state changes made so far. ::: {.note} ::: {.title} Note ::: Variables related to blockchain money, such as the `_amount` entry of a message or the `_balance` field of a contract, are of type `Uint128`. ::: ### Strings `String` literals in Scilla are expressed using a sequence of characters enclosed in double quotes. Variables can be declared by specifying using keyword `String`. The following code snippet declares a variable of type `String`: ```ocaml let x = "Hello" ``` Scilla supports the following built-in operations on strings: - `builtin eq s1 s2` : Is `s1` equal to `s2`? Returns a `Bool`. `s1` and `s2` must be of type `String`. - `builtin concat s1 s2` : Concatenate string `s1` with string `s2`. Returns a `String`. - `builtin substr s idx len` : Extract the substring of `s` of length `len` starting from position `idx`. `idx` and `len` must be of type `Uint32`. Character indices in strings start from `0`. Returns a `String` or fails with a runtime error if the combination of the input parameters results in an invalid substring. - `builtin to_string x`: Convert `x` to a string literal. Valid types of `x` are `IntX`, `UintX`, `ByStrX` and `ByStr`. Returns a `String`. Byte strings are converted to textual hexadecimal representation. - `builtin strlen s` : Calculate the length of `s` (of type `String`). Returns a `Uint32`. - `builtin strrev s` : Returns the reverse of the string `s`. - `builtin to_ascii h` : Reinterprets a byte string (`ByStr` or `ByStrX`) as a printable ASCII string and returns an equivalent `String` value. If the byte string contains any non-printable characters, a runtime error is raised. ### Byte strings Byte strings in Scilla are represented using the types `ByStr` and `ByStrX`, where `X` is a number. `ByStr` refers to a byte string of arbitrary length, whereas for any `X`, `ByStrX` refers to a byte string of fixed length `X`. For instance, `ByStr20` is the type of byte strings of length 20, `ByStr32` is the type of byte strings of length 32, and so on. Byte strings literals in Scilla are written using hexadecimal characters prefixed with `0x`. Note that it takes 2 hexadecimal characters to specify 1 byte, so a `ByStrX` literal requires `2 * X` hexadecimal characters. The following code snippet declares a variable of type `ByStr32`: ```ocaml let x = 0x123456789012345678901234567890123456789012345678901234567890abff ``` Scilla supports the following built-in operations for computing on and converting between byte string types: - `builtin to_bystr h` : Convert a value `h` of type `ByStrX` (for some known `X`) to one of arbitrary length of type `ByStr`. - `builtin to_bystrX h` : (note that `X` is a numerical parameter here and not a part of the builtin name, see the examples below) - if the argument `h` is of type `ByStr`: Convert an arbitrary size byte string value `h` (of type `ByStr`) to a fixed sized byte string of type `ByStrX`, with length `X`. The result is of type `Option ByStrX` in this case: the builtin returns `Some res` if the length of the argument is equal to `X` and `None` otherwise. E.g. `builtin to_bystr42 bs` returns `Some bs'` if the length of `bs` is 42. - if the argument `h` is of type `Uint(32/64/128/256)`: Convert unsigned integers to their big endian byte representation, returning a `ByStr(4/8/16/32)` value (notice it\'s not an optional type in this case). For instance, `builtin to_bystr4 x` (this only typechecks if `x` has type `Uint32`) or `builtin to_bystr16 x` (this only typechecks if `x` is of type `Uint128`). - `builtin to_uint(32/64/128/256) h` : Convert a fixed sized byte string value `h` to an equivalent value of type `Uint(32/64/128/256)`. `h` must be of type `ByStrX` for some known `X` less than or equal to (4/8/16/32). A big-endian representation is assumed. - `builtin concat h1 h2`: Concatenate byte strings `h1` and `h2`. - If `h1` has type `ByStrX` and `h2` has type `ByStrY`, then the result will have type `ByStr(X+Y)`. - If the arguments are of type `ByStr`, the result is also of type `ByStr`. - `builtin strlen h`: The length of byte string (`ByStr`) `h`. Returns `Uint32`. - `eq a1 a2`: Is `a1` equal to `a2`? Returns a `Bool`. ### Addresses {#Addresses} Addresses on the Zilliqa network are strings of 20 bytes, and raw addresses are therefore represented by values of type `ByStr20`. Additionally, Scilla supports structured address types, i.e., types that are equivalent to `ByStr20`, but which, when interpreted as an address on the network, provide additional information about the contents of that address. Address types are written using the form `ByStr20 with <address contents> end`, where `<address contents>` refers to what the address contains. The hierarchy of address types is as follows: - `ByStr20`: A raw byte string of length 20. The type does not provide any guarantee as to what is located at the address. (Generally, `ByStr20` is not regarded as an address type, because it can refer to any byte string of length 20, whether it is meant to represent an address or not.) - `ByStr20 with end`: A `ByStr20` which, when interpreted as a network address, refers to an address that is in use. An address is in use if it either contains a contract or a library, or if the balance or the _nonce_ of the address is greater than 0. (The balance of an address is the number of Qa held by the address account. The nonce of an address is the number of transactions that have been initiated from that address). - `ByStr20 with contract end`: A `ByStr20` which, when interpreted as a network address, refers to the address of a contract. - `ByStr20 with contract field f1 : t1, field f2 : t2, ... end`: A `ByStr20` which, when interpreted as a network address, refers to the address of a contract containing the mutable fields `f1` of type `t1`, `f2` of type `t2`, and so on. The contract in question may define more fields than the ones specified in the type, but the fields specified in the type must be defined in the contract. ::: {.note} ::: {.title} Note ::: All addresses in use, and therefore by extension all contract addresses, implicitly define a mutable field `_balance : Uint128`. For user accounts the `_balance` field refers to the account balance. ::: ::: {.note} ::: {.title} Note ::: Address types specifying immutable parameters or transitions of a contract are not supported. ::: #### Address subtyping The hierarchy of address types defines a subtype relation: - Any address type `ByStr20 with ... end` is as subtype of `ByStr20`. This means that any address type can be used in place of a `ByStr20`, e.g., for comparing equality using `builtin eq`, or as the `_recipient` value of a message. - Any contract address type `ByStr20 with contract ... end` is a subtype of `ByStr20 with end`. - Any contract address type specifying explicit fields `ByStr20 with contract field f1 : t11, field f2 : t12, ... end` is a subtype of a contract address type specifying a subset of those fields `ByStr20 with contract field f1 : t21, field f2 : t22, ... end`, provided that `t11` is a subtype of `t21`, `t12` is a subtype of `t22`, and so on for each field specified in both contract types. - For ADTs with type parameters such as `List` or `Option`, an ADT `T t1 t2 ...` is a subtype of `S s1 s2 ...` if `T` is the same as `S`, and `t1` is a subtype of `s1`, `t2` is a subtype of `s2`, and so on. - A map with key type `kt1` and value type `vt1` is a subtype of another map with key type `kt2` and value type `vt2` if `kt1` is a subtype of `kt2` and `vt1` is a subtype of `vt2`. #### Dynamic typecheck of addresses In general, address types cannot be fully typechecked statically by the Scilla checker. This can happen, e.g., because a byte string is a transition parameter and thus not known statically, or because a byte string refers to an address that does not currently contain a contract, but which might contain a contract in the future. For this reason immutable parameters (i.e., contract parameters supplied when the contract is deployed) and transition parameters of address types are typechecked dynamically, when the actual byte string is known. For example, a contract might specify an immutable field `init_owner` as follows: ```ocaml contract MyContract (init_owner : ByStr20 with end) ``` When the contract is deployed, the byte string supplied as `init_owner` is looked up as an address on the blockchain, and if the contents of that address matches the address type (in this case that the address is in use either by a user or by a contract), then deployment continues, and `init_owner` can be treated as a `ByStr20 with end` throughout the contract. Similarly, a transition might specify a parameter `token_contract` as follows: ```ocaml transition Transfer ( token_contract : ByStr20 with contract field balances : Map ByStr20 Uint128 end ) ``` When the transition is invoked, the byte string supplied as the `token_contract` parameter is looked up as an address on the blockchain, and if the contents of that address matches the address type (in this case that the address contains a contract with a field `balances` of a type that is assignable to `Map ByStr20 Uint128`), then the transition parameter is initialised successfully, and `token_contract` can be treated as a `ByStr20 with contract field balances : Map ByStr20 Uint128 end` throughout this transition invocation. In either case, if the contents of the address does not match the specified type, then the dynamic typecheck is unsuccessful, causing deployment (for failed immutable parameters) or transition invocation (for transition parameters) to fail. A failed dynamic typecheck is considered a run-time error, causing the current transaction to abort. (For the purposes of dynamic typechecks of immutable fields the deployment of a contract is considered a transaction). It is also possible to perform a dynamic typecheck as a statement, using the address type cast construct: ```ocaml x <- & c as ByStr20 with ... end ``` If `c` satisfies the address type, then `x` is bound to `Some v`, where `v` is the same value as `c` but is treated as having the address type specified in cast. On the other hand, if `c` does not satisfy the address type, then `x` is bound to `None`. The advantage of using an address type cast instead of a dynamic typecheck of transition parameters is that a type cast does not cause a run-time failure in case of a failed cast - a failed cast just results in a `None` value. The disadvantage of address type casts is that they cost more gas. The type cast itself costs the same as a typecheck of a transition parameter, but one then needs to pattern-match on the result of the type cast, which costs additional gas. ::: {.note} ::: {.title} Note ::: It is not possible to specify a `ByStr20` literal and have it interpreted as an address. In other words, the following code snippet will result in a static type error: ```ocaml let x : ByStr20 with end = 0x1234567890123456789012345678901234567890 ``` The only ways for a byte string to be validated against an address type is either to pass it as the value of an immutable field or as a transition parameter of the appropriate type, or to perform a cast statement to the appropriate type. ::: #### Remote fetches To perform a remote fetch `x <- & c.f`, the type of `c` must be some address type declaring the field `f`. For instance, if `c` has the type `ByStr20 with contract field paused : Bool end`, then the value of the field `paused` at address `c` can be fetched using the statement `x <- & c.paused`, whereas it is not possible to fetch the value of an undeclared field (e.g., `admin`) of `c`, even if the contract at address `c` does actually contain a field `admin`. To be able to fetch the value of the `admin` field, the type of `c` must contain the `admin` field as well, e.g, `ByStr20 with contract field paused : Bool, field admin : ByStr20 end` Remote fetches of map fields can be performed using in-place operations in the same way as for locally declared map fields, i.e., `x <- & c.m[key]`, `x <- & c.m[key1][key2]`, `x <- & exists m[key]`, etc. As with remote fetches of map fields, the remote map field must be declared in the type of `c`, e.g., `ByStr20 with contract field m : Map Uint128 (Map Uint32 Bool) end`. Writing to a remote field is not allowed. ### Crypto Built-ins A hash in Scilla is declared using the data type `ByStr32`. A `ByStr32` represents a hexadecimal byte string of 32 bytes (64 hexadecimal characters). A `ByStr32` literal is prefixed with `0x`. Scilla supports the following built-in operations on hashes and other cryptographic primitives, including byte sequences. - `builtin eq h1 h2`: Is `h1` equal to `h2`? Both inputs are of the same type `ByStrX` (or both are of type `ByStr`). Returns a `Bool`. - `builtin sha256hash x` : Convert `x` of any non-closure type to its SHA256 hash. Returns a `ByStr32`. - `builtin keccak256hash x`: Convert `x` of any non-closure type to its Keccak256 hash. Returns a `ByStr32`. - `builtin ripemd160hash x`: Convert `x` of any non-closure type to its RIPEMD-160 hash. Returns a `ByStr20`. - `builtin substr h idx len` : Extract the sub-byte-string of `h` of length `len` starting from position `idx`. `idx` and `len` must be of type `Uint32`. Character indices in byte strings start from `0`. Returns a `ByStr` or fails with a runtime error. - `builtin strrev h` : Reverse byte string (either `ByStr` or `ByStrX`). Returns a value of the same type as the argument. - `builtin schnorr_verify pubk data sig` : Verify a signature `sig` of type `ByStr64` against a byte string `data` of type `ByStr` with the Schnorr public key `pubk` of type `ByStr33`. - `builtin schnorr_get_address pubk`: Given a public key of type `ByStr33`, returns the `ByStr20` Zilliqa address that corresponds to that public key. - `builtin ecdsa_verify pubk data sig` : Verify a signature `sig` of type `ByStr64` against a byte string `data` of type `ByStr` with the ECDSA public key `pubk` of type `ByStr33`. - `builtin ecdsa_recover_pk data sig recid` : Recover `data` (of type `ByStr`), having signature `sig` (of type `ByStr64`) and a `Uint32` recovery integer `recid`, whose value is restricted to be 0, 1, 2 or 3, the uncompressed public key, returning a `ByStr65` value. - `builtin bech32_to_bystr20 prefix addr`. The builtin takes a network specific prefix (`"zil"` / `"tzil"`) of type `String` and an input bech32 string (of type `String`) and if the inputs are valid, converts it to a raw byte address ([ByStr20]{.title-ref}). The return type is `Option ByStr20`. On success, `Some addr` is returned and on invalid inputs `None` is returned. - `builtin bystr20_to_bech32 prefix addr`. The builtin takes a network specific prefix (`"zil"` / `"tzil"`) of type `String` and an input `ByStr20` address, and if the inputs are valid, converts it to a bech32 address. The return type is `Option String`. On success, `Some addr` is returned and on invalid inputs `None` is returned. - `builtin alt_bn128_G1_add p1 p2`. The builtin takes two points `p1`, `p2` on the `alt_bn128` curve and returns the sum of the points in the underlying group G1. The input points are each a `Pair {Bystr32 ByStr32}`. The return type is `Option (Pair (ByStr32) (ByStr32))`. On success, `Some point` is returned and on invalid inputs `None` is returned. Each scalar component `ByStr32` of a point is a big-endian encoded number. Also see <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-196.md> - `builtin alt_bn128_G1_mul p s`. The builtin takes a point `p` on the `alt_bn128` curve (as described previously), and a scalar `ByStr32` value `s` and returns the sum of the point `p` taken `s` times. The return type is `Option (Pair (ByStr32) (ByStr32))`. On success, `Some point` is returned and on invalid inputs `None` is returned. - `builtin alt_bn128_G1_neg p`. The builtin takes a point `p` on the `alt_bn128` curve and returns the negation of the point. The result is `Option (Pair (ByStr32) (ByStr32))`. On success, `Some point` is returned and on invalid inputs `None` is returned. - `builtin alt_bn128_pairing_product pairs`. This builtin takes in a list of pairs `pairs` of points. Each pair consists of a point in group G1 (`Pair {Bystr32 ByStr32}`) as the first component and a point in group G2 (`Pair {Bystr64 ByStr64}`) as the second component. Hence the argument has type `List {(Pair (Pair ByStr32 ByStr32) (Pair ByStr64 ByStr64)) }`. The function applies a pairing function on each point to check for equality and returns `True` or `False` depending on whether the pairing check succeeds or fails. Also see <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md> <a name="Maps"></a> ### Maps A value of type `Map kt vt` provides a key-value store where `kt` is the type of keys and `vt` is the type of values (in some other programming languages datatypes like Scilla\'s `Map` are called associative arrays, symbol tables, or dictionaries). The type of map keys `kt` may be any one of the following _primitive_ types: `String`, `IntX`, `UintX`, `ByStrX`, `ByStr` or `BNum`. The type of values `vt` may be any type except a function type, this includes both builtin and user-defined algebraic datatypes. ::: Since compound types are not supported as map key types, the way to model, e.g. association of pairs of values to another value is by using _nested_ maps. For instance, if one wants to associate with an account and a particular trusted user some money limit the trusted user is allowed to spend on behalf of the account, one can use the following nested map: ```ocaml field operators: Map ByStr20 (Map ByStr20 Uint128) = Emp ByStr20 (Map ByStr20 Unit) ``` The first and the second key are of type `ByStr20` and represent accounts and the trusted users correspondingly. We represent the money limits with the `Uint128` type. Scilla supports a number of operations on map, which can be categorized as - _in-place_ operations which modify _field_ maps without making any copies, hence they belong to the imperative fragment of Scilla. These operations are efficient and recommended to use in almost all of the cases; - _functional_ map operations are intended to use in pure functions, e.g. when designing a Scilla library, because they never modify the original map they are called on. These operations may incur significant performance overhead as some of them create a new (modified) copy of the input map. Syntactically, the copying operations are all prefixed with `builtin` keyword (see below). Note that to call the functional builtins on a field map one first needs to make a _copy_ of the field map using a command like so: `map_copy <- field_map`, which results in gas consumption proportional to the size of `field_map`. #### In-place map operations {#Maps_inplace_operations} - `m[k] := v`: _In-place_ insert. Inserts a key `k` bound to a value `v` into a map `m`. If `m` already contains key `k`, the old value bound to `k` gets replaced by `v` in the map. `m` must refer to a mutable field in the current contract. Insertion into nested maps is supported with the syntax `m[k1][k2][...] := v`. If the intermediate keys do not exist in the nested maps, they are freshly created along with the map values they are associated with. - `x <- m[k]`: _In-place_ local fetch. Fetches the value associated with the key `k` in the map `m`. `m` must refer to a mutable field in the current contract. If `k` has an associated value `v` in `m`, then the results of the fetch is `Some v` (see the `Option` type below), otherwise the result is `None`. After the fetch, the result gets bound to the local variable `x`. Fetching from nested maps is supported with the syntax `x <- m[k1][k2][...]`. If one or more of the intermediate keys do not exist in the corresponding map, the result of the fetch is `None`. - `x <- & c.m[k]`: _In-place_ remote fetch. Works in the same way as the local fetch operation, except that `m` must refer to a mutable field in the contract at address `c`. - `x <- exists m[k]`: _In-place_ local key existence check. `m` must refer to a mutable field in the current contract. If `k` has an associated value in the map `m` then the result (of type `Bool`) of the check is `True`, otherwise the result is `False`. After the check, the result gets bound to the local variable `x`. Existence checks through nested maps is supported with the syntax `x <- exists m[k1][k2][...]`. If one or more of the intermediate keys do not exist in the corresponding map, the result is `False`. - `b <- & exists c.m[k]`: _In-place_ remote key existence check. Works in the same way as the local key existence check, except that `m` must refer to a mutable field in the contract at address `c`. - `delete m[k]`: _In-place_ remove. Removes a key `k` and its associated value from the map `m`. The identifier `m` must refer to a mutable field in the current contract. Removal from nested maps is supported with the syntax `delete m[k1][k2][...]`. If one or more of the intermediate keys do not exist in the corresponding map, then the operation has no effect. Note that in the case of a nested removal `delete m[k1][...][kn-1][kn]`, only the key-value association of `kn` is removed. The key-value bindings of `k1` to `kn-1` will remain in the map. #### Functional map operations {#Maps_copying_builtins} - `builtin put m k v`: Insert a key `k` bound to a value `v` into a map `m`. Returns a new map which is a copy of the `m` but with `k` associated with `v`. If `m` already contains key `k`, the old value bound to `k` gets replaced by `v` in the result map. The value of `m` is unchanged. The `put` function is typically used in library functions. Note that `put` makes a copy of `m` before inserting the key-value pair. - `builtin get m k`: Fetch the value associated with the key `k` in the map `m`. Returns an optional value (see the `Option` type below) \-- if `k` has an associated value `v` in `m`, then the result is `Some v`, otherwise the result is `None`. The `get` function is typically used in library functions. - `builtin contains m k`: Is the key `k` associated with a value in the map `m`? Returns a `Bool`. The `contains` function is typically used in library functions. - `builtin remove m k`: Remove a key `k` and its associated value from the map `m`. Returns a new map which is a copy of `m` but with `k` being unassociated with a value. The value of `m` is unchanged. If `m` does not contain key `k` the `remove` function simply returns a copy of `m` with no indication that `k` is missing. The `remove` function is typically used in library functions. Note that `remove` makes a copy of `m` before removing the key-value pair. - `builtin to_list m`: Convert a map `m` to a `List (Pair kt vt)` where `kt` and `vt` are key and value types, respectively (see the `List` type below). - `builtin size m`: Return the number of bindings in map `m`. The result type is `Uint32`. Calling this builtin consumes a small _constant_ amount of gas. But calling it directly on a _field_ map is not supported, meaning that getting the size of field maps while avoiding expensive copying needs some more scaffolding, which you can find out about in the `Field map size <field_map_size>`{.interpreted-text role="ref"} section. ::: {.note} ::: {.title} Note ::: Builtin functions `put` and `remove` return a new map, which is a possibly modified copy of the original map. This may affect performance! ::: ::: {.note} ::: {.title} Note ::: Empty maps can be con