diff --git a/.gitignore b/.gitignore index 78df591..55b9e7b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ rebar3.crashdump aesophia .qcci current_counterexample.eqc +.history \ No newline at end of file diff --git a/docs/docs/contracts.md b/docs/docs/contracts.md new file mode 100644 index 0000000..0c1a7ce --- /dev/null +++ b/docs/docs/contracts.md @@ -0,0 +1,706 @@ +## Contracts + +The main unit of code in Sophia is the *contract*. + +- A contract implementation, or simply a contract, is the code for a + smart contract and consists of a list of types, entrypoints and local + functions. Only the entrypoints can be called from outside the contract. +- A contract instance is an entity living on the block chain (or in a state + channel). Each instance has an address that can be used to call its + entrypoints, either from another contract or in a call transaction. +- A contract may define a type `state` encapsulating its local + state. When creating a new contract the `init` entrypoint is executed and the + state is initialized to its return value. + +The language offers some primitive functions to interact with the blockchain and contracts. +Please refer to the [Chain](sophia_stdlib.md#Chain), [Contract](sophia_stdlib.md#Contract) +and the [Call](sophia_stdlib.md#Call) namespaces in the documentation. + +#### Calling other contracts + +To call a function in another contract you need the address to an instance of +the contract. The type of the address must be a contract type, which consists +of a number of type definitions and entrypoint declarations. For instance, + +```javascript +// A contract type +contract VotingType = + entrypoint vote : string => unit +``` + +Now given contract address of type `VotingType` you can call the `vote` +entrypoint of that contract: + +```javascript +contract VoteTwice = + entrypoint voteTwice(v : VotingType, alt : string) = + v.vote(alt) + v.vote(alt) +``` + +Contract calls take two optional named arguments `gas : int` and `value : int` +that lets you set a gas limit and provide tokens to a contract call. If omitted +the defaults are no gas limit and no tokens. Suppose there is a fee for voting: + +```javascript + entrypoint voteTwice(v : VotingType, fee : int, alt : string) = + v.vote(value = fee, alt) + v.vote(value = fee, alt) +``` + +Named arguments can be given in any order. + +Note that reentrant calls are not permitted. In other words, when calling +another contract it cannot call you back (directly or indirectly). + +To construct a value of a contract type you can give a contract address literal +(for instance `ct_2gPXZnZdKU716QBUFKaT4VdBZituK93KLvHJB3n4EnbrHHw4Ay`), or +convert an account address to a contract address using `Address.to_contract`. +Note that if the contract does not exist, or it doesn't have the entrypoint, or +the type of the entrypoint does not match the stated contract type, the call +fails. + +To recover the underlying `address` of a contract instance there is a field +`address : address`. For instance, to send tokens to the voting contract (given that it is payable) +without calling it you can write + +```javascript + entrypoint pay(v : VotingType, amount : int) = + Chain.spend(v.address, amount) +``` + +#### Protected contract calls + +!!! attention + This feature is not part of the current protocol version and could be implemented in a future protocol upgrade. + +If a contract call fails for any reason (for instance, the remote contract +crashes or runs out of gas, or the entrypoint doesn't exist or has the wrong +type) the parent call also fails. To make it possible to recover from failures, +contract calls takes a named argument `protected : bool` (default `false`). +The protected argument must be a literal boolean, and when set to `true` +changes the type of the contract call, wrapping the result in an `option` type. +If the call fails the result is `None`, otherwise it's `Some(r)` where `r` is +the return value of the call. +```sophia +contract VotingType = + entrypoint : vote : string => unit +contract Voter = + entrypoint tryVote(v : VotingType, alt : string) = + switch(v.vote(alt, protected = true) : option(unit)) + None => "Voting failed" + Some(_) => "Voting successful" +``` +Any gas that was consumed by the contract call before the failure stays +consumed, which means that in order to protect against the remote contract +running out of gas it is necessary to set a gas limit using the `gas` argument. +However, note that errors that would normally consume all the gas in the +transaction still only uses up the gas spent running the contract. + +### Mutable state + +Sophia does not have arbitrary mutable state, but only a limited form of +state associated with each contract instance. + +- Each contract defines a type `state` encapsulating its mutable state. + The type `state` defaults to the `unit`. +- The initial state of a contract is computed by the contract's `init` + function. The `init` function is *pure* and returns the initial state as its + return value. + If the type `state` is `unit`, the `init` function defaults to returning the value `()`. + At contract creation time, the `init` function is executed and + its result is stored as the contract state. +- The value of the state is accessible from inside the contract + through an implicitly bound variable `state`. +- State updates are performed by calling a function `put : state => unit`. +- Aside from the `put` function (and similar functions for transactions + and events), the language is purely functional. +- Functions modifying the state need to be annotated with the `stateful` keyword (see below). + +To make it convenient to update parts of a deeply nested state Sophia +provides special syntax for map/record updates. + +#### Stateful functions + +Top-level functions and entrypoints must be annotated with the +`stateful` keyword to be allowed to affect the state of the running contract. +For instance, + +```javascript + stateful entrypoint set_state(s : state) = + put(s) +``` + +Without the `stateful` annotation the compiler does not allow the call to +`put`. A `stateful` annotation is required to + +* Use a stateful primitive function. These are + - `put` + - `Chain.spend` + - `Oracle.register` + - `Oracle.query` + - `Oracle.respond` + - `Oracle.extend` + - `AENS.preclaim` + - `AENS.claim` + - `AENS.transfer` + - `AENS.revoke` +* Call a `stateful` function in the current contract +* Call another contract with a non-zero `value` argument. + +A `stateful` annotation *is not* required to + +* Read the contract state. +* Issue an event using the `event` function. +* Call another contract with `value = 0`, even if the called function is stateful. + +### Payable + +#### Payable contracts + +A concrete contract is by default *not* payable. Any attempt at spending to such +a contract (either a `Chain.spend` or a normal spend transaction) will fail. If a +contract shall be able to receive funds in this way it has to be declared `payable`: + +```javascript +// A payable contract +payable contract ExampleContract = + stateful entrypoint do_stuff() = ... +``` + +If in doubt, it is possible to check if an address is payable using +`Address.is_payable(addr)`. + +#### Payable entrypoints + +A contract entrypoint is by default *not* payable. Any call to such a function +(either a [Remote call](#calling-other-contracts) or a contract call transaction) +that has a non-zero `value` will fail. Contract entrypoints that should be called +with a non-zero value should be declared `payable`. + +```javascript +payable stateful entrypoint buy(to : address) = + if(Call.value > 42) + transfer_item(to) + else + abort("Value too low") +``` + +Note: In the Aeternity VM (AEVM) contracts and entrypoints were by default +payable until the Lima release. + +### Namespaces + +Code can be split into libraries using the `namespace` construct. Namespaces +can appear at the top-level and can contain type and function definitions, but +not entrypoints. Outside the namespace you can refer to the (non-private) names +by qualifying them with the namespace (`Namespace.name`). +For example, + +``` +namespace Library = + type number = int + function inc(x : number) : number = x + 1 + +contract MyContract = + entrypoint plus2(x) : Library.number = + Library.inc(Library.inc(x)) +``` + +Functions in namespaces have access to the same environment (including the +`Chain`, `Call`, and `Contract`, builtin namespaces) as function in a contract, +with the exception of `state`, `put` and `Chain.event` since these are +dependent on the specific state and event types of the contract. + +### Splitting code over multiple files + +!!! info + This feature is currently not supported by the [AEstudio IDE](https://studio.aepps.com/) + and shall only be used with the standalone compiler. + +Code from another file can be included in a contract using an `include` +statement. These must appear at the top-level (outside the main contract). The +included file can contain one or more namespaces and abstract contracts. For +example, if the file `library.aes` contains + +``` +namespace Library = + function inc(x) = x + 1 +``` + +you can use it from another file using an `include`: + +``` +include "library.aes" +contract MyContract = + entrypoint plus2(x) = Library.inc(Library.inc(x)) +``` + +This behaves as if the contents of `library.aes` was textually inserted into +the file, except that error messages will refer to the original source +locations. The language will try to include each file at most one time automatically, +so even cyclic includes should be working without any special tinkering. + +### Standard library + +Sophia offers [standard library](sophia_stdlib.md) which exposes some +primitive operations and some higher level utilities. The builtin +namespaces like `Chain`, `Contract`, `Map` +are included by default and are supported internally by the compiler. +Others like `List`, `Frac`, `Option` need to be manually included using the +`include` directive. For example +``` +include "List.aes" +include "Pair.aes" +-- Map is already there! + +namespace C = + entrypoint keys(m : map('a, 'b)) : list('a) = + List.map(Pair.fst, (Map.to_list(m))) +``` + +### Types +Sophia has the following types: + +| Type | Description | Example | +|----------------------|---------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| int | A 2-complement integer | ```-1``` | +| address | Aeternity address, 32 bytes | ```Call.origin``` | +| bool | A Boolean | ```true``` | +| bits | A bit field | ```Bits.none``` | +| bytes(n) | A byte array with `n` bytes | ```#fedcba9876543210``` | +| string | An array of bytes | ```"Foo"``` | +| list | A homogeneous immutable singly linked list. | ```[1, 2, 3]``` | +| ('a, 'b) => 'c | A function. Parentheses can be skipped if there is only one argument | ```(x : int, y : int) => x + y``` | +| tuple | An ordered heterogeneous array | ```(42, "Foo", true)``` | +| record | An immutable key value store with fixed key names and typed values | ``` record balance = { owner: address, value: int } ``` | +| map | An immutable key value store with dynamic mapping of keys of one type to values of one type | ```type accounts = map(string, address)``` | +| option('a) | An optional value either None or Some('a) | ```Some(42)``` | +| state | A user defined type holding the contract state | ```record state = { owner: address, magic_key: bytes(4) }``` | +| event | An append only list of blockchain events (or log entries) | ```datatype event = EventX(indexed int, string)``` | +| hash | A 32-byte hash - equivalent to `bytes(32)` | | +| signature | A signature - equivalent to `bytes(64)` | | +| Chain.ttl | Time-to-live (fixed height or relative to current block) | ```FixedTTL(1050)``` ```RelativeTTL(50)``` | +| oracle('a, 'b) | And oracle answering questions of type 'a with answers of type 'b | ```Oracle.register(acct, qfee, ttl)``` | +| oracle_query('a, 'b) | A specific oracle query | ```Oracle.query(o, q, qfee, qttl, rttl)``` | +| contract | A user defined, typed, contract address | ```function call_remote(r : RemoteContract) = r.fun()``` | + +### Literals +| Type | Constant/Literal example(s) | +| ---------- | ------------------------------- | +| int | `-1`, `2425`, `4598275923475723498573485768` | +| address | `ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt` | +| bool | `true`, `false` | +| bits | `Bits.none`, `Bits.all` | +| bytes(8) | `#fedcba9876543210` | +| string | `"This is a string"` | +| list | `[1, 2, 3]`, `[(true, 24), (false, 19), (false, -42)]` | +| tuple | `(42, "Foo", true)` | +| record | `{ owner = Call.origin, value = 100000000 }` | +| map | `{["foo"] = 19, ["bar"] = 42}`, `{}` | +| option(int) | `Some(42)`, `None` | +| state | `state{ owner = Call.origin, magic_key = #a298105f }` | +| event | `EventX(0, "Hello")` | +| hash | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` | +| signature | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` | +| Chain.ttl | `FixedTTL(1050)`, `RelativeTTL(50)` | +| oracle('a, 'b) | `ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5` | +| oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` | +| contract | `ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ` | + +### Arithmetic + +Sophia integers (`int`) are represented by 256-bit (AEVM) or arbitrary-sized (FATE) signed words and supports the following +arithmetic operations: +- addition (`x + y`) +- subtraction (`x - y`) +- multiplication (`x * y`) +- division (`x / y`), truncated towards zero +- remainder (`x mod y`), satisfying `y * (x / y) + x mod y == x` for non-zero `y` +- exponentiation (`x ^ y`) + +All operations are *safe* with respect to overflow and underflow. On AEVM they behave as the corresponding +operations on arbitrary-size integers and fail with `arithmetic_error` if the +result cannot be represented by a 256-bit signed word. For example, `2 ^ 255` +fails rather than wrapping around to -2²⁵⁵. + +The division and modulo operations also throw an arithmetic error if the +second argument is zero. + +### Bit fields + +Sophia integers do not support bit arithmetic. Instead there is a separate +type `bits`. See the standard library [documentation](sophia_stdlib.md#Bits). + +On the AEVM a bit field is represented by a 256-bit word and reading or writing +a bit outside the 0..255 range fails with an `arithmetic_error`. On FATE a bit +field can be of arbitrary size (but it is still represented by the +corresponding integer, so setting very high bits can be expensive). + +### Type aliases + +Type aliases can be introduced with the `type` keyword and can be +parameterized. For instance + +``` +type number = int +type string_map('a) = map(string, 'a) +``` + +A type alias and its definition can be used interchangeably. Sophia does not support +higher-kinded types, meaning that following type alias is invalid: `type wrap('f, 'a) = 'f('a)` + +### Algebraic data types + +Sophia supports algebraic data types (variant types) and pattern matching. Data +types are declared by giving a list of constructors with +their respective arguments. For instance, + +``` +datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b) +``` + +Elements of data types can be pattern matched against, using the `switch` construct: + +``` +function get_left(x : one_or_both('a, 'b)) : option('a) = + switch(x) + Left(x) => Some(x) + Right(_) => None + Both(x, _) => Some(x) +``` + +or directly in the left-hand side: +``` +function + get_left : one_or_both('a, 'b) => option('a) + get_left(Left(x)) = Some(x) + get_left(Right(_)) = None + get_left(Both(x, _)) = Some(x) +``` + +*NOTE: Data types cannot currently be recursive.* + +### Lists + +A Sophia list is a dynamically sized, homogenous, immutable, singly +linked list. A list is constructed with the syntax `[1, 2, 3]`. The +elements of a list can be any of datatype but they must have the same +type. The type of lists with elements of type `'e` is written +`list('e)`. For example we can have the following lists: + +``` +[1, 33, 2, 666] : list(int) +[(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(int * string) +[{[1] = "aaa", [10] = "jjj"}, {[5] = "eee", [666] = "the beast"}] : list(map(int, string)) +``` + +New elements can be prepended to the front of a list with the `::` +operator. So `42 :: [1, 2, 3]` returns the list `[42, 1, 2, 3]`. The +concatenation operator `++` appends its second argument to its first +and returns the resulting list. So concatenating two lists +`[1, 22, 33] ++ [10, 18, 55]` returns the list `[1, 22, 33, 10, 18, 55]`. + +Sophia supports list comprehensions known from languages like Python, Haskell or Erlang. +Example syntax: +``` +[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]] +// yields [12,13,14,20,21,22,30,31,32] +``` + +Lists can be constructed using the range syntax using special `..` operator: +``` +[1..4] == [1,2,3,4] +``` +The ranges are always ascending and have step equal to 1. + + +Please refer to the [standard library](sophia_stdlib.md#List) for the predefined functionalities. + +### Maps and records + +A Sophia record type is given by a fixed set of fields with associated, +possibly different, types. For instance +``` + record account = { name : string, + balance : int, + history : list(transaction) } +``` + +Maps, on the other hand, can contain an arbitrary number of key-value bindings, +but of a fixed type. The type of maps with keys of type `'k` and values of type +`'v` is written `map('k, 'v)`. The key type can be any type that does not +contain a map or a function type. + +Please refer to the [standard library](sophia_stdlib.md#Map) for the predefined functionalities. + +#### Constructing maps and records + +A value of record type is constructed by giving a value for each of the fields. +For the example above, +``` + function new_account(name) = + {name = name, balance = 0, history = []} +``` +Maps are constructed similarly, with keys enclosed in square brackets +``` + function example_map() : map(string, int) = + {["key1"] = 1, ["key2"] = 2} +``` +The empty map is written `{}`. + +#### Accessing values + +Record fields access is written `r.f` and map lookup `m[k]`. For instance, +``` + function get_balance(a : address, accounts : map(address, account)) = + accounts[a].balance +``` +Looking up a non-existing key in a map results in contract execution failing. A +default value to return for non-existing keys can be provided using the syntax +`m[k = default]`. See also `Map.member` and `Map.lookup` below. + +#### Updating a value + +Record field updates are written `r{f = v}`. This creates a new record value +which is the same as `r`, but with the value of the field `f` replaced by `v`. +Similarly, `m{[k] = v}` constructs a map with the same values as `m` except +that `k` maps to `v`. It makes no difference if `m` has a mapping for `k` or +not. + +It is possible to give a name to the old value of a field or mapping in an +update: instead of `acc{ balance = acc.balance + 100 }` it is possible to write +`acc{ balance @ b = b + 100 }`, binding `b` to `acc.balance`. When giving a +name to a map value (`m{ [k] @ x = v }`), the corresponding key must be present +in the map or execution fails, but a default value can be provided: +`m{ [k = default] @ x = v }`. In this case `x` is bound to `default` if +`k` is not in the map. + +Updates can be nested: +``` +function clear_history(a : address, accounts : map(address, account)) : map(address, account) = + accounts{ [a].history = [] } +``` +This is equivalent to `accounts{ [a] @ acc = acc{ history = [] } }` and thus +requires `a` to be present in the accounts map. To have `clear_history` create +an account if `a` is not in the map you can write (given a function `empty_account`): +``` + accounts{ [a = empty_account()].history = [] } +``` + +#### Map implementation + +Internally in the VM maps are implemented as hash maps and support fast lookup +and update. Large maps can be stored in the contract state and the size of the +map does not contribute to the gas costs of a contract call reading or updating +it. + +### Strings + +There is a builtin type `string`, which can be seen as an array of bytes. +Strings can be compared for equality (`==`, `!=`), used as keys in maps and +records, and used in builtin functions `String.length`, `String.concat` and +the hash functions described below. + +Please refer to the `String` [library documentation](sophia_stdlib.md#String). + +### Byte arrays + +Byte arrays are fixed size arrays of 8-bit integers. They are described in hexadecimal system, +for example the literal `#cafe` creates a two-element array of bytes `ca` (202) and `fe` (254) +and thus is a value of type `bytes(2)`. + +Please refer to the `Bytes` [library documentation](sophia_stdlib.md#Bytes). + + +### Cryptographic builtins + +Libraries [Crypto](sophia_stdlib.md#Crypto) and [String](sophia_stdlib.md#String) provide functions to +hash objects, verify signatures etc. The `hash` is a type alias for `bytes(32)`. + +#### AEVM note +The hash functions in `String` hash strings interpreted as byte arrays, and +the `Crypto` hash functions accept an element of any (first-order) type. The +result is the hash of the binary encoding of the argument as [described +below](#encoding-sophia-values-as-binaries). Note that this means that for `s : +string`, `String.sha3(s)` and `Crypto.sha3(s)` will give different results on AEVM. + + +### Authorization interface + +!!! attention + This feature is not part of the current protocol version and could be implemented in a future protocol upgrade. + +When a Generalized account is authorized, the authorization function needs +access to the transaction and the transaction hash for the wrapped transaction. (A `GAMetaTx` +wrapping a transaction.) The transaction and the transaction hash is available in the primitive +`Auth.tx` and `Auth.tx_hash` respectively, they are *only* available during authentication if invoked by a +normal contract call they return `None`. + +### Oracle interface +You can attach an oracle to the current contract and you can interact with oracles +through the Oracle interface. + +For a full description of how Oracle works see +[Oracles](https://github.com/aeternity/protocol/blob/master/oracles/oracles.md#oracles). +For a functionality documentation refer to the [standard library](sophia_stdlib.md#Oracle). + + +#### Example + +Example for an oracle answering questions of type `string` with answers of type `int`: +``` +contract Oracles = + + stateful entrypoint registerOracle(acct : address, + sign : signature, // Signed network id + oracle address + contract address + qfee : int, + ttl : Chain.ttl) : oracle(string, int) = + Oracle.register(acct, signature = sign, qfee, ttl) + + entrypoint queryFee(o : oracle(string, int)) : int = + Oracle.query_fee(o) + + payable stateful entrypoint createQuery(o : oracle_query(string, int), + q : string, + qfee : int, + qttl : Chain.ttl, + rttl : int) : oracle_query(string, int) = + require(qfee =< Call.value, "insufficient value for qfee") + Oracle.query(o, q, qfee, qttl, RelativeTTL(rttl)) + + stateful entrypoint extendOracle(o : oracle(string, int), + ttl : Chain.ttl) : unit = + Oracle.extend(o, ttl) + + stateful entrypoint signExtendOracle(o : oracle(string, int), + sign : signature, // Signed network id + oracle address + contract address + ttl : Chain.ttl) : unit = + Oracle.extend(o, signature = sign, ttl) + + stateful entrypoint respond(o : oracle(string, int), + q : oracle_query(string, int), + sign : signature, // Signed network id + oracle address + contract address + r : int) = + Oracle.respond(o, q, signature = sign, r) + + entrypoint getQuestion(o : oracle(string, int), + q : oracle_query(string, int)) : string = + Oracle.get_question(o, q) + + entrypoint hasAnswer(o : oracle(string, int), + q : oracle_query(string, int)) = + switch(Oracle.get_answer(o, q)) + None => false + Some(_) => true + + entrypoint getAnswer(o : oracle(string, int), + q : oracle_query(string, int)) : option(int) = + Oracle.get_answer(o, q) +``` + +#### Sanity checks + +When an Oracle literal is passed to a contract, no deep checks are performed. +For extra safety [Oracle.check](sophia_stdlib.md#check) and [Oracle.check_query](sophia_stdlib.md#check_query) +functions are provided. + +### AENS interface + +Contracts can interact with the +[Aeternity Naming System](https://github.com/aeternity/protocol/blob/master/AENS.md). +For this purpose the [AENS](sophia_stdlib.md#AENS) library was exposed. + +### Events + +Sophia contracts log structured messages to an event log in the resulting +blockchain transaction. The event log is quite similar to [Events in +Solidity](https://solidity.readthedocs.io/en/v0.4.24/contracts.html#events). +Events are further discussed in the [protocol](https://github.com/aeternity/protocol/blob/master/contracts/events.md). + + +To use events a contract must declare a datatype `event`, and events are then +logged using the `Chain.event` function: + +``` + datatype event + = Event1(int, int, string) + | Event2(string, address) + + Chain.event(e : event) : unit +``` + +The event can have 0-3 *indexed* fields, and an optional *payload* field. A +field is indexed if it fits in a 32-byte word, i.e. +- `bool` +- `int` +- `bits` +- `address` +- `oracle(_, _)` +- `oracle_query(_, _)` +- contract types +- `bytes(n)` for `n` ≤ 32, in particular `hash` + +The payload field must be either a string or a byte array of more than 32 bytes. +The fields can appear in any order. + +*NOTE:* Indexing is not part of the core aeternity node. + +Events are emitted by using the `Chain.event` function. The following function +will emit one Event of each kind in the example. + +``` + entrypoint emit_events() : () = + Chain.event(TheFirstEvent(42)) + Chain.event(AnotherEvent(Contract.address, "This is not indexed")) +``` + +#### Argument order + +It is only possible to have one (1) `string` parameter in the event, but it can +be placed in any position (and its value will end up in the `data` field), i.e. +``` +AnotherEvent(string, indexed address) + +... + +Chain.event(AnotherEvent("This is not indexed", Contract.address)) +``` +would yield exactly the same result in the example above! + +### Compiler pragmas + +To enforce that a contract is only compiled with specific versions of the +Sophia compiler, you can give one or more `@compiler` pragmas at the +top-level (typically at the beginning) of a file. For instance, to enforce that +a contract is compiled with version 4.3 of the compiler you write + +``` +@compiler >= 4.3 +@compiler < 4.4 +``` + +Valid operators in compiler pragmas are `<`, `=<`, `==`, `>=`, and `>`. Version +numbers are given as a sequence of non-negative integers separated by dots. +Trailing zeros are ignored, so `4.0.0 == 4`. If a constraint is violated an +error is reported and compilation fails. + +### Exceptions + +Contracts can fail with an (uncatchable) exception using the built-in function + +``` +abort(reason : string) : 'a +``` + +Calling abort causes the top-level call transaction to return an error result +containing the `reason` string. Only the gas used up to and including the abort +call is charged. This is different from termination due to a crash which +consumes all available gas. + +For convenience the following function is also built-in: + +``` +function require(b : bool, err : string) = + if(!b) abort(err) +``` diff --git a/docs/docs/examples.md b/docs/docs/examples.md new file mode 100644 index 0000000..f7145cb --- /dev/null +++ b/docs/docs/examples.md @@ -0,0 +1,75 @@ + +## Examples + +``` +/* + * A simple crowd-funding example + */ +contract FundMe = + + record spend_args = { recipient : address, + amount : int } + + record state = { contributions : map(address, int), + total : int, + beneficiary : address, + deadline : int, + goal : int } + + stateful function spend(args : spend_args) = + Chain.spend(args.recipient, args.amount) + + entrypoint init(beneficiary, deadline, goal) : state = + { contributions = {}, + beneficiary = beneficiary, + deadline = deadline, + total = 0, + goal = goal } + + function is_contributor(addr) = + Map.member(addr, state.contributions) + + stateful entrypoint contribute() = + if(Chain.block_height >= state.deadline) + spend({ recipient = Call.caller, amount = Call.value }) // Refund money + false + else + let amount = + switch(Map.lookup(Call.caller, state.contributions)) + None => Call.value + Some(n) => n + Call.value + put(state{ contributions[Call.caller] = amount, + total @ tot = tot + Call.value }) + true + + stateful entrypoint withdraw() = + if(Chain.block_height < state.deadline) + abort("Cannot withdraw before deadline") + if(Call.caller == state.beneficiary) + withdraw_beneficiary() + elif(is_contributor(Call.caller)) + withdraw_contributor() + else + abort("Not a contributor or beneficiary") + + stateful function withdraw_beneficiary() = + require(state.total >= state.goal, "Project was not funded") + spend({recipient = state.beneficiary, + amount = Contract.balance }) + + stateful function withdraw_contributor() = + if(state.total >= state.goal) + abort("Project was funded") + let to = Call.caller + spend({recipient = to, + amount = state.contributions[to]}) + put(state{ contributions @ c = Map.delete(to, c) }) +``` + +### Delegation signature + +Some chain operations (`Oracle.` and `AENS.`) have an +optional delegation signature. This is typically used when a user/accounts +would like to allow a contract to act on it's behalf. The exact data to be +signed varies for the different operations, but in all cases you should prepend +the signature data with the `network_id` (`ae_mainnet` for the Aeternity mainnet, etc.). diff --git a/docs/docs/extra.css b/docs/docs/extra.css new file mode 100644 index 0000000..e69de29 diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 0000000..6b1b9d5 --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,14 @@ +# The Sophia Language + +An Æternity BlockChain Language + +The Sophia is a language in the ML family. It is strongly typed and has +restricted mutable state. + +Sophia is customized for smart contracts, which can be published +to a blockchain (the Æternity BlockChain). Thus some features of conventional +languages, such as floating point arithmetic, are not present in Sophia, and +some blockchain specific primitives, constructions and types have been added. + +!!! hint + You can give all code examples you find in this doc a quick run in Aeternity's browser-based smart contract IDE, the [AEstudio IDE](https://studio.aepps.com/) ! diff --git a/docs/docs/sophia_stdlib.md b/docs/docs/sophia_stdlib.md new file mode 100644 index 0000000..ec7e49c --- /dev/null +++ b/docs/docs/sophia_stdlib.md @@ -0,0 +1,2069 @@ + + +# Standard library + +Sophia language offers standard library that consists of several namespaces. Some of them are already +in the scope and do not need any actions to be used, while the others require some files to be included. + +The out-of-the-box namespaces are: + +- [Bits](#Bits) +- [Bytes](#Bytes) +- [Int](#Int) +- [Map](#Map) +- [Address](#Address) +- [Crypto](#Crypto) +- [Auth](#Auth) +- [Oracle](#Oracle) +- [AENS](#AENS) +- [Contract](#Contract) +- [Call](#Call) +- [Chain](#Chain) + +The following ones need to be included as regular files with `.aes` suffix, for example +``` +include "List.aes" +``` + +- [List](#List) +- [Option](#Option) +- [String](#String) +- [Func](#Func) +- [Pair](#Pair) +- [Triple](#Triple) +- [BLS12_381](#BLS12_381) +- [Frac](#Frac) + +# Builtin namespaces + +They are available without any explicit includes. + +## Bits + +#### none +``` +Bits.none : bits +``` + +A bit field with all bits cleared + + +#### all +``` +Bits.all : bits +``` + +A bit field with all bits set + + +#### set +``` +Bits.set(b : bits, i : int) : bits +``` + +Set bit i + + +#### clear +``` +Bits.clear(b : bits, i : int) : bits +``` + +Clear bit i + + +#### test +``` +Bits.test(b : bits, i : int) : bool +``` + +Check if bit i is set + + +#### sum +``` +Bits.sum(b : bits) : int +``` + +Count the number of set bits + + +#### union +``` +Bits.union(a : bits, b : bits) : bits +``` + +Bitwise disjunction + + +#### intersection +``` +Bits.intersection(a : bits, b : bits) : bits +``` + +Bitwise conjunction + + +#### difference +``` +Bits.difference(a : bits, b : bits) : bits +``` + +Each bit is true if and only if it was 1 in `a` and 0 in `b` + + +## Bytes + +#### to_int +``` +Bytes.to_int(b : bytes(n)) : int +``` + +Interprets the byte array as a big endian integer + + +#### to_str +``` +Bytes.to_str(b : bytes(n)) : string +``` + +Returns the hexadecimal representation of the byte array + + +#### concat +``` +Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n) +``` + +Concatenates two byte arrays + + +#### split +``` +Bytes.split(a : bytes(m + n)) : bytes(m) * bytes(n) +``` + +Splits a byte array at given index + + + +#### to_int + ``` +Char.to_int(c : char) : int +``` + +Returns the UTF-8 codepoint of a character + + +#### from_int + +``` +Char.from_int(i : int) : option(char) + ``` + +Opposite of [to_int](#to_int). Returns `None` if the integer doesn't correspond to a single (normalized) codepoint. + + +## Int + +#### to_str +``` +Int.to_str : int => string +``` + +Casts integer to string using decimal representation + + +## Map + +#### lookup +`Map.lookup(k : 'k, m : map('k, 'v)) : option('v)` + +Returns the value under a key in given map as `Some` or `None` +if the key is not present + + +#### lookup_default +`Map.lookup_default(k : 'k, m : map('k, 'v), v : 'v) : 'v` + +Returns the value under a key in given map or the +default value `v` if the key is not present + + +#### member +`Map.member(k : 'k, m : map('k, 'v)) : bool` + +Checks if the key is present in the map + + +#### delete +`Map.delete(k : 'k, m : map('k, 'v)) : map('k, 'v)` + +Removes the key from the map + + +#### size +`Map.size(m : map('k, 'v)) : int` + +Returns the number of elements in the map + + +#### to_list +`Map.to_list(m : map('k, 'v)) : list('k * 'v)` + +Returns a list containing pairs of keys and their respective elements. + + +#### from_list +`Map.from_list(m : list('k * 'v)) : map('k, 'v)` + +Turns a list of pairs of form `(key, value)` into a map + + + +## Address + +#### to_str +``` +Address.to_str(a : address) : string +``` + +Base58 encoded string + + +#### is_contract +``` +Address.is_contract(a : address) : bool +``` + +Is the address a contract + + +#### is_oracle +``` +Address.is_oracle(a : address) : bool +``` + +Is the address a registered oracle + + +#### is_payable +``` +Address.is_payable(a : address) : bool +``` + +Can the address be spent to + + +#### to_contract +``` +Address.to_contract(a : address) : C +``` + +Cast address to contract type C (where `C` is a contract) + + +## Crypto + +#### sha3 +``` +Crypto.sha3(x : 'a) : hash +``` + +Hash any object to SHA3 + + +#### sha256 +``` +Crypto.sha256(x : 'a) : hash +``` + +Hash any object to SHA256 + + +#### blake2b +``` +Crypto.blake2b(x : 'a) : hash +``` + +Hash any object to blake2b + + +#### verify_sig +``` +Crypto.verify_sig(msg : hash, pubkey : address, sig : signature) : bool +``` + +Checks if the signature of `msg` was made using private key corresponding to +the `pubkey` + +#### ecverify_secp256k1 +``` +Crypto.ecverify_secp256k1(msg : hash, addr : bytes(20), sig : bytes(65)) : bool +``` + +Verifies a signature for a msg against an Ethereum style address + + +#### ecrecover_secp256k1 +``` +Crypto.ecrecover_secp256k1(msg : hash, sig : bytes(65)) : option(bytes(20)) +``` + +Recovers the Ethereum style address from a msg hash and respective signature + + +#### verify_sig_secp256k1 +``` +Crypto.verify_sig_secp256k1(msg : hash, pubkey : bytes(64), sig : bytes(64)) : bool +``` + + + +## Auth + +#### tx_hash +``` +Auth.tx_hash : option(Chain.tx) +``` + +Gets the transaction hash during authentication. + +## Oracle + +#### register +``` +Oracle.register(, acct : address, qfee : int, ttl : Chain.ttl) : oracle('a, 'b) +``` + +Registers new oracle answering questions of type `'a` with answers of type `'b`. + +* The `acct` is the address of the oracle to register (can be the same as the contract). +* `signature` is a signature proving that the contract is allowed to register the account - + the account address + the contract address (concatenated as byte arrays) is + [signed](./examples.md#delegation-signature) with the + private key of the account, proving you have the private key of the oracle to be. If the + address is the same as the contract `sign` is ignored and can be left out entirely. +* The `qfee` is the minimum query fee to be paid by a user when asking a question of the oracle. +* The `ttl` is the Time To Live for the oracle, either relative to the current + height (`RelativeTTL(delta)`) or a fixed height (`FixedTTL(height)`). +* The type `'a` is the type of the question to ask. +* The type `'b` is the type of the oracle answers. + +Examples: +``` + Oracle.register(addr0, 25, RelativeTTL(400)) + Oracle.register(addr1, 25, RelativeTTL(500), signature = sign1) +``` + + +#### get_question +``` +Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a +``` + +Checks what was the question of query `q` on oracle `o` + + +#### respond +``` +Oracle.respond(, o : oracle('a, 'b), q : oracle_query('a, 'b), 'b) : unit +``` + +Responds to the question `q` on `o`. +Unless the contract address is the same as the oracle address the `signature` +(which is an optional, named argument) +needs to be provided. Proving that we have the private key of the oracle by +[signing](./examples.md#delegation-signature) the oracle query id + contract address + + +#### extend +``` +Oracle.extend(, o : oracle('a, 'b), ttl : Chain.ttl) : unit +``` + +Extends TTL of an oracle. +* `singature` is a named argument and thus optional. Must be the same as for `Oracle.register` +* `o` is the oracle being extended +* `ttl` must be `RelativeTTL`. The time to live of `o` will be extended by this value. + +#### query_fee +``` +Oracle.query_fee(o : oracle('a, 'b)) : int +``` + +Returns the query fee of the oracle + + +#### query +``` +Oracle.query(o : oracle('a, 'b), q : 'a, qfee : int, qttl : Chain.ttl, rttl : Chain.ttl) : oracle_query('a, 'b) +``` + +Asks the oracle a question. +* The `qfee` is the query fee debited to the contract account (`Contract.address`). +* The `qttl` controls the last height at which the oracle can submit a response + and can be either fixed or relative. +* The `rttl` must be relative and controls how long an answer is kept on the chain. +The call fails if the oracle could expire before an answer. + + +#### get_answer +``` +Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b) +``` + +Checks what is the optional query answer + + +#### expire + +``` +Oracle.expire(o : oracle('a, 'b)) : int +``` + +Ask the oracle when it expires. The result is the block height at which it will happen. + + +#### check +``` +Oracle.check(o : oracle('a, 'b)) : bool +``` + +Returns `true` iff the oracle `o` exists and has correct type + + +#### check_query +``` +Oracle.check_query(o : oracle('a, 'b), q : oracle_query('a, 'b)) : bool +``` + +It returns `true` iff the oracle query exist and has the expected type. + + +## AENS + +The following functionality is available for interacting with the Aeternity +Naming System (AENS). +If `owner` is equal to `Contract.address` the signature `signature` is +ignored, and can be left out since it is a named argument. Otherwise we need +a signature to prove that we are allowed to do AENS operations on behalf of +`owner` + +### Types + +#### name +``` +datatype name = Name(address, Chain.ttl, map(string, AENS.pointee)) +``` + + +#### pointee + +``` +datatype pointee = AccountPt(address) | OraclePt(address) + | ContractPt(address) | ChannelPt(address) +``` + + +### Functions + +#### resolve +``` +AENS.resolve(name : string, key : string) : option('a) +``` + +Name resolution. Here `name` should be a registered name and `key` one of the attributes +associated with this name (for instance `"account_pubkey"`). The return type +(`'a`) must be resolved at compile time to an atomic type and the value is +type checked against this type at run time. + + +#### lookup +``` +AENS.lookup(name : string) : option(AENS.name) +``` + +If `name` is an active name `AENS.lookup` returns a name object. +The three arguments to `Name` are `owner`, `expiry` and a map of the +`pointees` for the name. Note: the expiry of the name is always a fixed TTL. +For example: +``` +let Some(Name(owner, FixedTTL(expiry), ptrs)) = AENS.lookup("example.chain") +``` + + +#### preclaim +``` +AENS.preclaim(owner : address, commitment_hash : hash, ) : unit +``` + +The [signature](./examples.md#delegation-signature) should be over `owner address` + `Contract.address` +(concatenated as byte arrays). + + +#### claim +``` +AENS.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit +``` + +The [signature](./examples.md#delegation-signature) should be over `owner address` + `name_hash` + `Contract.address` +using the private key of the `owner` account for signing. + + +#### transfer +``` +AENS.transfer(owner : address, new_owner : address, name : string, ) : unit +``` + +Transfers name to the new owner. + +The [signature](./examples.md#delegation-signature) should be over `owner address` + `name_hash` + `Contract.address` +using the private key of the `owner` account for signing. + + +#### revoke +``` +AENS.revoke(owner : address, name : string, ) : unit +``` + +Revokes the name to extend the ownership time. + +The [signature](./examples.md#delegation-signature) should be over `owner address` + `name_hash` + `Contract.address` +using the private key of the `owner` account for signing. + + +#### update +``` +AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int), + new_ptrs : map(string, AENS.pointee), ) : unit +``` + +Updates the name. If the optional parameters are set to `None` that parameter +will not be updated, for example if `None` is passed as `expiry` the expiry +block of the name is not changed. + + +## Contract + +Values related to the current contract + +#### creator +``` +Contract.creator : address +``` + +Address of the entity that signed the contract creation transaction + + +#### address +``` +Contract.address : address +``` + +Address of the contract account + + +#### balance +``` +Contract.balance : int +``` + +Amount of coins in the contract account + + +## Call + +Values related to the call to the current contract + +#### origin +``` +Call.origin : address +``` + +The address of the account that signed the call transaction that led to this call. + + +#### caller +``` +Call.caller : address +``` + +The address of the entity (possibly another contract) calling the contract. + +#### value +``` +Call.value : int +``` + +The amount of coins transferred to the contract in the call. + + +#### gas +``` +Call.gas_price : int +``` + +The gas price of the current call. + + +#### gas +``` +Call.gas_left() : int +``` + +The amount of gas left for the current call. + + +## Chain + +Values and functions related to the chain itself and other entities that live on it. + +### Types + +#### tx +``` +record tx = { paying_for : option(Chain.paying_for_tx) + , ga_metas : list(Chain.ga_meta_tx) + , actor : address + , fee : int + , ttl : int + , tx : Chain.base_tx } +``` + +#### ga_meta_tx +``` +datatype ga_meta_tx = GAMetaTx(address, int) +``` + +#### paying_for_tx +``` +datatype paying_for_tx = PayingForTx(address, int) +``` + + +#### base_tx +``` +datatype base_tx = SpendTx(address, int, string) + | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx + | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string) + | NameRevokeTx(hash) | NameTransferTx(address, string) + | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) | + | ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address) + | ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address) + | ContractCreateTx(int) | ContractCallTx(address, int) + | GAAttachTx +``` + + +### Functions + +#### balance +``` +Chain.balance(a : address) : int +``` + +The balance of account `a`. + + +#### block_hash +``` +Chain.block_hash(h : int) : option(bytes(32)) +``` + +The hash of the block at height `h`. + + +#### block_height +``` +Chain.block_height : int" +``` + +The height of the current block (i.e. the block in which the current call will be included). + + +#### coinbase +``` +Chain.coinbase : address +``` + +The address of the account that mined the current block. + + +#### timestamp +``` +Chain.timestamp : int +``` + +The timestamp of the current block. + + +#### difficulty +``` +Chain.difficulty : int +``` + +The difficulty of the current block. + + +#### gas +``` +Chain.gas_limit : int +``` + +The gas limit of the current block. + + +#### event +``` +Chain.event(e : event) : unit +``` +Emits the event. To use this function one needs to define the `event` type as a `datatype` in the contract. + + +# Includable namespaces + +These need to be explicitly included (with `.aes` suffix) + +## List + +This module contains common operations on lists like constructing, querying, traversing etc. + +#### is_empty +``` +List.is_empty(l : list('a)) : bool +``` + +Returns `true` iff the list is equal to `[]`. + + +#### first +``` +List.first(l : list('a)) : option('a) +``` + +Returns `Some` of the first element of a list or `None` if the list is empty. + + +#### tail +``` +List.tail(l : list('a)) : option(list('a)) +``` + +Returns `Some` of a list without its first element or `None` if the list is empty. + + +#### last +``` +List.last(l : list('a)) : option('a) +``` + +Returns `Some` of the last element of a list or `None` if the list is empty. + + +#### find +``` +List.find(p : 'a => bool, l : list('a)) : option('a) +``` + +Finds first element of `l` fulfilling predicate `p` as `Some` or `None` if no such element exists. + + +#### find_indices +``` +List.find_indices(p : 'a => bool, l : list('a)) : list(int) +``` + +Returns list of all indices of elements from `l` that fulfill the predicate `p`. + + +#### nth +``` +List.nth(n : int, l : list('a)) : option('a) +``` + +Gets `n`th element of `l` as `Some` or `None` if `l` is shorter than `n + 1` or `n` is negative. + + +#### get +``` +List.get(n : int, l : list('a)) : 'a +``` + +Gets `n`th element of `l` forcefully, throwing and error if `l` is shorter than `n + 1` or `n` is negative. + + +#### length +``` +List.length(l : list('a)) : int +``` + +Returns length of a list. + + +#### from_to +``` +List.from_to(a : int, b : int) : list(int) +``` + +Creates an ascending sequence of all integer numbers between `a` and `b` (including `a` and `b`). + + +#### from_to_step +``` +List.from_to_step(a : int, b : int, step : int) : list(int) +``` + +Creates an ascending sequence of integer numbers betweeen `a` and `b` jumping by given `step`. Includes `a` and takes `b` only if `(b - a) mod step == 0`. `step` should be bigger than 0. + + +#### replace_at +``` +List.replace_at(n : int, e : 'a, l : list('a)) : list('a) +``` + +Replaces `n`th element of `l` with `e`. Throws an error if `n` is negative or would cause an overflow. + + +#### insert_at +``` +List.insert_at(n : int, e : 'a, l : list('a)) : list('a) +``` + +Inserts `e` into `l` to be on position `n` by shifting following elements further. For instance, +``` +insert_at(2, 9, [1,2,3,4]) +``` +will yield `[1,2,9,3,4]`. + + +#### insert_by +``` +List.insert_by(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) +``` + +Assuming that cmp represents `<` comparison, inserts `x` before the first element in the list `l` which is greater than it. For instance, +``` +insert_by((a, b) => a < b, 4, [1,2,3,5,6,7]) +``` +will yield `[1,2,3,4,5,6,7]` + + +#### foldr +``` +List.foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b +``` + +Right fold of a list. Assuming `l = [x, y, z]` will return `f(x, f(y, f(z, nil)))`. +Not tail recursive. + + +#### foldl +``` +List.foldl(rcons : ('b, 'a) => 'b, acc : 'b, l : list('a)) : 'b +``` + +Left fold of a list. Assuming `l = [x, y, z]` will return `f(f(f(acc, x), y), z)`. +Tail recursive. + +#### foreach +``` +List.foreach(l : list('a), f : 'a => unit) : unit +``` + +Evaluates `f` on each element of a list. + + +#### reverse +``` +List.reverse(l : list('a)) : list('a) +``` + +Returns a copy of `l` with reversed order of elements. + + +#### map +``` +List.map(f : 'a => 'b, l : list('a)) : list('b) +``` + +Maps function `f` over a list. For instance +``` +map((x) => x == 0, [1, 2, 0, 3, 0]) +``` +will yield `[false, false, true, false, true]` + + +#### flat_map +``` +List.flat_map(f : 'a => list('b), l : list('a)) : list('b) +``` + +Maps `f` over a list and then flattens it. For instance +``` +flat_map((x) => [x, x * 10], [1, 2, 3]) +``` +will yield `[1, 10, 2, 20, 3, 30]` + + +#### filter +``` +List.filter(p : 'a => bool, l : list('a)) : list('a) +``` + +Filters out elements of `l` that fulfill predicate `p`. For instance +``` +filter((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3]) +``` +will yield `[1, 1, 2]` + + +#### take +``` +List.take(n : int, l : list('a)) : list('a) +``` + +Takes `n` first elements of `l`. Fails if `n` is negative. If `n` is greater than length of a list it will return whole list. + + +#### drop +``` +List.drop(n : int, l : list('a)) : list('a) +``` + +Removes `n` first elements of `l`. Fails if `n` is negative. If `n` is greater than length of a list it will return `[]`. + + +#### take_while +``` +List.take_while(p : 'a => bool, l : list('a)) : list('a) +``` + +Returns longest prefix of `l` in which all elements fulfill `p`. + + +#### drop_while +``` +List.drop_while(p : 'a => bool, l : list('a)) : list('a) +``` + +Removes longest prefix from `l` in which all elements fulfill `p`. + + +#### partition +``` +List.partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) +``` + +Separates elements of `l` that fulfill `p` and these that do not. Elements fulfilling predicate will be in the right list. For instance +``` +partition((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3]) +``` +will yield `([1, 1, 2], [-1, -2, 0, -3])` + + +#### flatten +``` +List.flatten(ll : list(list('a))) : list('a) +``` + +Flattens a list of lists into a one list. + + +#### all +``` +List.all(p : 'a => bool, l : list('a)) : bool +``` + +Checks if all elements of a list fulfill predicate `p`. + + +#### any +``` +List.any(p : 'a => bool, l : list('a)) : bool +``` + +Checks if any element of a list fulfills predicate `p`. + + +#### sum +``` +List.sum(l : list(int)) : int +``` + +Sums elements of a list. Returns 0 if the list is empty. + + +#### product +``` +List.product(l : list(int)) : int +``` + +Multiplies elements of a list. Returns 1 if the list is empty. + + +#### zip_with +``` +List.zip_with(f : ('a, 'b) => 'c, l1 : list('a), l2 : list('b)) : list('c) +``` + +"zips" two lists with a function. n-th element of resulting list will be equal to `f(x1, x2)` where `x1` and `x2` are n-th elements of `l1` and `l2` respectively. Will cut off the tail of the longer list. For instance +``` +zip_with((a, b) => a + b, [1,2], [1,2,3]) +``` +will yield `[2,4]` + + +#### zip +``` +List.zip(l1 : list('a), l2 : list('b)) : list('a * 'b) +``` + +Special case of [zip_with](#zip_with) where the zipping function is `(a, b) => (a, b)`. + +#### unzip +``` +List.unzip(l : list('a * 'b)) : list('a) * list('b) +``` + +Opposite to the `zip` operation. Takes a list of pairs and returns pair of lists with respective elements on same indices. + + +#### sort +``` +List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) +``` + +Sorts a list using given comparator. `lesser_cmp(x, y)` should return `true` iff `x < y`. If `lesser_cmp` is not transitive or there exists an element `x` such that `lesser_cmp(x, x)` or there exists a pair of elements `x` and `y` such that `lesser_cmp(x, y) && lesser_cmp(y, x)` then the result is undefined. Currently O(n^2). + + +#### intersperse +``` +List.intersperse(delim : 'a, l : list('a)) : list('a) +``` + +Intersperses elements of `l` with `delim`. Does nothing on empty lists and singletons. For instance +``` +intersperse(0, [1, 2, 3, 4]) +``` +will yield `[1, 0, 2, 0, 3, 0, 4]` + + +#### enumerate +``` +List.enumerate(l : list('a)) : list(int * 'a) +``` + +Equivalent to [zip](#zip) with `[0..length(l)]`, but slightly faster. + + +## Option + +Common operations on `option` types and lists of `option`s. + +#### is_none +``` +Option.is_none(o : option('a)) : bool +``` + +Returns true iff `o == None` + + +#### is_some +``` +Option.is_some(o : option('a)) : bool +``` + +Returns true iff `o` is not `None`. + + +#### match +``` +Option.match(n : 'b, s : 'a => 'b, o : option('a)) : 'b +``` + +Behaves like pattern matching on `option` using two case functions. + + +#### default +``` +Option.default(def : 'a, o : option('a)) : 'a +``` + +Escapes `option` wrapping by providing default value for `None`. + + +#### force +``` +Option.force(o : option('a)) : 'a +``` + +Forcefully escapes `option` wrapping assuming it is `Some`. Throws error on `None`. + + +#### on_elem +``` +Option.on_elem(o : option('a), f : 'a => unit) : unit +``` + +Evaluates `f` on element under `Some`. Does nothing on `None`. + + +#### map +``` +Option.map(f : 'a => 'b, o : option('a)) : option('b) +``` + +Maps element under `Some`. Leaves `None` unchanged. + + +#### map2 +``` +Option.map2(f : ('a, 'b) => 'c, o1 : option('a), o2 : option('b)) : option('c) +``` + +Applies arity 2 function over two `option`s' elements. Returns `Some` iff both of `o1` and `o2` were `Some`, or `None` otherwise. For instance +``` +map2((a, b) => a + b, Some(1), Some(2)) +``` +will yield `Some(3)` and +``` +map2((a, b) => a + b, Some(1), None) +``` +will yield `None`. + + +#### map3 +``` +Option.map3(f : ('a, 'b, 'c) => 'd, o1 : option('a), o2 : option('b), o3 : option('c)) : option('d) +``` + +Same as [map2](#map2) but with arity 3 function. + + +#### app_over +``` +Option.app_over(f : option ('a => 'b), o : option('a)) : option('b) +``` + +Applies function under `option` over argument under `option`. If either of them is `None` the result will be `None` as well. For instance +``` +app_over(Some((x) => x + 1), Some(1)) +``` +will yield `Some(2)` and +``` +app_over(Some((x) => x + 1), None) +``` +will yield `None`. + + +#### flat_map +``` +Option.flat_map(f : 'a => option('b), o : option('a)) : option('b) +``` + +Performs monadic bind on an `option`. Extracts element from `o` (if present) and forms new `option` from it. For instance +``` +flat_map((x) => Some(x + 1), Some(1)) +``` +will yield `Some(2)` and +``` +flat_map((x) => Some(x + 1), None) +``` +will yield `None`. + + +#### to_list +``` +Option.to_list(o : option('a)) : list('a) +``` + +Turns `o` into an empty (if `None`) or singleton (if `Some`) list. + + +#### filter_options +``` +Option.filter_options(l : list(option('a))) : list('a) +``` + +Removes `None`s from list and unpacks all remaining `Some`s. For instance +``` +filter_options([Some(1), None, Some(2)]) +``` +will yield `[1, 2]`. + + +#### seq_options +``` +Option.seq_options(l : list (option('a))) : option (list('a)) +``` + +Tries to unpack all elements of a list from `Some`s. Returns `None` if at least element of `l` is `None`. For instance +``` +seq_options([Some(1), Some(2)]) +``` +will yield `Some([1, 2])`, but +``` +seq_options([Some(1), Some(2), None]) +``` +will yield `None`. + + +#### choose +``` +Option.choose(o1 : option('a), o2 : option('a)) : option('a) +``` + +Out of two `option`s choose the one that is `Some`, or `None` if both are `None`s. + + +#### choose_first +``` +Option.choose_first(l : list(option('a))) : option('a) +``` + +Same as [choose](#choose), but chooses from a list insted of two arguments. + + +## String + +Operations on the `string` type. A `string` is a UTF-8 encoded byte array. + +#### length +`length(s : string) : int` + +The length of a string. + +Note: not equivalent to byte size of the string, rather `List.length(String.to_list(s))` + +#### concat +``` +concat(s1 : string, s2 : string) : string +``` + +Concatenates `s1` and `s2`. + +#### concats +``` +concats(ss : list(string)) : string +``` + +Concatenates a list of strings. + +#### to\_list +``` +to_list(s : string) : list(char) +``` + +Converts a `string` to a list of `char` - the code points are normalized, but +composite characters are possibly converted to multiple `char`s. For example the +string "😜i̇" is converted to `[128540,105,775]` - where the smiley is the first +code point and the strangely dotted `i` becomes `[105, 775]`. + +#### from\_list +``` +from_list(cs : list(char)) : string +``` + +Converts a list of characters into a normalized UTF-8 string. + +#### to\_lower +``` +to_lower(s : string) : string +``` + +Converts a string to lowercase. + +#### to\_upper +``` +to_upper(s : string) : string +``` + +Converts a string to uppercase. + +#### at +``` +at(ix : int, s : string) : option(char) +``` + +Returns the character/codepoint at (zero-based) index `ix`. Basically the equivalent to +`List.nth(ix, String.to_list(s))`. + +#### split +``` +split(ix : int, s:string) : string * string +``` + +Splits a string at (zero-based) index `ix`. + +#### contains +``` +contains(str : string, pat : string) : option(int) +``` + +Searches for `pat` in `str`, returning `Some(ix)` if `pat` is a substring of +`str` starting at position `ix`, otherwise returns `None`. + +#### tokens +``` +tokens(str : string, pat : string) : list(string) +``` + +Splits `str` into tokens, `pat` is the divider of tokens. + +#### to\_int +``` +to_int(s : string) : option(int) +``` + +Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string into +an integer. If the string doesn't contain a valid number `None` is returned. + +#### sha3 +``` +sha3(s : string) : hash +``` + +Computes the SHA3/Keccak hash of the string. + +#### sha256 +``` +sha256(s : string) : hash +``` + +Computes the SHA256 hash of the string. + +#### blake2b +``` +blake2b(s : string) : hash +``` + +Computes the Blake2B hash of the string. + + +## Func + +Functional combinators. + +#### id +``` +Func.id(x : 'a) : 'a +``` + +Identity function. Returns its argument. + + +#### const +``` +Func.const(x : 'a) : 'b => 'a = (y) => x +``` + +Constant function constructor. Given `x` returns a function that returns `x` regardless of its argument. + + +#### flip +``` +Func.flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c +``` + +Switches order of arguments of arity 2 function. + + +#### comp +``` +Func.comp(f : 'b => 'c, g : 'a => 'b) : 'a => 'c +``` + +Function composition. `comp(f, g)(x) == f(g(x))`. + + +#### pipe +``` +Func.pipe(f : 'a => 'b, g : 'b => 'c) : 'a => 'c +``` + +Flipped function composition. `pipe(f, g)(x) == g(f(x))`. + + +#### rapply +``` +Func.rapply(x : 'a, f : 'a => 'b) : 'b +``` + +Reverse application. `rapply(x, f) == f(x)`. + + +#### recur +``` +Func.recur(f : ('arg => 'res, 'arg) => 'res) : 'arg => 'res +``` + +The Z combinator. Allows performing local recursion and having anonymous recursive lambdas. To make function `A => B` recursive the user needs to transform it to take two arguments instead – one of type `A => B` which is going to work as a self-reference, and the other one of type `A` which is the original argument. Therefore, transformed function should have `(A => B, A) => B` signature. + +Example usage: +``` +let factorial = recur((fac, n) => if(n < 2) 1 else n * fac(n - 1)) +``` + +If the function is going to take more than one argument it will need to be either tuplified or have curried out latter arguments. + +Example (factorial with custom step): + +``` +// tuplified version +let factorial_t(n, step) = + let fac(rec, args) = + let (n, step) = args + if(n < 2) 1 else n * rec((n - step, step)) + recur(fac)((n, step)) + +// curried version +let factorial_c(n, step) = + let fac(rec, n) = (step) => + if(n < 2) 1 else n * rec(n - 1)(step) + recur(fac)(n)(step) +``` + + +#### iter +``` +Func.iter(n : int, f : 'a => 'a) : 'a => 'a +``` + +`n`th composition of f with itself, for instance `iter(3, f)` is equivalent to `(x) => f(f(f(x)))`. + + +#### curry +``` +Func.curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c) +Func.curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd)) +``` + +Turns a function that takes n arguments into a curried function that takes +one argument and returns a function that waits for the rest in the same +manner. For instance `curry2((a, b) => a + b)(1)(2) == 3`. + + +#### uncurry +``` +Func.uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c +Func.uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd +``` + +Opposite to [curry](#curry). + + +#### tuplify +``` +Func.tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c +Func.tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd +``` + +Turns a function that takes n arguments into a function that takes an n-tuple. + + +#### untuplify +``` +Func.untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c +Func.untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd +``` + +Opposite to [tuplify](#tuplify). + + +## Pair + +Common operations on 2-tuples. + +#### fst +``` +Pair.fst(t : ('a * 'b)) : 'a +``` + +First element projection. + + +#### snd +``` +Pair.snd(t : ('a * 'b)) : 'b +``` + +Second element projection. + + +#### map1 +``` +Pair.map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) +``` + +Applies function over first element. + + +#### map2 +``` +Pair.map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) +``` + +Applies function over second element. + + +#### bimap +``` +Pair.bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd) +``` + +Applies functions over respective elements. + + +#### swap +``` +Pair.swap(t : ('a * 'b)) : ('b * 'a) +``` + +Swaps elements. + + +## Triple + +#### fst +``` +Triple.fst(t : ('a * 'b * 'c)) : 'a +``` + +First element projection. + + +#### snd +``` +Triple.snd(t : ('a * 'b * 'c)) : 'b +``` + +Second element projection. + + +#### thd +``` +Triple.thd(t : ('a * 'b * 'c)) : 'c +``` + +Third element projection. + + +#### map1 +``` +Triple.map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) +``` + +Applies function over first element. + + +#### map2 +``` +Triple.map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) +``` + +Applies function over second element. + + +#### map3 +``` +Triple.map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) +``` + +Applies function over third element. + + +#### trimap +``` +Triple.trimap(f : 'a => 'x, g : 'b => 'y, h : 'c => 'z, t : ('a * 'b * 'c)) : ('x * 'y * 'z) +``` + +Applies functions over respective elements. + + +#### swap +``` +Triple.swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a) +``` + +Swaps first and third element. + + +#### rotr +``` +Triple.rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) +``` + +Cyclic rotation of the elements to the right. + + +#### rotl +``` +Triple.rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a) +``` + +Cyclic rotation of the elements to the left. + +## BLS12\_381 + +### Types + +#### fp + +Built-in (Montgomery) integer representation 32 bytes + + +#### fr + +Built-in (Montgomery) integer representation 48 bytes + + +#### fp2 +``` +record fp2 = { x1 : fp, x2 : fp }` +``` + +#### g1 +``` +record g1 = { x : fp, y : fp, z : fp } +``` + + +#### g2 +``` +record g2 = { x : fp2, y : fp2, z : fp2 } +``` + + +#### gt +``` +record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp, x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp } +``` + +### Functions + +#### pairing\_check +``` +BLS12_381.pairing_check(xs : list(g1), ys : list(g2)) : bool +``` + +Pairing check of a list of points, `xs` and `ys` should be of equal length. + +#### int_to_fr +``` +BLS12_381.int_to_fr(x : int) : fr +``` + +Convert an integer to an `fr` - a 32 bytes internal (Montgomery) integer representation. + +#### int_to_fp +``` +BLS12_381.int_to_fp(x : int) : fp +``` + +Convert an integer to an `fp` - a 48 bytes internal (Montgomery) integer representation. + +#### fr_to_int +``` +BLS12_381.fr_to_int(x : fr) : int +``` + +Convert a `fr` value into an integer. + +#### fp_to_int +``` +BLS12_381.fp_to_int(x : fp) : int +``` + +Convert a `fp` value into an integer. + +#### mk_g1 +``` +BLS12_381.mk_g1(x : int, y : int, z : int) : g1 +``` + +Construct a `g1` point from three integers. + +#### mk_g2 +``` +BLS12_381.mk_g2(x1 : int, x2 : int, y1 : int, y2 : int, z1 : int, z2 : int) : g2 +``` + +Construct a `g2` point from six integers. + +#### g1_neg +``` +BLS12_381.g1_neg(p : g1) : g1 +``` + +Negate a `g1` value. + +#### g1_norm +``` +BLS12_381.g1_norm(p : g1) : g1 +``` + +Normalize a `g1` value. + +#### g1_valid +``` +BLS12_381.g1_valid(p : g1) : bool +``` + +Check that a `g1` value is a group member. + +#### g1_is_zero +``` +BLS12_381.g1_is_zero(p : g1) : bool +``` + +Check if a `g1` value corresponds to the zero value of the group. + +#### g1_add +``` +BLS12_381.g1_add(p : g1, q : g1) : g1 +``` + +Add two `g1` values. + +#### g1_mul +``` +BLS12_381.g1_mul(k : fr, p : g1) : g1 +``` + +Scalar multiplication for `g1`. + +#### g2_neg +``` +BLS12_381.g2_neg(p : g2) : g2 +``` + +Negate a `g2` value. + +#### g2_norm +``` +BLS12_381.g2_norm(p : g2) : g2 +``` + +Normalize a `g2` value. + +#### g2_valid +``` +BLS12_381.g2_valid(p : g2) : bool +``` + +Check that a `g2` value is a group member. + +#### g2_is_zero +``` +BLS12_381.g2_is_zero(p : g2) : bool +``` + +Check if a `g2` value corresponds to the zero value of the group. + +#### g2_add +``` +BLS12_381.g2_add(p : g2, q : g2) : g2 +``` + +Add two `g2` values. + +#### g2_mul +``` +BLS12_381.g2_mul(k : fr, p : g2) : g2 +``` + +Scalar multiplication for `g2`. + +#### gt_inv +``` +BLS12_381.gt_inv(p : gt) : gt +``` + +Invert a `gt` value. + +#### gt_add +``` +BLS12_381.gt_add(p : gt, q : gt) : gt +``` + +Add two `gt` values. + +#### gt_mul +``` +BLS12_381.gt_mul(p : gt, q : gt) : gt +``` + +Multiply two `gt` values. + +#### gt_pow +``` +BLS12_381.gt_pow(p : gt, k : fr) : gt +``` + +Calculate exponentiation `p ^ k`. + +#### gt_is_one +``` +BLS12_381.gt_is_one(p : gt) : bool +``` + +Compare a `gt` value to the unit value of the Gt group. + +#### pairing +``` +BLS12_381.pairing(p : g1, q : g2) : gt +``` + +Compute the pairing of a `g1` value and a `g2` value. + +#### miller_loop +``` +BLS12_381.miller_loop(p : g1, q : g2) : gt +``` + +Do the Miller loop stage of pairing for `g1` and `g2`. + +#### final_exp +``` +BLS12_381.final_exp(p : gt) : gt +``` + +Perform the final exponentiation step of pairing for a `gt` value. + +## Frac + +This namespace provides operations on rational numbers. A rational number is represented +as a fraction of two integers which are stored internally in the `frac` datatype. + +The datatype consists of three constructors `Neg/2`, `Zero/0` and `Pos/2` which determine the +sign of the number. Both values stored in `Neg` and `Pos` need to be strictly positive +integers. However, when creating a `frac` you should never use the constructors explicitly. +Instead of that, always use provided functions like `make_frac` or `from_int`. This helps +keeping the internal representation well defined. + +The described below functions take care of the normalization of the fractions – +they won't grow if it is unnecessary. Please note that the size of `frac` can be still +very big while the value is actually very close to a natural number – the division of +two extremely big prime numbers *will* be as big as both of them. To face this issue +the [optimize](#optimize) function is provided. It will approximate the value of the +fraction to fit in the given error margin and to shrink its size as much as possible. + +**Important note:** `frac` must *not* be compared using standard `<`-like operators. +The operator comparison is not possible to overload at this moment, nor the +language provides checkers to prevent unintended usage of them. Therefore the typechecker +**will** allow that and the results of such comparison will be unspecified. +You should use [lt](#lt), [geq](#geq), [eq](#eq) etc instead. + +### Types + +#### frac +``` +datatype frac = Pos(int, int) | Zero | Neg(int, int) +``` + +Internal representation of fractional numbers. First integer encodes the numerator and the second the denominator – +both must be always positive, as the sign is being handled by the choice of the constructor. + + +### Functions + +#### make_frac +`Frac.make_frac(n : int, d : int) : frac` + +Creates a fraction out of numerator and denominator. Automatically normalizes, so +`make_frac(2, 4)` and `make_frac(1, 2)` will yield same results. + + +#### num +`Frac.num(f : frac) : int` + +Returns the numerator of a fraction. + + +#### den +`Frac.den(f : frac) : int` + +Returns the denominator of a fraction. + + +#### to_pair +`Frac.to_pair(f : frac) : int * int` + +Turns a fraction into a pair of numerator and denominator. + + +#### sign +`Frac.sign(f : frac) : int` + +Returns the signum of a fraction, -1, 0, 1 if negative, zero, positive respectively. + + +#### to_str +`Frac.to_str(f : frac) : string` + +Conversion to string. Does not display division by 1 or denominator if equals zero. + + +#### simplify +`Frac.simplify(f : frac) : frac` + +Reduces fraction to normal form if for some reason it is not in it. + + +#### eq +`Frac.eq(a : frac, b : frac) : bool` + +Checks if `a` is equal to `b`. + + +#### neq +`Frac.neq(a : frac, b : frac) : bool` + +Checks if `a` is not equal to `b`. + + +#### geq +`Frac.geq(a : frac, b : frac) : bool` + +Checks if `a` is greater or equal to `b`. + + +#### leq +`Frac.leq(a : frac, b : frac) : bool` + +Checks if `a` is lesser or equal to `b`. + + +#### gt +`Frac.gt(a : frac, b : frac) : bool` + +Checks if `a` is greater than `b`. + + +#### lt +`Frac.lt(a : frac, b : frac) : bool` + +Checks if `a` is lesser than `b`. + + +#### min +`Frac.min(a : frac, b : frac) : frac` + +Chooses lesser of the two fractions. + + +#### max +`Frac.max(a : frac, b : frac) : frac` + +Chooses greater of the two fractions. + + +#### abs +`Frac.abs(f : frac) : frac` + +Absolute value. + + +#### from_int +`Frac.from_int(n : int) : frac` + +From integer conversion. Effectively `make_frac(n, 1)`. + + +#### floor +`Frac.floor(f : frac) : int` + +Rounds a fraction to the nearest lesser or equal integer. + + +#### ceil +`Frac.ceil(f : frac) : int` + +Rounds a fraction to the nearest greater or equal integer. + + +#### round_to_zero +`Frac.round_to_zero(f : frac) : int` + +Rounds a fraction towards zero. +Effectively `ceil` if lesser than zero and `floor` if greater. + + +#### round_from_zero +`Frac.round_from_zero(f : frac) : int` + +Rounds a fraction from zero. +Effectively `ceil` if greater than zero and `floor` if lesser. + + +#### round +`Frac.round(f : frac) : int` + +Rounds a fraction to a nearest integer. If two integers are in the same distance it +will choose the even one. + + +#### add +`Frac.add(a : frac, b : frac) : frac` + +Sum of the fractions. + + +#### neg +`Frac.neg(a : frac) : frac` + +Negation of the fraction. + + +#### sub +`Frac.sub(a : frac, b : frac) : frac` + +Subtraction of two fractions. + + +#### inv +`Frac.inv(a : frac) : frac` + +Inverts a fraction. Throws error if `a` is zero. + + +#### mul +`Frac.mul(a : frac, b : frac) : frac` + +Multiplication of two fractions. + + +#### div +`Frac.div(a : frac, b : frac) : frac` + +Division of two fractions. + + +#### int_exp +`Frac.int_exp(b : frac, e : int) : frac` + +Takes `b` to the power of `e`. The exponent can be a negative value. + + +#### optimize +`Frac.optimize(f : frac, loss : frac) : frac` + +Shrink the internal size of a fraction as much as possible by approximating it to the +point where the error would exceed the `loss` value. + + +#### is_sane +`Frac.is_sane(f : frac) : bool` + +For debugging. If it ever returns false in a code that doesn't call `frac` constructors or +accept arbitrary `frac`s from the surface you should report it as a +[bug](https://github.com/aeternity/aesophia/issues/new) + +If you expect getting calls with malformed `frac`s in your contract, you should use +this function to verify the input. diff --git a/docs/docs/syntax.md b/docs/docs/syntax.md new file mode 100644 index 0000000..6a846c3 --- /dev/null +++ b/docs/docs/syntax.md @@ -0,0 +1,282 @@ +# Syntax + +### Lexical syntax + +#### Comments + +Single line comments start with `//` and block comments are enclosed in `/*` +and `*/` and can be nested. + +#### Keywords + +- `include` +- `contract` +- `datatype` +- `namespace` +- `type` +- `record` +- `payable` +- `private` +- `stateful` +- `entrypoint` +- `function` +- `import` +- `let` +- `switch` +- `if` +- `elif` +- `else` +- `true` +- `false` +- `mod` + +#### Tokens + +- `Id = [a-z_][A-Za-z0-9_']*` identifiers start with a lower case letter. +- `Con = [A-Z][A-Za-z0-9_']*` constructors start with an upper case letter. +- `QId = (Con\.)+Id` qualified identifiers (e.g. `Map.member`) +- `QCon = (Con\.)+Con` qualified constructor +- `TVar = 'Id` type variable (e.g `'a`, `'b`) +- `Int = [0-9]+(_[0-9]+)*|0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` integer literal with optional `_` separators +- `Bytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` byte array literal with optional `_` separators +- `String` string literal enclosed in `"` with escape character `\` +- `Char` character literal enclosed in `'` with escape character `\` +- `AccountAddress` base58-encoded 32 byte account pubkey with `ak_` prefix +- `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix +- `OracleAddress` base58-encoded 32 byte oracle address with `ok_` prefix +- `OracleQueryId` base58-encoded 32 byte oracle query id with `oq_` prefix + +Valid string escape codes are + +| Escape | ASCII | | +|---------------|-------------|---| +| `\b` | 8 | | +| `\t` | 9 | | +| `\n` | 10 | | +| `\v` | 11 | | +| `\f` | 12 | | +| `\r` | 13 | | +| `\e` | 27 | | +| `\xHexDigits` | *HexDigits* | | + + +See the [identifier encoding scheme](https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md) for the +details on the base58 literals. + +### Layout blocks + +Sophia uses Python-style layout rules to group declarations and statements. A +layout block with more than one element must start on a separate line and be +indented more than the currently enclosing layout block. Blocks with a single +element can be written on the same line as the previous token. + +Each element of the block must share the same indentation and no part of an +element may be indented less than the indentation of the block. For instance + +``` +contract Layout = + function foo() = 0 // no layout + function bar() = // layout block starts on next line + let x = foo() // indented more than 2 spaces + x + + 1 // the '+' is indented more than the 'x' +``` + +### Notation + +In describing the syntax below, we use the following conventions: +- Upper-case identifiers denote non-terminals (like `Expr`) or terminals with + some associated value (like `Id`). +- Keywords and symbols are enclosed in single quotes: `'let'` or `'='`. +- Choices are separated by vertical bars: `|`. +- Optional elements are enclosed in `[` square brackets `]`. +- `(` Parentheses `)` are used for grouping. +- Zero or more repetitions are denoted by a postfix `*`, and one or more + repetitions by a `+`. +- `Block(X)` denotes a layout block of `X`s. +- `Sep(X, S)` is short for `[X (S X)*]`, i.e. a possibly empty sequence of `X`s + separated by `S`s. +- `Sep1(X, S)` is short for `X (S X)*`, i.e. same as `Sep`, but must not be empty. + + +### Declarations + +A Sophia file consists of a sequence of *declarations* in a layout block. + +```c +File ::= Block(TopDecl) + +TopDecl ::= ['payable'] 'contract' Con '=' Block(Decl) + | 'namespace' Con '=' Block(Decl) + | '@compiler' PragmaOp Version + | 'include' String + +Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias + | 'record' Id ['(' TVar* ')'] '=' RecordType + | 'datatype' Id ['(' TVar* ')'] '=' DataType + | (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl) + +FunDecl ::= Id ':' Type // Type signature + | Id Args [':' Type] '=' Block(Stmt) // Definition + +PragmaOp ::= '<' | '=<' | '==' | '>=' | '>' +Version ::= Sep1(Int, '.') + +EModifier ::= 'payable' | 'stateful' +FModifier ::= 'stateful' | 'private' + +Args ::= '(' Sep(Pattern, ',') ')' +``` + +Contract declarations must appear at the top-level. + +For example, +``` +contract Test = + type t = int + entrypoint add (x : t, y : t) = x + y +``` + +There are three forms of type declarations: type aliases (declared with the +`type` keyword), record type definitions (`record`) and data type definitions +(`datatype`): + +```c +TypeAlias ::= Type +RecordType ::= '{' Sep(FieldType, ',') '}' +DataType ::= Sep1(ConDecl, '|') + +FieldType ::= Id ':' Type +ConDecl ::= Con ['(' Sep1(Type, ',') ')'] +``` + +For example, +``` +record point('a) = {x : 'a, y : 'a} +datatype shape('a) = Circle(point('a), 'a) | Rect(point('a), point('a)) +type int_shape = shape(int) +``` + +### Types + +!!! tip + + This is just the lexical type doc, find the type reference [here](contracts.md#types). + +```c +Type ::= Domain '=>' Type // Function type + | Type '(' Sep(Type, ',') ')' // Type application + | '(' Type ')' // Parens + | 'unit' | Sep(Type, '*') // Tuples + | Id | QId | TVar + +Domain ::= Type // Single argument + | '(' Sep(Type, ',') ')' // Multiple arguments +``` + +The function type arrow associates to the right. + +Example, +``` +'a => list('a) => (int * list('a)) +``` + +### Statements + +Function bodies are blocks of *statements*, where a statement is one of the following + +```c +Stmt ::= 'switch' '(' Expr ')' Block(Case) + | 'if' '(' Expr ')' Block(Stmt) + | 'elif' '(' Expr ')' Block(Stmt) + | 'else' Block(Stmt) + | 'let' LetDef + | Expr + +LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition + | Pattern '=' Block(Stmt) // Value definition + +Case ::= Pattern '=>' Block(Stmt) +Pattern ::= Expr +``` + +`if` statements can be followed by zero or more `elif` statements and an optional final `else` statement. For example, + +``` +let x : int = 4 +switch(f(x)) + None => 0 + Some(y) => + if(y > 10) + "too big" + elif(y < 3) + "too small" + else + "just right" +``` + +### Expressions + +```c +Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1 + | 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x + | Expr ':' Type // Type annotation 5 : int + | Expr BinOp Expr // Binary operator x + y + | UnOp Expr // Unary operator ! b + | Expr '(' Sep(Expr, ',') ')' // Application f(x, y) + | Expr '.' Id // Projection state.x + | Expr '[' Expr ']' // Map lookup map[key] + | Expr '{' Sep(FieldUpdate, ',') '}' // Record or map update r{ fld[key].x = y } + | '[' Sep(Expr, ',') ']' // List [1, 2, 3] + | '[' Expr '|' Sep(Generator, ',') ']' + // List comprehension [k | x <- [1], if (f(x)), let k = x+1] + | '[' Expr '..' Expr ']' // List range [1..n] + | '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val} + | '(' Expr ')' // Parens (1 + 2) * 3 + | Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token + | Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%' + | AccountAddress | ContractAddress // Chain identifiers + | OracleAddress | OracleQueryId // Chain identifiers + +Generator ::= Pattern '<-' Expr // Generator + | 'if' '(' Expr ')' // Guard + | LetDef // Definition + +LamArgs ::= '(' Sep(LamArg, ',') ')' +LamArg ::= Id [':' Type] + +FieldUpdate ::= Path '=' Expr +Path ::= Id // Record field + | '[' Expr ']' // Map key + | Path '.' Id // Nested record field + | Path '[' Expr ']' // Nested map key + +BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!=' + | '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^' +UnOp ::= '-' | '!' +``` + +### Operators types + +| Operators | Type +| --- | --- +| `-` `+` `*` `/` `mod` `^` | arithmetic operators +| `!` `&&` `\|\|` | logical operators +| `==` `!=` `<` `>` `=<` `>=` | comparison operators +| `::` `++` | list operators + +### Operator precendences + +In order of highest to lowest precedence. + +| Operators | Associativity +| --- | --- +| `!` | right +| `^` | left +| `*` `/` `mod` | left +| `-` (unary) | right +| `+` `-` | left +| `::` `++` | right +| `<` `>` `=<` `>=` `==` `!=` | none +| `&&` | right +| `\|\|` | right diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 0000000..3a305a1 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,25 @@ +site_name: Aeternity Sophia +extra_css: [extra.css] +theme: + name: readthedocs + highlightjs: true + hljs_languages: + - yaml + - rust + - erlang + +markdown_extensions: + - attr_list + - admonition + #- pymdownx.details + #- pymdownx.superfences + +nav: + - 'The sophia language' : 'index.md' + - Syntax : 'syntax.md' + - Contracts : 'contracts.md' + - 'Standard Library Functions' : 'sophia_stdlib.md' + - Examples : 'examples.md' + +repo_name: 'nikita-fuchs/sophiadocs' +repo_url: 'https://github.com/nikita-fuchs/sophiadocs' diff --git a/docs/sophia.md b/docs/sophia.md index 228dea9..8870628 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -1,7 +1,10 @@ + + **Table of Contents** +- [-](#-) - [Language Features](#language-features) - [Contracts](#contracts) - [Calling other contracts](#calling-other-contracts) @@ -27,7 +30,7 @@ - [Map implementation](#map-implementation) - [Strings](#strings) - [Byte arrays](#byte-arrays) - - [Cryptographic builins](#cryptographic-builins) + - [Cryptographic builtins](#cryptographic-builtins) - [AEVM note](#aevm-note) - [Authorization interface](#authorization-interface) - [Oracle interface](#oracle-interface) @@ -52,6 +55,7 @@ - [Operators types](#operators-types) - [Operator precendences](#operator-precendences) - [Examples](#examples) + - [Delegation signature](#delegation-signature) ## The Sophia Language @@ -75,10 +79,10 @@ The main unit of code in Sophia is the *contract*. - A contract instance is an entity living on the block chain (or in a state channel). Each instance has an address that can be used to call its entrypoints, either from another contract or in a call transaction. -- A contract may define a type `state` encapsulating its local +- A contract may define a type `state` encapsulating its local mutable state. When creating a new contract the `init` entrypoint is executed and the state is initialized to its return value. - + The language offers some primitive functions to interact with the blockchain and contracts. Please refer to the [Chain](sophia_stdlib.md#Chain), [Contract](sophia_stdlib.md#Contract) and the [Call](sophia_stdlib.md#Call) namespaces in the documentation. @@ -538,7 +542,7 @@ Strings can be compared for equality (`==`, `!=`), used as keys in maps and records, and used in builtin functions `String.length`, `String.concat` and the hash functions described below. -Please refer to the `Map` [library documentation](sophia_stdlib.md#String). +Please refer to the `String` [library documentation](sophia_stdlib.md#String). ### Byte arrays @@ -549,7 +553,7 @@ and thus is a value of type `bytes(2)`. Please refer to the `Bytes` [library documentation](sophia_stdlib.md#Bytes). -### Cryptographic builins +### Cryptographic builtins Libraries [Crypto](sophia_stdlib.md#Crypto) and [String](sophia_stdlib.md#String) provide functions to hash objects, verify signatures etc. The `hash` is a type alias for `bytes(32)`. @@ -565,11 +569,10 @@ string`, `String.sha3(s)` and `Crypto.sha3(s)` will give different results on AE ### Authorization interface When a Generalized account is authorized, the authorization function needs -access to the transaction hash for the wrapped transaction. (A `GAMetaTx` -wrapping a transaction.) The transaction hash is available in the primitive -`Auth.tx_hash`, it is *only* available during authentication if invoked by a -normal contract call it returns `None`. - +access to the transaction and the transaction hash for the wrapped transaction. (A `GAMetaTx` +wrapping a transaction.) The transaction and the transaction hash is available in the primitive +`Auth.tx` and `Auth.tx_hash` respectively, they are *only* available during authentication if invoked by a +normal contract call they return `None`. ### Oracle interface You can attach an oracle to the current contract and you can interact with oracles @@ -645,7 +648,6 @@ Contracts can interact with the [Aeternity Naming System](https://github.com/aeternity/protocol/blob/master/AENS.md). For this purpose the [AENS](sophia_stdlib.md#AENS) library was exposed. - ### Events Sophia contracts log structured messages to an event log in the resulting @@ -1069,3 +1071,11 @@ contract FundMe = amount = state.contributions[to]}) put(state{ contributions @ c = Map.delete(to, c) }) ``` + +### Delegation signature + +Some chain operations (`Oracle.` and `AENS.`) have an +optional delegation signature. This is typically used when a user/accounts +would like to allow a contract to act on it's behalf. The exact data to be +signed varies for the different operations, but in all cases you should prepend +the signature data with the `network_id` (`ae_mainnet` for the Aeternity mainnet, etc.). diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 6554810..3394056 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -13,7 +13,6 @@ in the scope and do not need any actions to be used, while the others require so The out-of-the-box namespaces are: - [Bits](#Bits) -- [String](#String) - [Bytes](#Bytes) - [Int](#Int) - [Map](#Map) @@ -33,6 +32,7 @@ include "List.aes" - [List](#List) - [Option](#Option) +- [String](#String) - [Func](#Func) - [Pair](#Pair) - [Triple](#Triple) @@ -45,7 +45,7 @@ They are available without any explicit includes. ## Bits -### none +#### none ``` Bits.none : bits ``` @@ -53,7 +53,7 @@ Bits.none : bits A bit field with all bits cleared -### all +#### all ``` Bits.all : bits ``` @@ -61,7 +61,7 @@ Bits.all : bits A bit field with all bits set -### set +#### set ``` Bits.set(b : bits, i : int) : bits ``` @@ -69,7 +69,7 @@ Bits.set(b : bits, i : int) : bits Set bit i -### clear +#### clear ``` Bits.clear(b : bits, i : int) : bits ``` @@ -77,7 +77,7 @@ Bits.clear(b : bits, i : int) : bits Clear bit i -### test +#### test ``` Bits.test(b : bits, i : int) : bool ``` @@ -85,7 +85,7 @@ Bits.test(b : bits, i : int) : bool Check if bit i is set -### sum +#### sum ``` Bits.sum(b : bits) : int ``` @@ -93,7 +93,7 @@ Bits.sum(b : bits) : int Count the number of set bits -### union +#### union ``` Bits.union(a : bits, b : bits) : bits ``` @@ -101,7 +101,7 @@ Bits.union(a : bits, b : bits) : bits Bitwise disjunction -### intersection +#### intersection ``` Bits.intersection(a : bits, b : bits) : bits ``` @@ -109,7 +109,7 @@ Bits.intersection(a : bits, b : bits) : bits Bitwise conjunction -### difference +#### difference ``` Bits.difference(a : bits, b : bits) : bits ``` @@ -117,51 +117,9 @@ Bits.difference(a : bits, b : bits) : bits Each bit is true if and only if it was 1 in `a` and 0 in `b` -## String - -### length -``` -String.length(s : string) : int -``` - -Returns the length of a string - - -### concat -``` -String.concat(s1 : string, s2 : string) : string -``` - -Concatenates two strings - - -### sha3 -``` -String.sha3(s : string) : hash -``` - -Calculates SHA3 sum of a string. - - -### sha256 -``` -String.sha256(s : string) : hash -``` - -Calculates SHA256 sum of a string - - -### blake2b -``` -String.blake2b(s : string) : hash -``` - -Calculates blake2b of a string - - ## Bytes -### to_int +#### to_int ``` Bytes.to_int(b : bytes(n)) : int ``` @@ -169,7 +127,7 @@ Bytes.to_int(b : bytes(n)) : int Interprets the byte array as a big endian integer -### to_str +#### to_str ``` Bytes.to_str(b : bytes(n)) : string ``` @@ -177,7 +135,7 @@ Bytes.to_str(b : bytes(n)) : string Returns the hexadecimal representation of the byte array -### concat +#### concat ``` Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n) ``` @@ -185,17 +143,16 @@ Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n) Concatenates two byte arrays -### split +#### split ``` Bytes.split(a : bytes(m + n)) : bytes(m) * bytes(n) ``` Splits a byte array at given index - ## Int -### to_str +#### to_str ``` Int.to_str : int => string ``` @@ -205,45 +162,45 @@ Casts integer to string using decimal representation ## Map -### lookup +#### lookup `Map.lookup(k : 'k, m : map('k, 'v)) : option('v)` Returns the value under a key in given map as `Some` or `None` if the key is not present -### lookup_default +#### lookup_default `Map.lookup_default(k : 'k, m : map('k, 'v), v : 'v) : 'v` Returns the value under a key in given map or the default value `v` if the key is not present -### member +#### member `Map.member(k : 'k, m : map('k, 'v)) : bool` Checks if the key is present in the map -### delete +#### delete `Map.delete(k : 'k, m : map('k, 'v)) : map('k, 'v)` Removes the key from the map -### size +#### size `Map.size(m : map('k, 'v)) : int` Returns the number of elements in the map -### to_list +#### to_list `Map.to_list(m : map('k, 'v)) : list('k * 'v)` Returns a list containing pairs of keys and their respective elements. -### from_list +#### from_list `Map.from_list(m : list('k * 'v)) : map('k, 'v)` Turns a list of pairs of form `(key, value)` into a map @@ -252,7 +209,7 @@ Turns a list of pairs of form `(key, value)` into a map ## Address -### to_str +#### to_str ``` Address.to_str(a : address) : string ``` @@ -260,7 +217,7 @@ Address.to_str(a : address) : string Base58 encoded string -### is_contract +#### is_contract ``` Address.is_contract(a : address) : bool ``` @@ -268,7 +225,7 @@ Address.is_contract(a : address) : bool Is the address a contract -### is_oracle +#### is_oracle ``` Address.is_oracle(a : address) : bool ``` @@ -276,7 +233,7 @@ Address.is_oracle(a : address) : bool Is the address a registered oracle -### is_payable +#### is_payable ``` Address.is_payable(a : address) : bool ``` @@ -284,7 +241,7 @@ Address.is_payable(a : address) : bool Can the address be spent to -### to_contract +#### to_contract ``` Address.to_contract(a : address) : C ``` @@ -294,7 +251,7 @@ Cast address to contract type C (where `C` is a contract) ## Crypto -### sha3 +#### sha3 ``` Crypto.sha3(x : 'a) : hash ``` @@ -302,7 +259,7 @@ Crypto.sha3(x : 'a) : hash Hash any object to SHA3 -### sha256 +#### sha256 ``` Crypto.sha256(x : 'a) : hash ``` @@ -310,7 +267,7 @@ Crypto.sha256(x : 'a) : hash Hash any object to SHA256 -### blake2b +#### blake2b ``` Crypto.blake2b(x : 'a) : hash ``` @@ -318,7 +275,7 @@ Crypto.blake2b(x : 'a) : hash Hash any object to blake2b -### verify_sig +#### verify_sig ``` Crypto.verify_sig(msg : hash, pubkey : address, sig : signature) : bool ``` @@ -326,7 +283,7 @@ Crypto.verify_sig(msg : hash, pubkey : address, sig : signature) : bool Checks if the signature of `msg` was made using private key corresponding to the `pubkey` -### ecverify_secp256k1 +#### ecverify_secp256k1 ``` Crypto.ecverify_secp256k1(msg : hash, addr : bytes(20), sig : bytes(65)) : bool ``` @@ -334,7 +291,7 @@ Crypto.ecverify_secp256k1(msg : hash, addr : bytes(20), sig : bytes(65)) : bool Verifies a signature for a msg against an Ethereum style address -### ecrecover_secp256k1 +#### ecrecover_secp256k1 ``` Crypto.ecrecover_secp256k1(msg : hash, sig : bytes(65)) : option(bytes(20)) ``` @@ -342,7 +299,7 @@ Crypto.ecrecover_secp256k1(msg : hash, sig : bytes(65)) : option(bytes(20)) Recovers the Ethereum style address from a msg hash and respective signature -### verify_sig_secp256k1 +#### verify_sig_secp256k1 ``` Crypto.verify_sig_secp256k1(msg : hash, pubkey : bytes(64), sig : bytes(64)) : bool ``` @@ -351,16 +308,16 @@ Crypto.verify_sig_secp256k1(msg : hash, pubkey : bytes(64), sig : bytes(64)) : b ## Auth -### tx_hash +#### tx_hash ``` -Auth.tx_hash : option(hash) +Auth.tx_hash : option(Chain.tx) ``` Gets the transaction hash during authentication. ## Oracle -### register +#### register ``` Oracle.register(, acct : address, qfee : int, ttl : Chain.ttl) : oracle('a, 'b) ``` @@ -370,7 +327,7 @@ Registers new oracle answering questions of type `'a` with answers of type `'b`. * The `acct` is the address of the oracle to register (can be the same as the contract). * `signature` is a signature proving that the contract is allowed to register the account - the `network id` + `account address` + `contract address` (concatenated as byte arrays) is - signed with the + [signed](./sophia.md#delegation-signature) with the private key of the account, proving you have the private key of the oracle to be. If the address is the same as the contract `sign` is ignored and can be left out entirely. * The `qfee` is the minimum query fee to be paid by a user when asking a question of the oracle. @@ -386,7 +343,7 @@ Examples: ``` -### get_question +#### get_question ``` Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a ``` @@ -394,7 +351,7 @@ Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a Checks what was the question of query `q` on oracle `o` -### respond +#### respond ``` Oracle.respond(, o : oracle('a, 'b), q : oracle_query('a, 'b), 'b) : unit ``` @@ -403,10 +360,10 @@ Responds to the question `q` on `o`. Unless the contract address is the same as the oracle address the `signature` (which is an optional, named argument) needs to be provided. Proving that we have the private key of the oracle by -signing the `network id` + `oracle query id` + `contract address` +[signing](./sophia.md#delegation-signature) the `network id` + `oracle query id` + `contract address` -### extend +#### extend ``` Oracle.extend(, o : oracle('a, 'b), ttl : Chain.ttl) : unit ``` @@ -416,7 +373,7 @@ Extends TTL of an oracle. * `o` is the oracle being extended * `ttl` must be `RelativeTTL`. The time to live of `o` will be extended by this value. -### query_fee +#### query_fee ``` Oracle.query_fee(o : oracle('a, 'b)) : int ``` @@ -424,7 +381,7 @@ Oracle.query_fee(o : oracle('a, 'b)) : int Returns the query fee of the oracle -### query +#### query ``` Oracle.query(o : oracle('a, 'b), q : 'a, qfee : int, qttl : Chain.ttl, rttl : Chain.ttl) : oracle_query('a, 'b) ``` @@ -437,7 +394,7 @@ Asks the oracle a question. The call fails if the oracle could expire before an answer. -### get_answer +#### get_answer ``` Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b) ``` @@ -445,7 +402,16 @@ Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b) Checks what is the optional query answer -### check +#### expire + +``` +Oracle.expire(o : oracle('a, 'b)) : int +``` + +Ask the oracle when it expires. The result is the block height at which it will happen. + + +#### check ``` Oracle.check(o : oracle('a, 'b)) : bool ``` @@ -453,7 +419,7 @@ Oracle.check(o : oracle('a, 'b)) : bool Returns `true` iff the oracle `o` exists and has correct type -### check_query +#### check_query ``` Oracle.check_query(o : oracle('a, 'b), q : oracle_query('a, 'b)) : bool ``` @@ -471,7 +437,25 @@ a signature to prove that we are allowed to do AENS operations on behalf of `owner`. The [signature is tied to a network id](https://github.com/aeternity/protocol/blob/iris/consensus/consensus.md#transaction-signature), i.e. the signature material should be prefixed by the network id. -### resolve +### Types + +#### name +``` +datatype name = Name(address, Chain.ttl, map(string, AENS.pointee)) +``` + + +#### pointee + +``` +datatype pointee = AccountPt(address) | OraclePt(address) + | ContractPt(address) | ChannelPt(address) +``` + + +### Functions + +#### resolve ``` AENS.resolve(name : string, key : string) : option('a) ``` @@ -482,51 +466,76 @@ associated with this name (for instance `"account_pubkey"`). The return type type checked against this type at run time. -### preclaim +#### lookup +``` +AENS.lookup(name : string) : option(AENS.name) +``` + +If `name` is an active name `AENS.lookup` returns a name object. +The three arguments to `Name` are `owner`, `expiry` and a map of the +`pointees` for the name. Note: the expiry of the name is always a fixed TTL. +For example: +``` +let Some(Name(owner, FixedTTL(expiry), ptrs)) = AENS.lookup("example.chain") +``` + + +#### preclaim ``` AENS.preclaim(owner : address, commitment_hash : hash, ) : unit ``` -The signature should be over `network id` + `owner address` + `Contract.address` +The [signature](./sophia.md#delegation-signature) should be over `network id` + `owner address` + `Contract.address` (concatenated as byte arrays). -### claim +#### claim ``` AENS.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit ``` -The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address` +The [signature](./sophia.md#delegation-signature) should be over `network id` + `owner address` + `name_hash` + `Contract.address` using the private key of the `owner` account for signing. -### transfer +#### transfer ``` AENS.transfer(owner : address, new_owner : address, name : string, ) : unit ``` Transfers name to the new owner. -The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address` +The [signature](./sophia.md#delegation-signature) should be over `network id` + `owner address` + `name_hash` + `Contract.address` using the private key of the `owner` account for signing. -### revoke +#### revoke ``` AENS.revoke(owner : address, name : string, ) : unit ``` Revokes the name to extend the ownership time. -The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address` +The [signature](./sophia.md#delegation-signature) should be over `network id` + `owner address` + `name_hash` + `Contract.address` using the private key of the `owner` account for signing. +#### update +``` +AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int), + new_ptrs : map(string, AENS.pointee), ) : unit +``` + +Updates the name. If the optional parameters are set to `None` that parameter +will not be updated, for example if `None` is passed as `expiry` the expiry +block of the name is not changed. + + ## Contract Values related to the current contract -### creator +#### creator ``` Contract.creator : address ``` @@ -534,7 +543,7 @@ Contract.creator : address Address of the entity that signed the contract creation transaction -### address +#### address ``` Contract.address : address ``` @@ -542,7 +551,7 @@ Contract.address : address Address of the contract account -### balance +#### balance ``` Contract.balance : int ``` @@ -554,7 +563,7 @@ Amount of coins in the contract account Values related to the call to the current contract -### origin +#### origin ``` Call.origin : address ``` @@ -562,14 +571,14 @@ Call.origin : address The address of the account that signed the call transaction that led to this call. -### caller +#### caller ``` Call.caller : address ``` The address of the entity (possibly another contract) calling the contract. -### value +#### value ``` Call.value : int ``` @@ -577,7 +586,7 @@ Call.value : int The amount of coins transferred to the contract in the call. -### gas +#### gas ``` Call.gas_price : int ``` @@ -585,7 +594,7 @@ Call.gas_price : int The gas price of the current call. -### gas +#### gas ``` Call.gas_left() : int ``` @@ -597,7 +606,46 @@ The amount of gas left for the current call. Values and functions related to the chain itself and other entities that live on it. -### balance +### Types + +#### tx +``` +record tx = { paying_for : option(Chain.paying_for_tx) + , ga_metas : list(Chain.ga_meta_tx) + , actor : address + , fee : int + , ttl : int + , tx : Chain.base_tx } +``` + +#### ga_meta_tx +``` +datatype ga_meta_tx = GAMetaTx(address, int) +``` + +#### paying_for_tx +``` +datatype paying_for_tx = PayingForTx(address, int) +``` + + +#### base_tx +``` +datatype base_tx = SpendTx(address, int, string) + | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx + | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string) + | NameRevokeTx(hash) | NameTransferTx(address, string) + | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) | + | ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address) + | ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address) + | ContractCreateTx(int) | ContractCallTx(address, int) + | GAAttachTx +``` + + +### Functions + +#### balance ``` Chain.balance(a : address) : int ``` @@ -605,7 +653,7 @@ Chain.balance(a : address) : int The balance of account `a`. -### block_hash +#### block_hash ``` Chain.block_hash(h : int) : option(bytes(32)) ``` @@ -613,7 +661,7 @@ Chain.block_hash(h : int) : option(bytes(32)) The hash of the block at height `h`. -### block_height +#### block_height ``` Chain.block_height : int" ``` @@ -621,7 +669,7 @@ Chain.block_height : int" The height of the current block (i.e. the block in which the current call will be included). -### coinbase +#### coinbase ``` Chain.coinbase : address ``` @@ -629,7 +677,7 @@ Chain.coinbase : address The address of the account that mined the current block. -### timestamp +#### timestamp ``` Chain.timestamp : int ``` @@ -637,7 +685,7 @@ Chain.timestamp : int The timestamp of the current block. -### difficulty +#### difficulty ``` Chain.difficulty : int ``` @@ -645,7 +693,7 @@ Chain.difficulty : int The difficulty of the current block. -### gas +#### gas ``` Chain.gas_limit : int ``` @@ -653,7 +701,7 @@ Chain.gas_limit : int The gas limit of the current block. -### event +#### event ``` Chain.event(e : event) : unit ``` @@ -668,7 +716,7 @@ These need to be explicitly included (with `.aes` suffix) This module contains common operations on lists like constructing, querying, traversing etc. -### is_empty +#### is_empty ``` List.is_empty(l : list('a)) : bool ``` @@ -676,7 +724,7 @@ List.is_empty(l : list('a)) : bool Returns `true` iff the list is equal to `[]`. -### first +#### first ``` List.first(l : list('a)) : option('a) ``` @@ -684,7 +732,7 @@ List.first(l : list('a)) : option('a) Returns `Some` of the first element of a list or `None` if the list is empty. -### tail +#### tail ``` List.tail(l : list('a)) : option(list('a)) ``` @@ -692,7 +740,7 @@ List.tail(l : list('a)) : option(list('a)) Returns `Some` of a list without its first element or `None` if the list is empty. -### last +#### last ``` List.last(l : list('a)) : option('a) ``` @@ -715,7 +763,7 @@ List.find(p : 'a => bool, l : list('a)) : option('a) Finds first element of `l` fulfilling predicate `p` as `Some` or `None` if no such element exists. -### find_indices +#### find_indices ``` List.find_indices(p : 'a => bool, l : list('a)) : list(int) ``` @@ -723,7 +771,7 @@ List.find_indices(p : 'a => bool, l : list('a)) : list(int) Returns list of all indices of elements from `l` that fulfill the predicate `p`. -### nth +#### nth ``` List.nth(n : int, l : list('a)) : option('a) ``` @@ -731,7 +779,7 @@ List.nth(n : int, l : list('a)) : option('a) Gets `n`th element of `l` as `Some` or `None` if `l` is shorter than `n + 1` or `n` is negative. -### get +#### get ``` List.get(n : int, l : list('a)) : 'a ``` @@ -739,7 +787,7 @@ List.get(n : int, l : list('a)) : 'a Gets `n`th element of `l` forcefully, throwing and error if `l` is shorter than `n + 1` or `n` is negative. -### length +#### length ``` List.length(l : list('a)) : int ``` @@ -747,7 +795,7 @@ List.length(l : list('a)) : int Returns length of a list. -### from_to +#### from_to ``` List.from_to(a : int, b : int) : list(int) ``` @@ -755,7 +803,7 @@ List.from_to(a : int, b : int) : list(int) Creates an ascending sequence of all integer numbers between `a` and `b` (including `a` and `b`). -### from_to_step +#### from_to_step ``` List.from_to_step(a : int, b : int, step : int) : list(int) ``` @@ -763,7 +811,7 @@ List.from_to_step(a : int, b : int, step : int) : list(int) Creates an ascending sequence of integer numbers betweeen `a` and `b` jumping by given `step`. Includes `a` and takes `b` only if `(b - a) mod step == 0`. `step` should be bigger than 0. -### replace_at +#### replace_at ``` List.replace_at(n : int, e : 'a, l : list('a)) : list('a) ``` @@ -771,7 +819,7 @@ List.replace_at(n : int, e : 'a, l : list('a)) : list('a) Replaces `n`th element of `l` with `e`. Throws an error if `n` is negative or would cause an overflow. -### insert_at +#### insert_at ``` List.insert_at(n : int, e : 'a, l : list('a)) : list('a) ``` @@ -783,7 +831,7 @@ insert_at(2, 9, [1,2,3,4]) will yield `[1,2,9,3,4]`. -### insert_by +#### insert_by ``` List.insert_by(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) ``` @@ -795,7 +843,7 @@ insert_by((a, b) => a < b, 4, [1,2,3,5,6,7]) will yield `[1,2,3,4,5,6,7]` -### foldr +#### foldr ``` List.foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b ``` @@ -804,7 +852,7 @@ Right fold of a list. Assuming `l = [x, y, z]` will return `f(x, f(y, f(z, nil)) Not tail recursive. -### foldl +#### foldl ``` List.foldl(rcons : ('b, 'a) => 'b, acc : 'b, l : list('a)) : 'b ``` @@ -812,7 +860,7 @@ List.foldl(rcons : ('b, 'a) => 'b, acc : 'b, l : list('a)) : 'b Left fold of a list. Assuming `l = [x, y, z]` will return `f(f(f(acc, x), y), z)`. Tail recursive. -### foreach +#### foreach ``` List.foreach(l : list('a), f : 'a => unit) : unit ``` @@ -820,7 +868,7 @@ List.foreach(l : list('a), f : 'a => unit) : unit Evaluates `f` on each element of a list. -### reverse +#### reverse ``` List.reverse(l : list('a)) : list('a) ``` @@ -828,7 +876,7 @@ List.reverse(l : list('a)) : list('a) Returns a copy of `l` with reversed order of elements. -### map +#### map ``` List.map(f : 'a => 'b, l : list('a)) : list('b) ``` @@ -840,7 +888,7 @@ map((x) => x == 0, [1, 2, 0, 3, 0]) will yield `[false, false, true, false, true]` -### flat_map +#### flat_map ``` List.flat_map(f : 'a => list('b), l : list('a)) : list('b) ``` @@ -852,7 +900,7 @@ flat_map((x) => [x, x * 10], [1, 2, 3]) will yield `[1, 10, 2, 20, 3, 30]` -### filter +#### filter ``` List.filter(p : 'a => bool, l : list('a)) : list('a) ``` @@ -864,7 +912,7 @@ filter((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3]) will yield `[1, 1, 2]` -### take +#### take ``` List.take(n : int, l : list('a)) : list('a) ``` @@ -872,7 +920,7 @@ List.take(n : int, l : list('a)) : list('a) Takes `n` first elements of `l`. Fails if `n` is negative. If `n` is greater than length of a list it will return whole list. -### drop +#### drop ``` List.drop(n : int, l : list('a)) : list('a) ``` @@ -880,7 +928,7 @@ List.drop(n : int, l : list('a)) : list('a) Removes `n` first elements of `l`. Fails if `n` is negative. If `n` is greater than length of a list it will return `[]`. -### take_while +#### take_while ``` List.take_while(p : 'a => bool, l : list('a)) : list('a) ``` @@ -888,7 +936,7 @@ List.take_while(p : 'a => bool, l : list('a)) : list('a) Returns longest prefix of `l` in which all elements fulfill `p`. -### drop_while +#### drop_while ``` List.drop_while(p : 'a => bool, l : list('a)) : list('a) ``` @@ -896,7 +944,7 @@ List.drop_while(p : 'a => bool, l : list('a)) : list('a) Removes longest prefix from `l` in which all elements fulfill `p`. -### partition +#### partition ``` List.partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) ``` @@ -908,7 +956,7 @@ partition((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3]) will yield `([1, 1, 2], [-1, -2, 0, -3])` -### flatten +#### flatten ``` List.flatten(ll : list(list('a))) : list('a) ``` @@ -916,7 +964,7 @@ List.flatten(ll : list(list('a))) : list('a) Flattens a list of lists into a one list. -### all +#### all ``` List.all(p : 'a => bool, l : list('a)) : bool ``` @@ -924,7 +972,7 @@ List.all(p : 'a => bool, l : list('a)) : bool Checks if all elements of a list fulfill predicate `p`. -### any +#### any ``` List.any(p : 'a => bool, l : list('a)) : bool ``` @@ -932,7 +980,7 @@ List.any(p : 'a => bool, l : list('a)) : bool Checks if any element of a list fulfills predicate `p`. -### sum +#### sum ``` List.sum(l : list(int)) : int ``` @@ -940,7 +988,7 @@ List.sum(l : list(int)) : int Sums elements of a list. Returns 0 if the list is empty. -### product +#### product ``` List.product(l : list(int)) : int ``` @@ -948,7 +996,7 @@ List.product(l : list(int)) : int Multiplies elements of a list. Returns 1 if the list is empty. -### zip_with +#### zip_with ``` List.zip_with(f : ('a, 'b) => 'c, l1 : list('a), l2 : list('b)) : list('c) ``` @@ -960,14 +1008,14 @@ zip_with((a, b) => a + b, [1,2], [1,2,3]) will yield `[2,4]` -### zip +#### zip ``` List.zip(l1 : list('a), l2 : list('b)) : list('a * 'b) ``` Special case of [zip_with](#zip_with) where the zipping function is `(a, b) => (a, b)`. -### unzip +#### unzip ``` List.unzip(l : list('a * 'b)) : list('a) * list('b) ``` @@ -975,7 +1023,7 @@ List.unzip(l : list('a * 'b)) : list('a) * list('b) Opposite to the `zip` operation. Takes a list of pairs and returns pair of lists with respective elements on same indices. -### sort +#### sort ``` List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) ``` @@ -983,7 +1031,7 @@ List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) Sorts a list using given comparator. `lesser_cmp(x, y)` should return `true` iff `x < y`. If `lesser_cmp` is not transitive or there exists an element `x` such that `lesser_cmp(x, x)` or there exists a pair of elements `x` and `y` such that `lesser_cmp(x, y) && lesser_cmp(y, x)` then the result is undefined. Currently O(n^2). -### intersperse +#### intersperse ``` List.intersperse(delim : 'a, l : list('a)) : list('a) ``` @@ -995,7 +1043,7 @@ intersperse(0, [1, 2, 3, 4]) will yield `[1, 0, 2, 0, 3, 0, 4]` -### enumerate +#### enumerate ``` List.enumerate(l : list('a)) : list(int * 'a) ``` @@ -1007,7 +1055,7 @@ Equivalent to [zip](#zip) with `[0..length(l)]`, but slightly faster. Common operations on `option` types and lists of `option`s. -### is_none +#### is_none ``` Option.is_none(o : option('a)) : bool ``` @@ -1015,7 +1063,7 @@ Option.is_none(o : option('a)) : bool Returns true iff `o == None` -### is_some +#### is_some ``` Option.is_some(o : option('a)) : bool ``` @@ -1023,7 +1071,7 @@ Option.is_some(o : option('a)) : bool Returns true iff `o` is not `None`. -### match +#### match ``` Option.match(n : 'b, s : 'a => 'b, o : option('a)) : 'b ``` @@ -1031,7 +1079,7 @@ Option.match(n : 'b, s : 'a => 'b, o : option('a)) : 'b Behaves like pattern matching on `option` using two case functions. -### default +#### default ``` Option.default(def : 'a, o : option('a)) : 'a ``` @@ -1039,7 +1087,7 @@ Option.default(def : 'a, o : option('a)) : 'a Escapes `option` wrapping by providing default value for `None`. -### force +#### force ``` Option.force(o : option('a)) : 'a ``` @@ -1062,7 +1110,7 @@ Option.on_elem(o : option('a), f : 'a => unit) : unit Evaluates `f` on element under `Some`. Does nothing on `None`. -### map +#### map ``` Option.map(f : 'a => 'b, o : option('a)) : option('b) ``` @@ -1070,7 +1118,7 @@ Option.map(f : 'a => 'b, o : option('a)) : option('b) Maps element under `Some`. Leaves `None` unchanged. -### map2 +#### map2 ``` Option.map2(f : ('a, 'b) => 'c, o1 : option('a), o2 : option('b)) : option('c) ``` @@ -1086,7 +1134,7 @@ map2((a, b) => a + b, Some(1), None) will yield `None`. -### map3 +#### map3 ``` Option.map3(f : ('a, 'b, 'c) => 'd, o1 : option('a), o2 : option('b), o3 : option('c)) : option('d) ``` @@ -1094,7 +1142,7 @@ Option.map3(f : ('a, 'b, 'c) => 'd, o1 : option('a), o2 : option('b), o3 : optio Same as [map2](#map2) but with arity 3 function. -### app_over +#### app_over ``` Option.app_over(f : option ('a => 'b), o : option('a)) : option('b) ``` @@ -1110,7 +1158,7 @@ app_over(Some((x) => x + 1), None) will yield `None`. -### flat_map +#### flat_map ``` Option.flat_map(f : 'a => option('b), o : option('a)) : option('b) ``` @@ -1126,7 +1174,7 @@ flat_map((x) => Some(x + 1), None) will yield `None`. -### to_list +#### to_list ``` Option.to_list(o : option('a)) : list('a) ``` @@ -1134,7 +1182,7 @@ Option.to_list(o : option('a)) : list('a) Turns `o` into an empty (if `None`) or singleton (if `Some`) list. -### filter_options +#### filter_options ``` Option.filter_options(l : list(option('a))) : list('a) ``` @@ -1146,7 +1194,7 @@ filter_options([Some(1), None, Some(2)]) will yield `[1, 2]`. -### seq_options +#### seq_options ``` Option.seq_options(l : list (option('a))) : option (list('a)) ``` @@ -1162,7 +1210,7 @@ seq_options([Some(1), Some(2), None]) will yield `None`. -### choose +#### choose ``` Option.choose(o1 : option('a), o2 : option('a)) : option('a) ``` @@ -1170,7 +1218,7 @@ Option.choose(o1 : option('a), o2 : option('a)) : option('a) Out of two `option`s choose the one that is `Some`, or `None` if both are `None`s. -### choose_first +#### choose_first ``` Option.choose_first(l : list(option('a))) : option('a) ``` @@ -1178,11 +1226,127 @@ Option.choose_first(l : list(option('a))) : option('a) Same as [choose](#choose), but chooses from a list insted of two arguments. +## String + +Operations on the `string` type. A `string` is a UTF-8 encoded byte array. + +#### length +`length(s : string) : int` + +The length of a string. + +Note: not equivalent to byte size of the string, rather `List.length(String.to_list(s))` + +#### concat +``` +concat(s1 : string, s2 : string) : string +``` + +Concatenates `s1` and `s2`. + +#### concats +``` +concats(ss : list(string)) : string +``` + +Concatenates a list of strings. + +#### to\_list +``` +to_list(s : string) : list(char) +``` + +Converts a `string` to a list of `char` - the code points are normalized, but +composite characters are possibly converted to multiple `char`s. For example the +string "😜i̇" is converted to `[128540,105,775]` - where the smiley is the first +code point and the strangely dotted `i` becomes `[105, 775]`. + +#### from\_list +``` +from_list(cs : list(char)) : string +``` + +Converts a list of characters into a normalized UTF-8 string. + +#### to\_lower +``` +to_lower(s : string) : string +``` + +Converts a string to lowercase. + +#### to\_upper +``` +to_upper(s : string) : string +``` + +Converts a string to uppercase. + +#### at +``` +at(ix : int, s : string) : option(char) +``` + +Returns the character/codepoint at (zero-based) index `ix`. Basically the equivalent to +`List.nth(ix, String.to_list(s))`. + +#### split +``` +split(ix : int, s:string) : string * string +``` + +Splits a string at (zero-based) index `ix`. + +#### contains +``` +contains(str : string, pat : string) : option(int) +``` + +Searches for `pat` in `str`, returning `Some(ix)` if `pat` is a substring of +`str` starting at position `ix`, otherwise returns `None`. + +#### tokens +``` +tokens(str : string, pat : string) : list(string) +``` + +Splits `str` into tokens, `pat` is the divider of tokens. + +#### to\_int +``` +to_int(s : string) : option(int) +``` + +Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string into +an integer. If the string doesn't contain a valid number `None` is returned. + +#### sha3 +``` +sha3(s : string) : hash +``` + +Computes the SHA3/Keccak hash of the string. + +#### sha256 +``` +sha256(s : string) : hash +``` + +Computes the SHA256 hash of the string. + +#### blake2b +``` +blake2b(s : string) : hash +``` + +Computes the Blake2B hash of the string. + + ## Func Functional combinators. -### id +#### id ``` Func.id(x : 'a) : 'a ``` @@ -1190,7 +1354,7 @@ Func.id(x : 'a) : 'a Identity function. Returns its argument. -### const +#### const ``` Func.const(x : 'a) : 'b => 'a = (y) => x ``` @@ -1198,7 +1362,7 @@ Func.const(x : 'a) : 'b => 'a = (y) => x Constant function constructor. Given `x` returns a function that returns `x` regardless of its argument. -### flip +#### flip ``` Func.flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c ``` @@ -1206,7 +1370,7 @@ Func.flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c Switches order of arguments of arity 2 function. -### comp +#### comp ``` Func.comp(f : 'b => 'c, g : 'a => 'b) : 'a => 'c ``` @@ -1214,7 +1378,7 @@ Func.comp(f : 'b => 'c, g : 'a => 'b) : 'a => 'c Function composition. `comp(f, g)(x) == f(g(x))`. -### pipe +#### pipe ``` Func.pipe(f : 'a => 'b, g : 'b => 'c) : 'a => 'c ``` @@ -1222,7 +1386,7 @@ Func.pipe(f : 'a => 'b, g : 'b => 'c) : 'a => 'c Flipped function composition. `pipe(f, g)(x) == g(f(x))`. -### rapply +#### rapply ``` Func.rapply(x : 'a, f : 'a => 'b) : 'b ``` @@ -1230,7 +1394,7 @@ Func.rapply(x : 'a, f : 'a => 'b) : 'b Reverse application. `rapply(x, f) == f(x)`. -### recur +#### recur ``` Func.recur(f : ('arg => 'res, 'arg) => 'res) : 'arg => 'res ``` @@ -1262,7 +1426,7 @@ let factorial_c(n, step) = ``` -### iter +#### iter ``` Func.iter(n : int, f : 'a => 'a) : 'a => 'a ``` @@ -1270,7 +1434,7 @@ Func.iter(n : int, f : 'a => 'a) : 'a => 'a `n`th composition of f with itself, for instance `iter(3, f)` is equivalent to `(x) => f(f(f(x)))`. -### curry +#### curry ``` Func.curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c) Func.curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd)) @@ -1281,7 +1445,7 @@ one argument and returns a function that waits for the rest in the same manner. For instance `curry2((a, b) => a + b)(1)(2) == 3`. -### uncurry +#### uncurry ``` Func.uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c Func.uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd @@ -1290,7 +1454,7 @@ Func.uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd Opposite to [curry](#curry). -### tuplify +#### tuplify ``` Func.tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c Func.tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd @@ -1299,7 +1463,7 @@ Func.tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd Turns a function that takes n arguments into a function that takes an n-tuple. -### untuplify +#### untuplify ``` Func.untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c Func.untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd @@ -1312,7 +1476,7 @@ Opposite to [tuplify](#tuplify). Common operations on 2-tuples. -### fst +#### fst ``` Pair.fst(t : ('a * 'b)) : 'a ``` @@ -1320,7 +1484,7 @@ Pair.fst(t : ('a * 'b)) : 'a First element projection. -### snd +#### snd ``` Pair.snd(t : ('a * 'b)) : 'b ``` @@ -1328,7 +1492,7 @@ Pair.snd(t : ('a * 'b)) : 'b Second element projection. -### map1 +#### map1 ``` Pair.map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) ``` @@ -1336,7 +1500,7 @@ Pair.map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) Applies function over first element. -### map2 +#### map2 ``` Pair.map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) ``` @@ -1344,7 +1508,7 @@ Pair.map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) Applies function over second element. -### bimap +#### bimap ``` Pair.bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd) ``` @@ -1352,7 +1516,7 @@ Pair.bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd) Applies functions over respective elements. -### swap +#### swap ``` Pair.swap(t : ('a * 'b)) : ('b * 'a) ``` @@ -1362,7 +1526,7 @@ Swaps elements. ## Triple -### fst +#### fst ``` Triple.fst(t : ('a * 'b * 'c)) : 'a ``` @@ -1370,7 +1534,7 @@ Triple.fst(t : ('a * 'b * 'c)) : 'a First element projection. -### snd +#### snd ``` Triple.snd(t : ('a * 'b * 'c)) : 'b ``` @@ -1378,7 +1542,7 @@ Triple.snd(t : ('a * 'b * 'c)) : 'b Second element projection. -### thd +#### thd ``` Triple.thd(t : ('a * 'b * 'c)) : 'c ``` @@ -1386,7 +1550,7 @@ Triple.thd(t : ('a * 'b * 'c)) : 'c Third element projection. -### map1 +#### map1 ``` Triple.map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) ``` @@ -1394,7 +1558,7 @@ Triple.map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) Applies function over first element. -### map2 +#### map2 ``` Triple.map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) ``` @@ -1402,7 +1566,7 @@ Triple.map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) Applies function over second element. -### map3 +#### map3 ``` Triple.map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) ``` @@ -1410,7 +1574,7 @@ Triple.map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) Applies function over third element. -### trimap +#### trimap ``` Triple.trimap(f : 'a => 'x, g : 'b => 'y, h : 'c => 'z, t : ('a * 'b * 'c)) : ('x * 'y * 'z) ``` @@ -1418,7 +1582,7 @@ Triple.trimap(f : 'a => 'x, g : 'b => 'y, h : 'c => 'z, t : ('a * 'b * 'c)) : (' Applies functions over respective elements. -### swap +#### swap ``` Triple.swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a) ``` @@ -1426,7 +1590,7 @@ Triple.swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a) Swaps first and third element. -### rotr +#### rotr ``` Triple.rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) ``` @@ -1434,7 +1598,7 @@ Triple.rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) Cyclic rotation of the elements to the right. -### rotl +#### rotl ``` Triple.rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a) ``` @@ -1444,196 +1608,224 @@ Cyclic rotation of the elements to the left. ## BLS12\_381 ### Types -- `fp // Built-in (Montgomery) integer representation 32 bytes` -- `fr // Built-in (Montgomery) integer representation 48 bytes` -- `record fp2 = { x1 : fp, x2 : fp }` -- `record g1 = { x : fp, y : fp, z : fp }` -- `record g2 = { x : fp2, y : fp2, z : fp2 }` -- `record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp, x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp }` -### pairing\_check +#### fp + +Built-in (Montgomery) integer representation 32 bytes + + +#### fr + +Built-in (Montgomery) integer representation 48 bytes + + +#### fp2 +``` +record fp2 = { x1 : fp, x2 : fp }` +``` + +#### g1 +``` +record g1 = { x : fp, y : fp, z : fp } +``` + + +#### g2 +``` +record g2 = { x : fp2, y : fp2, z : fp2 } +``` + + +#### gt +``` +record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp, x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp } +``` + +### Functions + +#### pairing\_check ``` BLS12_381.pairing_check(xs : list(g1), ys : list(g2)) : bool ``` Pairing check of a list of points, `xs` and `ys` should be of equal length. -### int_to_fr +#### int_to_fr ``` BLS12_381.int_to_fr(x : int) : fr ``` Convert an integer to an `fr` - a 32 bytes internal (Montgomery) integer representation. -### int_to_fp +#### int_to_fp ``` BLS12_381.int_to_fp(x : int) : fp ``` Convert an integer to an `fp` - a 48 bytes internal (Montgomery) integer representation. -### fr_to_int +#### fr_to_int ``` BLS12_381.fr_to_int(x : fr) : int ``` Convert a `fr` value into an integer. -### fp_to_int +#### fp_to_int ``` BLS12_381.fp_to_int(x : fp) : int ``` Convert a `fp` value into an integer. -### mk_g1 +#### mk_g1 ``` BLS12_381.mk_g1(x : int, y : int, z : int) : g1 ``` Construct a `g1` point from three integers. -### mk_g2 +#### mk_g2 ``` BLS12_381.mk_g2(x1 : int, x2 : int, y1 : int, y2 : int, z1 : int, z2 : int) : g2 ``` Construct a `g2` point from six integers. -### g1_neg +#### g1_neg ``` BLS12_381.g1_neg(p : g1) : g1 ``` Negate a `g1` value. -### g1_norm +#### g1_norm ``` BLS12_381.g1_norm(p : g1) : g1 ``` Normalize a `g1` value. -### g1_valid +#### g1_valid ``` BLS12_381.g1_valid(p : g1) : bool ``` Check that a `g1` value is a group member. -### g1_is_zero +#### g1_is_zero ``` BLS12_381.g1_is_zero(p : g1) : bool ``` Check if a `g1` value corresponds to the zero value of the group. -### g1_add +#### g1_add ``` BLS12_381.g1_add(p : g1, q : g1) : g1 ``` Add two `g1` values. -### g1_mul +#### g1_mul ``` BLS12_381.g1_mul(k : fr, p : g1) : g1 ``` Scalar multiplication for `g1`. -### g2_neg +#### g2_neg ``` BLS12_381.g2_neg(p : g2) : g2 ``` Negate a `g2` value. -### g2_norm +#### g2_norm ``` BLS12_381.g2_norm(p : g2) : g2 ``` Normalize a `g2` value. -### g2_valid +#### g2_valid ``` BLS12_381.g2_valid(p : g2) : bool ``` Check that a `g2` value is a group member. -### g2_is_zero +#### g2_is_zero ``` BLS12_381.g2_is_zero(p : g2) : bool ``` Check if a `g2` value corresponds to the zero value of the group. -### g2_add +#### g2_add ``` BLS12_381.g2_add(p : g2, q : g2) : g2 ``` Add two `g2` values. -### g2_mul +#### g2_mul ``` BLS12_381.g2_mul(k : fr, p : g2) : g2 ``` Scalar multiplication for `g2`. -### gt_inv +#### gt_inv ``` BLS12_381.gt_inv(p : gt) : gt ``` Invert a `gt` value. -### gt_add +#### gt_add ``` BLS12_381.gt_add(p : gt, q : gt) : gt ``` Add two `gt` values. -### gt_mul +#### gt_mul ``` BLS12_381.gt_mul(p : gt, q : gt) : gt ``` Multiply two `gt` values. -### gt_pow +#### gt_pow ``` BLS12_381.gt_pow(p : gt, k : fr) : gt ``` Calculate exponentiation `p ^ k`. -### gt_is_one +#### gt_is_one ``` BLS12_381.gt_is_one(p : gt) : bool ``` Compare a `gt` value to the unit value of the Gt group. -### pairing +#### pairing ``` BLS12_381.pairing(p : g1, q : g2) : gt ``` Compute the pairing of a `g1` value and a `g2` value. -### miller_loop +#### miller_loop ``` BLS12_381.miller_loop(p : g1, q : g2) : gt ``` Do the Miller loop stage of pairing for `g1` and `g2`. -### final_exp +#### final_exp ``` BLS12_381.final_exp(p : gt) : gt ``` @@ -1664,192 +1856,205 @@ language provides checkers to prevent unintended usage of them. Therefore the ty **will** allow that and the results of such comparison will be unspecified. You should use [lt](#lt), [geq](#geq), [eq](#eq) etc instead. -### make_frac +### Types + +#### frac +``` +datatype frac = Pos(int, int) | Zero | Neg(int, int) +``` + +Internal representation of fractional numbers. First integer encodes the numerator and the second the denominator – +both must be always positive, as the sign is being handled by the choice of the constructor. + + +### Functions + +#### make_frac `Frac.make_frac(n : int, d : int) : frac` Creates a fraction out of numerator and denominator. Automatically normalizes, so `make_frac(2, 4)` and `make_frac(1, 2)` will yield same results. -### num +#### num `Frac.num(f : frac) : int` Returns the numerator of a fraction. -### den +#### den `Frac.den(f : frac) : int` Returns the denominator of a fraction. -### to_pair +#### to_pair `Frac.to_pair(f : frac) : int * int` Turns a fraction into a pair of numerator and denominator. -### sign +#### sign `Frac.sign(f : frac) : int` Returns the signum of a fraction, -1, 0, 1 if negative, zero, positive respectively. -### to_str +#### to_str `Frac.to_str(f : frac) : string` Conversion to string. Does not display division by 1 or denominator if equals zero. -### simplify +#### simplify `Frac.simplify(f : frac) : frac` Reduces fraction to normal form if for some reason it is not in it. -### eq +#### eq `Frac.eq(a : frac, b : frac) : bool` Checks if `a` is equal to `b`. -### neq +#### neq `Frac.neq(a : frac, b : frac) : bool` Checks if `a` is not equal to `b`. -### geq +#### geq `Frac.geq(a : frac, b : frac) : bool` Checks if `a` is greater or equal to `b`. -### leq +#### leq `Frac.leq(a : frac, b : frac) : bool` Checks if `a` is lesser or equal to `b`. -### gt +#### gt `Frac.gt(a : frac, b : frac) : bool` Checks if `a` is greater than `b`. -### lt +#### lt `Frac.lt(a : frac, b : frac) : bool` Checks if `a` is lesser than `b`. -### min +#### min `Frac.min(a : frac, b : frac) : frac` Chooses lesser of the two fractions. -### max +#### max `Frac.max(a : frac, b : frac) : frac` Chooses greater of the two fractions. -### abs +#### abs `Frac.abs(f : frac) : frac` Absolute value. -### from_int +#### from_int `Frac.from_int(n : int) : frac` From integer conversion. Effectively `make_frac(n, 1)`. -### floor +#### floor `Frac.floor(f : frac) : int` Rounds a fraction to the nearest lesser or equal integer. -### ceil +#### ceil `Frac.ceil(f : frac) : int` Rounds a fraction to the nearest greater or equal integer. -### round_to_zero +#### round_to_zero `Frac.round_to_zero(f : frac) : int` Rounds a fraction towards zero. Effectively `ceil` if lesser than zero and `floor` if greater. -### round_from_zero +#### round_from_zero `Frac.round_from_zero(f : frac) : int` Rounds a fraction from zero. Effectively `ceil` if greater than zero and `floor` if lesser. -### round +#### round `Frac.round(f : frac) : int` Rounds a fraction to a nearest integer. If two integers are in the same distance it will choose the even one. -### add +#### add `Frac.add(a : frac, b : frac) : frac` Sum of the fractions. -### neg +#### neg `Frac.neg(a : frac) : frac` Negation of the fraction. -### sub +#### sub `Frac.sub(a : frac, b : frac) : frac` Subtraction of two fractions. -### inv +#### inv `Frac.inv(a : frac) : frac` Inverts a fraction. Throws error if `a` is zero. -### mul +#### mul `Frac.mul(a : frac, b : frac) : frac` Multiplication of two fractions. -### div +#### div `Frac.div(a : frac, b : frac) : frac` Division of two fractions. -### int_exp +#### int_exp `Frac.int_exp(b : frac, e : int) : frac` Takes `b` to the power of `e`. The exponent can be a negative value. -### optimize +#### optimize `Frac.optimize(f : frac, loss : frac) : frac` Shrink the internal size of a fraction as much as possible by approximating it to the point where the error would exceed the `loss` value. -### is_sane +#### is_sane `Frac.is_sane(f : frac) : bool` For debugging. If it ever returns false in a code that doesn't call `frac` constructors or