diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d6231f..59355bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Removed +## [4.3.0] +### Added +- Added documentation (moved from `protocol`) +- `Frac.aes` – library for rational numbers +- Added some more meaningful error messages +- Exported several parsing functionalities + - With option `keep_included` it is possible to see which files were included during the parse + - There is a function `run_parser` that be used to evaluate any parsing rule + - Exported parsers: `body`, `type` and `decl` +### Changed +- Performance improvements in the standard library +- Fixed ACI encoder to handle `-` unary operator +- Fixed including by absolute path +- Fixed variant type printing in the ACI error messages +- Fixed pretty printing of combined function clauses +### Removed +- `let` definitions are no longer supported in the toplevel of the contract +- type declarations are no longer supported + ## [4.2.0] - 2020-01-15 ### Added - Allow separate entrypoint/function type signature and definition, and pattern @@ -53,7 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (de-)construct byte arrays. - `[a..b]` language construct, returning the list of numbers between `a` and `b` (inclusive). Returns the empty list if `a` > `b`. -- [Standard libraries] (https://github.com/aeternity/protocol/blob/master/contracts/sophia_stdlib.md) +- [Standard libraries](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md) - Checks that `init` is not called from other functions. - FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts @@ -192,10 +211,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string. -[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.2.0...HEAD -[4.2.0]: https://github.com/aeternity/aesophia/compare/v4.2.0...v4.1.0 -[4.1.0]: https://github.com/aeternity/aesophia/compare/v4.1.0...v4.0.0 -[4.0.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v3.2.0 +[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.3.0...HEAD +[4.3.0]: https://github.com/aeternity/aesophia/compare/v4.2.0...v4.3.0 +[4.2.0]: https://github.com/aeternity/aesophia/compare/v4.1.0...v4.2.0 +[4.1.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v4.1.0 +[4.0.0]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.0 [3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0 [3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0 [3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0 diff --git a/README.md b/README.md index 2f8bf0c..f4f9555 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,19 @@ This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ code to the æternity VM code. -For more information about æternity smart contracts and the sophia language see [Smart Contracts](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) and the [Sophia Language](https://github.com/aeternity/protocol/blob/master/contracts/sophia.md). - It is an OTP application written in Erlang and is by default included in [the æternity node](https://github.com/aeternity/epoch). However, it can also be included in other systems to compile contracts coded in sophia which can then be loaded into the æternity system. + +## Documentation + +* [Smart Contracts on aeternity Blockchain](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md). +* [Sophia Documentation](docs/sophia.md). +* [Sophia Standard Library](docs/sophia_stdlib.md). + + ## Versioning `aesophia` has a version that is only loosely connected to the version of the @@ -17,6 +23,7 @@ minor/patch version. The `aesophia` compiler version MUST be bumped whenever there is a change in how byte code is generated, but it MAY also be bumped upon API changes etc. + ## Interface Modules The basic modules for interfacing the compiler: diff --git a/docs/sophia.md b/docs/sophia.md new file mode 100644 index 0000000..6478a1d --- /dev/null +++ b/docs/sophia.md @@ -0,0 +1,1159 @@ + + +**Table of Contents** + +- [-](#-) +- [Language Features](#language-features) + - [Contracts](#contracts) + - [Calling other contracts](#calling-other-contracts) + - [Protected contract calls](#protected-contract-calls) + - [Mutable state](#mutable-state) + - [Stateful functions](#stateful-functions) + - [Payable](#payable) + - [Payable contracts](#payable-contracts) + - [Payable entrypoints](#payable-entrypoints) + - [Namespaces](#namespaces) + - [Splitting code over multiple files](#splitting-code-over-multiple-files) + - [Standard library](#standard-library) + - [Types](#types) + - [Literals](#literals) + - [Arithmetic](#arithmetic) + - [Bit fields](#bit-fields) + - [Type aliases](#type-aliases) + - [Algebraic data types](#algebraic-data-types) + - [Lists](#lists) + - [Maps and records](#maps-and-records) + - [Constructing maps and records](#constructing-maps-and-records) + - [Accessing values](#accessing-values) + - [Updating a value](#updating-a-value) + - [Map implementation](#map-implementation) + - [Strings](#strings) + - [Chars](#chars) + - [Byte arrays](#byte-arrays) + - [Cryptographic builins](#cryptographic-builins) + - [AEVM note](#aevm-note) + - [Authorization interface](#authorization-interface) + - [Oracle interface](#oracle-interface) + - [Example](#example) + - [Sanity checks](#sanity-checks) + - [AENS interface](#aens-interface) + - [Example](#example-1) + - [Events](#events) + - [Argument order](#argument-order) + - [Compiler pragmas](#compiler-pragmas) + - [Exceptions](#exceptions) + - [Syntax](#syntax) + - [Lexical syntax](#lexical-syntax) + - [Comments](#comments) + - [Keywords](#keywords) + - [Tokens](#tokens) + - [Layout blocks](#layout-blocks) + - [Notation](#notation) + - [Declarations](#declarations) + - [Types](#types-1) + - [Statements](#statements) + - [Expressions](#expressions) + - [Operators types](#operators-types) + - [Operator precendences](#operator-precendences) + - [Examples](#examples) + - [Delegation signature](#delegation-signature) + + +## 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. +## Language Features +### 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 + +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 + + +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). + +### Chars + +There is a builtin type `char` (the underlying representation being an integer), +mainly used to manipulate strings via `String.to_list`/`String.from_list`. + +Characters can also be introduced as character literals (`'x', '+', ...). + +Please refer to the `Char` [library documentation](sophia_stdlib.md#Char). + +### 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 builins + +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 + +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 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 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 oracle query id + 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. + +#### Example + +In this example we assume that the name `name` already exists, and is owned by +an account with address `addr`. In order to allow a contract `ct` to handle +`name` the account holder needs to create a +[signature](#delegation-signature) `sig` of `addr | name.hash | ct.address`. + +Armed with this information we can for example write a function that extends +the name if it expires within 1000 blocks: +``` + stateful entrypoint extend_if_necessary(addr : address, name : string, sig : signature) = + switch(AENS.lookup(name)) + None => () + Some(AENS.Name(_, FixedTTL(expiry), _)) => + if(Chain.block_height + 1000 > expiry) + AENS.update(addr, name, Some(RelativeTTL(50000)), None, None, signature = sig) +``` + +And we can write functions that adds and removes keys from the pointers of the +name: +``` + stateful entrypoint add_key(addr : address, name : string, key : string, + pt : AENS.pointee, sig : signature) = + switch(AENS.lookup(name)) + None => () + Some(AENS.Name(_, _, ptrs)) => + AENS.update(addr, name, None, None, Some(ptrs{[key] = pt}), signature = sig) + + stateful entrypoint delete_key(addr : address, name : string, + key : string, sig : signature) = + switch(AENS.lookup(name)) + None => () + Some(AENS.Name(_, _, ptrs)) => + let ptrs = Map.delete(key, ptrs) + AENS.update(addr, name, None, None, Some(ptrs), signature = sig) +``` + + +### 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) +``` + +## Syntax + +### Lexical syntax + +#### Comments + +Single line comments start with `//` and block comments are enclosed in `/*` +and `*/` and can be nested. + +#### Keywords + +``` +contract elif else entrypoint false function if import include let mod namespace +private payable stateful switch true type record datatype +``` + +#### 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 + +```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 + +## 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/sophia_stdlib.md b/docs/sophia_stdlib.md new file mode 100644 index 0000000..158b8df --- /dev/null +++ b/docs/sophia_stdlib.md @@ -0,0 +1,2071 @@ + + +# 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) +- [Char](#Char) +- [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 + + +## Char + +#### 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](./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. +* 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](./sophia.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](./sophia.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](./sophia.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_hash : hash, ) : unit +``` + +Transfers name to the new owner. + +The [signature](./sophia.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_hash : hash, ) : unit +``` + +Revokes the name to extend the ownership time. + +The [signature](./sophia.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/priv/stdlib/Frac.aes b/priv/stdlib/Frac.aes new file mode 100644 index 0000000..64cec1e --- /dev/null +++ b/priv/stdlib/Frac.aes @@ -0,0 +1,183 @@ +namespace Frac = + + private function gcd(a : int, b : int) = + if (b == 0) a else gcd(b, a mod b) + + private function abs_int(a : int) = if (a < 0) -a else a + + datatype frac = Pos(int, int) | Zero | Neg(int, int) + +/** Checks if the internal representation is correct. + * Numerator and denominator must be positive. + * Exposed for debug purposes + */ + function is_sane(f : frac) : bool = switch(f) + Pos(n, d) => n > 0 && d > 0 + Zero => true + Neg(n, d) => n > 0 && d > 0 + + function num(f : frac) : int = switch(f) + Pos(n, _) => n + Neg(n, _) => -n + Zero => 0 + + function den(f : frac) : int = switch(f) + Pos(_, d) => d + Neg(_, d) => d + Zero => 1 + + function to_pair(f : frac) : int * int = switch(f) + Pos(n, d) => (n, d) + Neg(n, d) => (-n, d) + Zero => (0, 1) + + function sign(f : frac) : int = switch(f) + Pos(_, _) => 1 + Neg(_, _) => -1 + Zero => 0 + + function to_str(f : frac) : string = switch(f) + Pos(n, d) => String.concat(Int.to_str(n), if (d == 1) "" else String.concat("/", Int.to_str(d))) + Neg(n, d) => String.concat("-", to_str(Pos(n, d))) + Zero => "0" + +/** Reduce fraction to normal form + */ + function simplify(f : frac) : frac = + switch(f) + Neg(n, d) => + let cd = gcd(n, d) + Neg(n / cd, d / cd) + Zero => Zero + Pos(n, d) => + let cd = gcd(n, d) + Pos(n / cd, d / cd) + +/** Integer to rational division + */ + function make_frac(n : int, d : int) : frac = + if (d == 0) abort("Zero denominator") + elif (n == 0) Zero + elif ((n < 0) == (d < 0)) simplify(Pos(abs_int(n), abs_int(d))) + else simplify(Neg(abs_int(n), abs_int(d))) + + function one() : frac = Pos(1, 1) + function zero() : frac = Zero + + function eq(a : frac, b : frac) : bool = + let (na, da) = to_pair(a) + let (nb, db) = to_pair(b) + (na == nb && da == db) || na * db == nb * da // they are more likely to be normalized + + function neq(a : frac, b : frac) : bool = + let (na, da) = to_pair(a) + let (nb, db) = to_pair(b) + (na != nb || da != db) && na * db != nb * da + + function geq(a : frac, b : frac) : bool = num(a) * den(b) >= num(b) * den(a) + + function leq(a : frac, b : frac) : bool = num(a) * den(b) =< num(b) * den(a) + + function gt(a : frac, b : frac) : bool = num(a) * den(b) > num(b) * den(a) + + function lt(a : frac, b : frac) : bool = num(a) * den(b) < num(b) * den(a) + + function min(a : frac, b : frac) : frac = if (leq(a, b)) a else b + + function max(a : frac, b : frac) : frac = if (geq(a, b)) a else b + + function abs(f : frac) : frac = switch(f) + Pos(n, d) => Pos(n, d) + Zero => Zero + Neg(n, d) => Pos(n, d) + + function from_int(n : int) : frac = + if (n > 0) Pos(n, 1) + elif (n < 0) Neg(-n, 1) + else Zero + + function floor(f : frac) : int = switch(f) + Pos(n, d) => n / d + Zero => 0 + Neg(n, d) => -(n + d - 1) / d + + function ceil(f : frac) : int = switch(f) + Pos(n, d) => (n + d - 1) / d + Zero => 0 + Neg(n, d) => -n / d + + function round_to_zero(f : frac) : int = switch(f) + Pos(n, d) => n / d + Zero => 0 + Neg(n, d) => -n / d + + function round_from_zero(f : frac) : int = switch(f) + Pos(n, d) => (n + d - 1) / d + Zero => 0 + Neg(n, d) => -(n + d - 1) / d + +/** Round towards nearest integer. If two integers are in the same + * distance, choose the even one. + */ + function round(f : frac) : int = + let fl = floor(f) + let cl = ceil(f) + let dif_fl = abs(sub(f, from_int(fl))) + let dif_cl = abs(sub(f, from_int(cl))) + if (gt(dif_fl, dif_cl)) cl + elif (gt(dif_cl, dif_fl)) fl + elif (fl mod 2 == 0) fl + else cl + + function add(a : frac, b : frac) : frac = + let (na, da) = to_pair(a) + let (nb, db) = to_pair(b) + if (da == db) make_frac(na + nb, da) + else make_frac(na * db + nb * da, da * db) + + function neg(a : frac) : frac = switch(a) + Neg(n, d) => Pos(n, d) + Zero => Zero + Pos(n, d) => Neg(n, d) + + function sub(a : frac, b : frac) : frac = add(a, neg(b)) + + function inv(a : frac) : frac = switch(a) + Neg(n, d) => Neg(d, n) + Zero => abort("Inversion of zero") + Pos(n, d) => Pos(d, n) + + function mul(a : frac, b : frac) : frac = make_frac(num(a) * num(b), den(a) * den(b)) + + function div(a : frac, b : frac) : frac = switch(b) + Neg(n, d) => mul(a, Neg(d, n)) + Zero => abort("Division by zero") + Pos(n, d) => mul(a, Pos(d, n)) + +/** `b` to the power of `e` + */ + function int_exp(b : frac, e : int) : frac = + if (sign(b) == 0 && e == 0) abort("Zero to the zero exponentation") + elif (e < 0) inv(int_exp_(b, -e)) + else int_exp_(b, e) + private function int_exp_(b : frac, e : int) = + if (e == 0) from_int(1) + elif (e == 1) b + else + let half = int_exp_(b, e / 2) + if (e mod 2 == 1) mul(mul(half, half), b) + else mul(half, half) + +/** Reduces the fraction's in-memory size by dividing its components by two until the + * the error is bigger than `loss` value + */ + function optimize(f : frac, loss : frac) : frac = + require(geq(loss, Zero), "negative loss optimize") + let s = sign(f) + mul(from_int(s), run_optimize(abs(f), abs(f), loss)) + private function run_optimize(orig : frac, f : frac, loss : frac) : frac = + let (n, d) = to_pair(f) + let t = make_frac((n+1)/2, (d+1)/2) + if(gt(abs(sub(t, orig)), loss)) f + elif (eq(t, f)) f + else run_optimize(orig, t, loss) diff --git a/priv/stdlib/Func.aes b/priv/stdlib/Func.aes index 7d633a2..42cef77 100644 --- a/priv/stdlib/Func.aes +++ b/priv/stdlib/Func.aes @@ -12,35 +12,66 @@ namespace Func = function rapply(x : 'a, f : 'a => 'b) : 'b = f(x) - /* The Z combinator - replacement for local and anonymous recursion. - */ +/** The Z combinator - replacement for local and anonymous recursion. + */ function recur(f : ('arg => 'res, 'arg) => 'res) : 'arg => 'res = (x) => f(recur(f), x) +/** n-times composition with itself + */ function iter(n : int, f : 'a => 'a) : 'a => 'a = iter_(n, f, (x) => x) private function iter_(n : int, f : 'a => 'a, acc : 'a => 'a) : 'a => 'a = if(n == 0) acc elif(n == 1) comp(f, acc) else iter_(n / 2, comp(f, f), if(n mod 2 == 0) acc else comp(f, acc)) - function curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c) = +/** Turns an ugly, bad and disgusting arity-n function into + * a beautiful and sweet function taking the first argument + * and returning a function watiting for the remaining ones + * in the same manner + */ + function curry2(f : ('a, 'b) => 'x) : 'a => ('b => 'x) = (x) => (y) => f(x, y) - function curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd)) = + function curry3(f : ('a, 'b, 'c) => 'x) : 'a => ('b => ('c => 'x)) = (x) => (y) => (z) => f(x, y, z) + function curry4(f : ('a, 'b, 'c, 'd) => 'x) : 'a => ('b => ('c => ('d => 'x))) = + (x) => (y) => (z) => (w) => f(x, y, z, w) + function curry5(f : ('a, 'b, 'c, 'd, 'e) => 'x) : 'a => ('b => ('c => ('d => ('e => 'x)))) = + (x) => (y) => (z) => (w) => (q) => f(x, y, z, w, q) - function uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c = +/** Opposite of curry. Gross + */ + function uncurry2(f : 'a => ('b => 'x)) : ('a, 'b) => 'x = (x, y) => f(x)(y) - function uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd = + function uncurry3(f : 'a => ('b => ('c => 'x))) : ('a, 'b, 'c) => 'x = (x, y, z) => f(x)(y)(z) + function uncurry4(f : 'a => ('b => ('c => ('d => 'x)))) : ('a, 'b, 'c, 'd) => 'x = + (x, y, z, w) => f(x)(y)(z)(w) + function uncurry5(f : 'a => ('b => ('c => ('d => ('e => 'x))))) : ('a, 'b, 'c, 'd, 'e) => 'x = + (x, y, z, w, q) => f(x)(y)(z)(w)(q) - function tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c = +/** Turns an arity-n function into a function taking n-tuple + */ + function tuplify2(f : ('a, 'b) => 'x) : (('a * 'b)) => 'x = (t) => switch(t) (x, y) => f(x, y) - function tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd = + function tuplify3(f : ('a, 'b, 'c) => 'x) : 'a * 'b * 'c => 'x = (t) => switch(t) (x, y, z) => f(x, y, z) + function tuplify4(f : ('a, 'b, 'c, 'd) => 'x) : 'a * 'b * 'c * 'd => 'x = + (t) => switch(t) + (x, y, z, w) => f(x, y, z, w) + function tuplify5(f : ('a, 'b, 'c, 'd, 'e) => 'x) : 'a * 'b * 'c * 'd * 'e => 'x = + (t) => switch(t) + (x, y, z, w, q) => f(x, y, z, w, q) - function untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c = +/** Opposite of tuplify + */ + function untuplify2(f : 'a * 'b => 'x) : ('a, 'b) => 'x = (x, y) => f((x, y)) - function untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd = + function untuplify3(f : 'a * 'b * 'c => 'x) : ('a, 'b, 'c) => 'x = (x, y, z) => f((x, y, z)) + function untuplify4(f : 'a * 'b * 'c * 'd => 'x) : ('a, 'b, 'c, 'd) => 'x = + (x, y, z, w) => f((x, y, z, w)) + function untuplify5(f : 'a * 'b * 'c * 'd * 'e => 'x) : ('a, 'b, 'c, 'd, 'e) => 'x = + (x, y, z, w, q) => f((x, y, z, w, q)) diff --git a/priv/stdlib/List.aes b/priv/stdlib/List.aes index 98fd4fe..c66a591 100644 --- a/priv/stdlib/List.aes +++ b/priv/stdlib/List.aes @@ -28,10 +28,15 @@ namespace List = h::t => h::drop_last_unsafe(t) [] => abort("drop_last_unsafe: list empty") +/** Finds first element of `l` fulfilling predicate `p` as `Some` or `None` + * if no such element exists. + */ function find(p : 'a => bool, l : list('a)) : option('a) = switch(l) [] => None h::t => if(p(h)) Some(h) else find(p, t) +/** Returns list of all indices of elements from `l` that fulfill the predicate `p`. + */ function find_indices(p : 'a => bool, l : list('a)) : list(int) = find_indices_(p, l, 0) private function find_indices_( p : 'a => bool , l : list('a) @@ -60,8 +65,15 @@ namespace List = _::t => length_(t, acc + 1) +/** Creates an ascending sequence of all integer numbers + * between `a` and `b` (including `a` and `b`) + */ function from_to(a : int, b : int) : list(int) = [a..b] +/** 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. + */ function from_to_step(a : int, b : int, s : int) : list(int) = from_to_step_(a, b - (b-a) mod s, s, []) private function from_to_step_(a : int, b : int, s : int, acc : list(int)) : list(int) = @@ -69,8 +81,8 @@ namespace List = else from_to_step_(a, b - s, s, b::acc) - - /* Unsafe. Replaces `n`th element of `l` with `e`. Crashes on over/underflow */ +/** Unsafe. Replaces `n`th element of `l` with `e`. Crashes on over/underflow + */ function replace_at(n : int, e : 'a, l : list('a)) : list('a) = if(n<0) abort("insert_at underflow") else replace_at_(n, e, l) private function replace_at_(n : int, e : 'a, l : list('a)) : list('a) = @@ -79,7 +91,8 @@ namespace List = h::t => if (n == 0) e::t else h::replace_at_(n-1, e, t) - /* Unsafe. Adds `e` to `l` to be its `n`th element. Crashes on over/underflow */ +/** Unsafe. Adds `e` to `l` to be its `n`th element. Crashes on over/underflow + */ function insert_at(n : int, e : 'a, l : list('a)) : list('a) = if(n<0) abort("insert_at underflow") else insert_at_(n, e, l) private function insert_at_(n : int, e : 'a, l : list('a)) : list('a) = @@ -88,6 +101,9 @@ namespace List = [] => abort("insert_at overflow") h::t => h::insert_at_(n-1, e, t) +/** Assuming that cmp represents `<` comparison, inserts `x` before + * the first element in the list `l` which is greater than it + */ function insert_by(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) = switch(l) [] => [x] @@ -122,6 +138,8 @@ namespace List = [] => [] h::t => f(h)::map(f, t) +/** Effectively composition of `map` and `flatten` + */ function flat_map(f : 'a => list('b), l : list('a)) : list('b) = ListInternal.flat_map(f, l) @@ -131,7 +149,8 @@ namespace List = let rest = filter(p, t) if(p(h)) h::rest else rest - /* Take up to `n` first elements */ +/** Take up to `n` first elements + */ function take(n : int, l : list('a)) : list('a) = if(n < 0) abort("Take negative number of elements") else take_(n, l) private function take_(n : int, l : list('a)) : list('a) = @@ -140,7 +159,8 @@ namespace List = [] => [] h::t => h::take_(n-1, t) - /* Drop up to `n` first elements */ +/** Drop up to `n` first elements + */ function drop(n : int, l : list('a)) : list('a) = if(n < 0) abort("Drop negative number of elements") else drop_(n, l) private function drop_(n : int, l : list('a)) : list('a) = @@ -149,17 +169,22 @@ namespace List = [] => [] h::t => drop_(n-1, t) - /* Get the longest prefix of a list in which every element matches predicate `p` */ +/** Get the longest prefix of a list in which every element + * matches predicate `p` + */ function take_while(p : 'a => bool, l : list('a)) : list('a) = switch(l) [] => [] h::t => if(p(h)) h::take_while(p, t) else [] - /* Drop elements from `l` until `p` holds */ +/** Drop elements from `l` until `p` holds + */ function drop_while(p : 'a => bool, l : list('a)) : list('a) = switch(l) [] => [] h::t => if(p(h)) drop_while(p, t) else l - /* Splits list into two lists of elements that respectively match and don't match predicate `p` */ +/** Splits list into two lists of elements that respectively + * match and don't match predicate `p` + */ function partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) = switch(l) [] => ([], []) h::t => @@ -183,7 +208,9 @@ namespace List = function product(l : list(int)) : int = foldl((a, b) => a * b, 1, l) - /* Zips two list by applying bimapping function on respective elements. Drops longer tail. */ +/** Zips two list by applying bimapping function on respective elements. + * Drops the tail of the longer list. + */ private function zip_with( f : ('a, 'b) => 'c , l1 : list('a) , l2 : list('b) @@ -191,7 +218,9 @@ namespace List = (h1::t1, h2::t2) => f(h1, h2)::zip_with(f, t1, t2) _ => [] - /* Zips two lists into list of pairs. Drops longer tail. */ +/** Zips two lists into list of pairs. + * Drops the tail of the longer list. + */ function zip(l1 : list('a), l2 : list('b)) : list('a * 'b) = zip_with((a, b) => (a, b), l1, l2) function unzip(l : list('a * 'b)) : (list('a) * list('b)) = switch(l) @@ -207,15 +236,16 @@ namespace List = h::t => switch (partition((x) => lesser_cmp(x, h), t)) (lesser, bigger) => sort(lesser_cmp, lesser) ++ h::sort(lesser_cmp, bigger) - +/** Puts `delim` between every two members of the list + */ function intersperse(delim : 'a, l : list('a)) : list('a) = switch(l) [] => [] [e] => [e] h::t => h::delim::intersperse(delim, t) - +/** Effectively a zip with an infinite sequence of natural numbers + */ function enumerate(l : list('a)) : list(int * 'a) = enumerate_(l, 0) private function enumerate_(l : list('a), n : int) : list(int * 'a) = switch(l) [] => [] - h::t => (n, h)::enumerate_(t, n + 1) - + h::t => (n, h)::enumerate_(t, n + 1) \ No newline at end of file diff --git a/priv/stdlib/Option.aes b/priv/stdlib/Option.aes index 9647230..b163e8a 100644 --- a/priv/stdlib/Option.aes +++ b/priv/stdlib/Option.aes @@ -10,13 +10,18 @@ namespace Option = None => false Some(_) => true - +/** Catamorphism on `option`. Also known as inlined pattern matching. + */ function match(n : 'b, s : 'a => 'b, o : option('a)) : 'b = switch(o) None => n Some(x) => s(x) +/** Escape option providing default if `None` + */ function default(def : 'a, o : option('a)) : 'a = match(def, (x) => x, o) +/** Assume it is `Some` + */ function force(o : option('a)) : 'a = default(abort("Forced None value"), o) function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o) @@ -40,10 +45,14 @@ namespace Option = (Some(x1), Some(x2), Some(x3)) => Some(f(x1, x2, x3)) _ => None +/** Like `map`, but the function is in `option` + */ function app_over(f : option ('a => 'b), o : option('a)) : option('b) = switch((f, o)) (Some(ff), Some(xx)) => Some(ff(xx)) _ => None +/** Monadic bind + */ function flat_map(f : 'a => option('b), o : option('a)) : option('b) = switch(o) None => None Some(x) => f(x) @@ -53,11 +62,17 @@ namespace Option = None => [] Some(x) => [x] +/** Turns list of options into a list of elements that are under `Some`s. + * Safe. + */ function filter_options(l : list(option('a))) : list('a) = switch(l) [] => [] None::t => filter_options(t) Some(x)::t => x::filter_options(t) +/** Just like `filter_options` but requires all elements to be `Some` and returns + * None if any of them is not + */ function seq_options(l : list (option('a))) : option (list('a)) = switch(l) [] => Some([]) None::_ => None @@ -66,11 +81,14 @@ namespace Option = Some(st) => Some(x::st) +/** Choose `Some` out of two if possible + */ function choose(o1 : option('a), o2 : option('a)) : option('a) = if(is_some(o1)) o1 else o2 +/** Choose `Some` from list of options if possible + */ function choose_first(l : list(option('a))) : option('a) = switch(l) [] => None None::t => choose_first(t) Some(x)::_ => Some(x) - diff --git a/priv/stdlib/Pair.aes b/priv/stdlib/Pair.aes index 22312e3..16ec30f 100644 --- a/priv/stdlib/Pair.aes +++ b/priv/stdlib/Pair.aes @@ -6,12 +6,18 @@ namespace Pair = function snd(t : ('a * 'b)) : 'b = switch(t) (_, y) => y +/** Map over first + */ function map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) = switch(t) (x, y) => (f(x), y) +/** Map over second + */ function map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) = switch(t) (x, y) => (x, f(y)) +/** Map over both + */ function bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd) = switch(t) (x, y) => (f(x), g(y)) diff --git a/priv/stdlib/Triple.aes b/priv/stdlib/Triple.aes index 84f3ddd..07a716d 100644 --- a/priv/stdlib/Triple.aes +++ b/priv/stdlib/Triple.aes @@ -10,15 +10,23 @@ namespace Triple = (_, _, z) => z +/** Map over first + */ function map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) = switch(t) (x, y, z) => (f(x), y, z) +/** Map over second + */ function map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) = switch(t) (x, y, z) => (x, f(y), z) +/** Map over third + */ function map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) = switch(t) (x, y, z) => (x, y, f(z)) +/** Map over all elements + */ function trimap( f : 'a => 'x , g : 'b => 'y , h : 'c => 'z @@ -29,9 +37,13 @@ namespace Triple = function swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a) = switch(t) (x, y, z) => (z, y, x) +/** Right rotation + */ function rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) = switch(t) (x, y, z) => (z, x, y) +/** Left rotation + */ function rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a) = switch(t) (x, y, z) => (y, z, x) diff --git a/rebar.config b/rebar.config index a41b9ef..4bc4501 100644 --- a/rebar.config +++ b/rebar.config @@ -15,7 +15,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "4.2.0"}, +{relx, [{release, {aesophia, "4.3.0"}, [aesophia, aebytecode, getopt]}, {dev_mode, true}, diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 274be69..26372a8 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -194,6 +194,8 @@ encode_expr({bytes, _, B}) -> encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id; Lit == contract_pubkey; Lit == account_pubkey -> aeser_api_encoder:encode(Lit, L); +encode_expr({app, _, {'-', _}, [{int, _, N}]}) -> + encode_expr({int, [], -N}); encode_expr({app, _, F, As}) -> Ef = encode_expr(F), Eas = encode_exprs(As), diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 62e6c7a..b7f54ba 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -12,7 +12,11 @@ -module(aeso_ast_infer_types). --export([infer/1, infer/2, unfold_types_in_type/3]). +-export([ infer/1 + , infer/2 + , unfold_types_in_type/3 + , pp_type/2 + ]). -type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()} | {app_t, aeso_syntax:ann(), utype(), [utype()]} @@ -2253,7 +2257,8 @@ unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) -> unify1(Env, {if_t, _, {id, _, Id}, Then1, Else1}, {if_t, _, {id, _, Id}, Then2, Else2}, When) -> unify(Env, Then1, Then2, When) andalso unify(Env, Else1, Else2, When); -unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) -> +unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) + when length(Args1) == length(Args2) -> unify(Env, Named1, Named2, When) andalso unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When); unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When) @@ -2444,6 +2449,24 @@ mk_t_err(Pos, Msg) -> mk_t_err(Pos, Msg, Ctxt) -> aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)). +mk_error({higher_kinded_typevar, T}) -> + Msg = io_lib:format("Type ~s is a higher kinded type variable\n" + "(takes another type as an argument)\n", [pp(instantiate(T))] + ), + mk_t_err(pos(T), Msg); +mk_error({wrong_type_arguments, X, ArityGiven, ArityReal}) -> + Msg = io_lib:format("Arity for ~s doesn't match. Expected ~p, got ~p\n" + , [pp(instantiate(X)), ArityReal, ArityGiven] + ), + mk_t_err(pos(X), Msg); +mk_error({unnamed_map_update_with_default, Upd}) -> + Msg = "Invalid map update with default\n", + mk_t_err(pos(Upd), Msg); +mk_error({fundecl_must_have_funtype, _Ann, Id, Type}) -> + Msg = io_lib:format("~s at ~s was declared with an invalid type ~s.\n" + "Entrypoints and functions must have functional types" + , [pp(Id), pp_loc(Id), pp(instantiate(Type))]), + mk_t_err(pos(Id), Msg); mk_error({cannot_unify, A, B, When}) -> Msg = io_lib:format("Cannot unify ~s\n and ~s\n", [pp(instantiate(A)), pp(instantiate(B))]), @@ -2526,14 +2549,6 @@ mk_error({indexed_type_must_be_word, Type, Type1}) -> Msg = io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n", [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]), mk_t_err(pos(Type), Msg); -mk_error({payload_type_must_be_string, Type, Type}) -> - Msg = io_lib:format("The payload type ~s (at ~s) should be string\n", - [pp_type("", Type), pp_loc(Type)]), - mk_t_err(pos(Type), Msg); -mk_error({payload_type_must_be_string, Type, Type1}) -> - Msg = io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n", - [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]), - mk_t_err(pos(Type), Msg); mk_error({event_0_to_3_indexed_values, Constr}) -> Msg = io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n", [name(Constr), pp_loc(Constr)]), @@ -2577,13 +2592,21 @@ mk_error({include, _, {string, Pos, Name}}) -> [binary_to_list(Name), pp_loc(Pos)]), mk_t_err(pos(Pos), Msg); mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) -> - Msg = io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n", + Msg = io_lib:format("Nested namespaces are not allowed\nNamespace '~s' at ~s not defined at top level.\n", + [Name, pp_loc(Pos)]), + mk_t_err(pos(Pos), Msg); +mk_error({contract, _Pos, {con, Pos, Name}, _Def}) -> + Msg = io_lib:format("Nested contracts are not allowed\nContract '~s' at ~s not defined at top level.\n", + [Name, pp_loc(Pos)]), + mk_t_err(pos(Pos), Msg); +mk_error({type_decl, _, {id, Pos, Name}, _}) -> + Msg = io_lib:format("Empty type declarations are not supported\nType ~s at ~s lacks a definition\n", + [Name, pp_loc(Pos)]), + mk_t_err(pos(Pos), Msg); +mk_error({letval, _Pos, {id, Pos, Name}, _Def}) -> + Msg = io_lib:format("Toplevel \"let\" definitions are not supported\nValue ~s at ~s could be replaced by 0-argument function\n", [Name, pp_loc(Pos)]), mk_t_err(pos(Pos), Msg); -mk_error({repeated_arg, Fun, Arg}) -> - Msg = io_lib:format("Repeated argument ~s to function ~s (at ~s).\n", - [Arg, pp(Fun), pp_loc(Fun)]), - mk_t_err(pos(Fun), Msg); mk_error({stateful_not_allowed, Id, Fun}) -> Msg = io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n", [pp(Id), pp_loc(Id), pp(Fun)]), diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 2736cd4..07f4e01 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -347,7 +347,6 @@ decls_to_fcode(Env, Decls) -> end, Env1, Decls). -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). -decl_to_fcode(Env, {type_decl, _, _, _}) -> Env; decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) -> case is_no_code(Env) of false -> fcode_error({missing_definition, Id}); diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index c677119..ad5288b 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -38,6 +38,7 @@ | pp_assembler | pp_bytecode | no_code + | keep_included | {backend, aevm | fate} | {include, {file_system, [string()]} | {explicit_files, #{string() => binary()}}} @@ -336,12 +337,12 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) -> try {ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))} catch throw:cannot_translate_to_sophia -> - Type1 = prettypr:format(aeso_pretty:type(Type)), + Type1 = prettypr:format(aeso_pretty:type(Type0)), Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n", [aeb_fate_encoding:deserialize(Data), Type1]), {error, [aeso_errors:new(data_error, Msg)]}; _:_ -> - Type1 = prettypr:format(aeso_pretty:type(Type)), + Type1 = prettypr:format(aeso_pretty:type(Type0)), Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]), {error, [aeso_errors:new(data_error, Msg)]} end @@ -650,8 +651,9 @@ pp_fate_type(T) -> io_lib:format("~w", [T]). %% ------------------------------------------------------------------- +-spec sophia_type_to_typerep(string()) -> {error, bad_type} | {ok, aeb_aevm_data:type()}. sophia_type_to_typerep(String) -> - {ok, Ast} = aeso_parser:type(String), + Ast = aeso_parser:run_parser(aeso_parser:type(), String), try aeso_ast_to_icode:ast_typerep(Ast) of Type -> {ok, Type} catch _:_ -> {error, bad_type} diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 74a329c..acb09c6 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -3,20 +3,33 @@ %%% Description : %%% Created : 1 Mar 2018 by Ulf Norell -module(aeso_parser). +-compile({no_auto_import,[map_get/2]}). -export([string/1, string/2, string/3, + auto_imports/1, hash_include/2, - type/1]). + decl/0, + type/0, + body/0, + maybe_block/1, + run_parser/2, + run_parser/3]). -include("aeso_parse_lib.hrl"). -import(aeso_parse_lib, [current_file/0, set_current_file/1]). --type parse_result() :: aeso_syntax:ast() | none(). +-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none(). -type include_hash() :: {string(), binary()}. + +escape_errors({ok, Ok}) -> + Ok; +escape_errors({error, Err}) -> + parse_error(Err). + -spec string(string()) -> parse_result(). string(String) -> string(String, sets:new(), []). @@ -30,21 +43,17 @@ string(String, Opts) -> -spec string(string(), sets:set(include_hash()), aeso_compiler:options()) -> parse_result(). string(String, Included, Opts) -> - case parse_and_scan(file(), String, Opts) of - {ok, AST} -> - case expand_includes(AST, Included, Opts) of - {ok, AST1} -> AST1; - {error, Err} -> parse_error(Err) - end; - {error, Err} -> - parse_error(Err) + AST = run_parser(file(), String, Opts), + case expand_includes(AST, Included, Opts) of + {ok, AST1} -> AST1; + {error, Err} -> parse_error(Err) end. -type(String) -> - case parse_and_scan(type(), String, []) of - {ok, AST} -> {ok, AST}; - {error, Err} -> {error, [mk_error(Err)]} - end. + +run_parser(P, Inp) -> + escape_errors(parse_and_scan(P, Inp, [])). +run_parser(P, Inp, Opts) -> + escape_errors(parse_and_scan(P, Inp, Opts)). parse_and_scan(P, S, Opts) -> set_current_file(proplists:get_value(src_file, Opts, no_file)), @@ -102,7 +111,7 @@ decl() -> %% Function declarations , ?RULE(modifiers(), fun_or_entry(), maybe_block(fundef_or_decl()), fun_block(_1, _2, _3)) - , ?RULE(keyword('let'), valdef(),set_pos(get_pos(_1), _2)) + , ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) ])). fun_block(Mods, Kind, [Decl]) -> @@ -596,8 +605,13 @@ expand_includes(AST, Included, Opts) -> || File <- lists:usort(auto_imports(AST)) ] ++ AST, expand_includes(AST1, Included, [], Opts). -expand_includes([], _Included, Acc, _Opts) -> - {ok, lists:reverse(Acc)}; +expand_includes([], Included, Acc, Opts) -> + case lists:member(keep_included, Opts) of + false -> + {ok, lists:reverse(Acc)}; + true -> + {ok, {lists:reverse(Acc), Included}} + end; expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) -> case get_include_code(File, Ann, Opts) of {ok, Code} -> @@ -656,8 +670,14 @@ stdlib_options() -> get_include_code(File, Ann, Opts) -> case {read_file(File, Opts), read_file(File, stdlib_options())} of - {{ok, _}, {ok,_ }} -> - fail(ann_pos(Ann), "Illegal redefinition of standard library " ++ File); + {{ok, Bin}, {ok, _}} -> + case filename:basename(File) == File of + true -> { error + , fail( ann_pos(Ann) + , "Illegal redefinition of standard library " ++ binary_to_list(File))}; + %% If a path is provided then the stdlib takes lower priority + false -> {ok, binary_to_list(Bin)} + end; {_, {ok, Bin}} -> {ok, binary_to_list(Bin)}; {{ok, Bin}, _} -> diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 39dc19e..dfbd577 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -145,8 +145,12 @@ decl(D, Options) -> with_options(Options, fun() -> decl(D) end). -spec decl(aeso_syntax:decl()) -> doc(). -decl({contract, _, C, Ds}) -> - block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds)); +decl({contract, Attrs, C, Ds}) -> + Mod = fun({Mod, true}) when Mod == payable -> + text(atom_to_list(Mod)); + (_) -> empty() end, + block(follow( hsep(lists:map(Mod, Attrs) ++ [text("contract")]) + , hsep(name(C), text("="))), decls(Ds)); decl({namespace, _, C, Ds}) -> block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds)); decl({pragma, _, Pragma}) -> pragma(Pragma); @@ -155,13 +159,16 @@ decl({type_def, _, T, Vars, Def}) -> Kind = element(1, Def), equals(typedecl(Kind, T, Vars), typedef(Def)); decl({fun_decl, Ann, F, T}) -> + Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable -> + text(atom_to_list(Mod)); + (_) -> empty() end, Fun = case aeso_syntax:get_ann(entrypoint, Ann, false) of true -> text("entrypoint"); false -> text("function") end, - hsep(Fun, typed(name(F), T)); + hsep(lists:map(Mod, Ann) ++ [Fun, typed(name(F), T)]); decl(D = {letfun, Attrs, _, _, _, _}) -> - Mod = fun({Mod, true}) when Mod == private; Mod == stateful -> + Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable -> text(atom_to_list(Mod)); (_) -> empty() end, Fun = case aeso_syntax:get_ann(entrypoint, Attrs, false) of @@ -363,7 +370,8 @@ expr_p(_, {Type, _, Bin}) Type == oracle_query_id -> text(binary_to_list(aeser_api_encoder:encode(Type, Bin))); expr_p(_, {string, _, <<>>}) -> text("\"\""); -expr_p(_, {string, _, S}) -> term(binary_to_list(S)); +expr_p(_, {string, _, S}) -> + text(io_lib:format("\"~s\"", [binary_to_list(S)])); expr_p(_, {char, _, C}) -> case C of $' -> text("'\\''"); @@ -490,6 +498,3 @@ get_elifs(If = {'if', Ann, Cond, Then, Else}, Elifs) -> end; get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {else, Else}}. -fmt(Fmt, Args) -> text(lists:flatten(io_lib:format(Fmt, Args))). -term(X) -> fmt("~p", [X]). - diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 61011da..30d4d67 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -37,20 +37,24 @@ -type decl() :: {contract, ann(), con(), [decl()]} | {namespace, ann(), con(), [decl()]} | {pragma, ann(), pragma()} - | {type_decl, ann(), id(), [tvar()]} + | {type_decl, ann(), id(), [tvar()]} % Only for error msgs | {type_def, ann(), id(), [tvar()], typedef()} | {fun_decl, ann(), id(), type()} | {fun_clauses, ann(), id(), type(), [letbind()]} | {block, ann(), [decl()]} - | letbind(). + | letfun() + | letval(). % Only for error msgs -type compiler_version() :: [non_neg_integer()]. -type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}. + +-type letval() :: {letval, ann(), pat(), expr()}. +-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}. -type letbind() - :: {letval, ann(), pat(), expr()} - | {letfun, ann(), id(), [pat()], type(), expr()}. + :: letfun() + | letval(). -type arg() :: {arg, ann(), id(), type()}. diff --git a/src/aeso_syntax_utils.erl b/src/aeso_syntax_utils.erl index 71c7c90..deef257 100644 --- a/src/aeso_syntax_utils.erl +++ b/src/aeso_syntax_utils.erl @@ -45,7 +45,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> %% decl() {contract, _, _, Ds} -> Decl(Ds); {namespace, _, _, Ds} -> Decl(Ds); - {type_decl, _, I, _} -> BindType(I); {type_def, _, I, _, D} -> Plus(BindType(I), Decl(D)); {fun_decl, _, _, T} -> Type(T); {letval, _, P, E} -> Scoped(BindExpr(P), Expr(E)); diff --git a/src/aesophia.app.src b/src/aesophia.app.src index d224976..e5744a0 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,6 +1,6 @@ {application, aesophia, [{description, "Contract Language for aeternity"}, - {vsn, "4.2.0"}, + {vsn, "4.3.0"}, {registered, []}, {applications, [kernel, diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 77fdd52..fae8ea7 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -381,11 +381,15 @@ failing_contracts() -> " r.foo() : map(int, string)\n" "against the expected type\n" " map(string, int)">>]) - , ?TYPE_ERROR(bad_include_and_ns, - [<>, - <>]) + , ?TYPE_ERROR(not_toplevel_include, + [<>]) + , ?TYPE_ERROR(not_toplevel_namespace, + [<>]) + , ?TYPE_ERROR(not_toplevel_contract, + [<>]) , ?TYPE_ERROR(bad_address_literals, [< [<>]) + , ?TYPE_ERROR(toplevel_let, + [<>]) + , ?TYPE_ERROR(empty_typedecl, + [<>]) + , ?TYPE_ERROR(higher_kinded_type, + [<>]) + , ?TYPE_ERROR(bad_arity, + [<>, + <>, + <>, + <>]) + , ?TYPE_ERROR(bad_unnamed_map_update_default, + [<>]) + , ?TYPE_ERROR(non_functional_entrypoint, + [<>]) , ?TYPE_ERROR(bad_records, [< " (0 : int) == (1 : int) : bool\n" "It must be either 'true' or 'false'.">> ]) + , ?TYPE_ERROR(bad_number_of_args, + [< unit\n" + " and (int) => 'a\n", + "when checking the application at line 3, column 39 of\n" + " f : () => unit\n" + "to arguments\n" + " 1 : int">>, + < 'e\n" + " and (int) => 'd\n" + "when checking the application at line 4, column 20 of\n" + " g : (int, string) => 'e\n" + "to arguments\n" + " 1 : int">>, + < 'c\n" + " and (string) => 'b\n" + "when checking the application at line 5, column 20 of\n" + " g : (int, string) => 'c\nto arguments\n" + " \"Litwo, ojczyzno moja\" : string">> + ]) ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/05_greeter.aes b/test/contracts/05_greeter.aes index 04ee5fd..1b83716 100644 --- a/test/contracts/05_greeter.aes +++ b/test/contracts/05_greeter.aes @@ -58,8 +58,7 @@ contract Greeter = let state = { greeting = "Hello" } - let setGreeting = - (greeting: string) => + function setGreeting(greeting: string) = state{ greeting = greeting } diff --git a/test/contracts/all_syntax.aes b/test/contracts/all_syntax.aes index a88bae8..ced918a 100644 --- a/test/contracts/all_syntax.aes +++ b/test/contracts/all_syntax.aes @@ -1,44 +1,82 @@ // Try to cover all syntactic constructs. +@compiler > 0 +@compiler =< 10.1.1.1.1.1.2.3.4 -contract AllSyntaxType = - type typeDecl /* bla */ - type paramTypeDecl('a, 'b) +namespace Ns = + datatype d('a) = D | S(int) | M('a, list('a), int) + private function fff() = 123 + + stateful entrypoint + f (1, x) = (_) => x + +payable contract AllSyntaxType = /** Multi- * line * comment */ - function foo : _ + stateful function foo : _ + entrypoint bar : int => (int * 'a) + contract AllSyntax = - type typeDecl = int - type paramTypeDecl('a, 'b) = (('a, 'b) => 'b) => list('a) => 'b => 'b + datatype mickiewicz = Adam | Mickiewicz + record goethe('a, 'b) = { + johann : int, + wolfgang : 'a, + von : 'a * 'b * int, + goethe : unit + } + type dante = Ns.d(int) + type shakespeare('a) = goethe('a, 'a) - record nestedRecord = { x : int } - record recordType = { z : nestedRecord, y : int } - datatype variantType('a) = None | Some('a) + type state = shakespeare(int) - let valWithType : map(int, int) => option(int) = (m) => Map.get(m, 42) - let valNoType = - if(valWithType(Map.empty) == None) - print(42 mod 10 * 5 / 3) + entrypoint init() = { + johann = 1000, + wolfgang = -10, - function funWithType(x : int, y) : int * list(int) = (x, 0 :: [y] ++ []) - function funNoType() = - let foo = (x, y : bool) => - if (! (y && x =< 0x0b || true)) [x] - else [11..20] - let setY(r : recordType) : unit = r{ y = 5 } - let setX(r : recordType, x : int) : recordType = r { z.x = x } // nested record update - let getY(r) = switch(r) {y = y} => y - switch (funWithType(1, -2)) - (x, [y, z]) => bar({x = z, y = -y + - -z * (-1)}) - (x, y :: _) => () +/* TODO: This does not compile because of bug in the parser tester. + von = (2 + 2, 0, List.sum([x | k <- [1,2,3] + , let l = k + 1 + , if(l < 10) + , let f(x) = x + 100 + , Adam <- [Adam, Mickiewicz] + , let x = f(l) + ])), +*/ + von = (2 + 2, 0, List.sum([1,2,3,4])), + goethe = () } - let hash : address = #01ab0fff11 - let b = false - let qcon = Mod.Con - let str = "blabla\nfoo" - let chr = '"' + function f() = + let kp = "nietzsche" + // let p = "Пушкин" // TODO: this also doesn't do right round_trip... + let k(x : bytes(8)) : bytes(8) = Bytes.to_int(#fedcba9876543210) + let f : () => address = () => ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt + if(Bits.test(Bits.all, 10)) + abort("ohno") + if(true && false) + require(true, "ohyes") + elif(false || 2 == 2) + () + else + () + if(true) f(1,2)((1,2)) + else switch(1::[1,2,3]) + [] => 1 + a::b => 123 + 1::2::3 => 123123 + [2,3,4] => 1 + _ => 13 + 1::[2] => 2138 + put(state{johann = 1}) + + let m = {["foo"] = 19, /*hey wanna talk about inlined comments?*/ ["bar"] = 42} + let n = {} + m{ ["x" = 0] @ z = z + state.johann } + + let sh : shakespeare(shakespeare(int)) = + {wolfgang = state} + sh{wolfgang.wolfgang = sh.wolfgang} // comment diff --git a/test/contracts/bad_arity.aes b/test/contracts/bad_arity.aes new file mode 100644 index 0000000..0f02b14 --- /dev/null +++ b/test/contracts/bad_arity.aes @@ -0,0 +1,4 @@ +contract C = + type id('a) = 'a + entrypoint f() : id = 123 + entrypoint g() : id(int, int) = 123 \ No newline at end of file diff --git a/test/contracts/bad_number_of_args.aes b/test/contracts/bad_number_of_args.aes new file mode 100644 index 0000000..3571cae --- /dev/null +++ b/test/contracts/bad_number_of_args.aes @@ -0,0 +1,6 @@ +contract Test = + entrypoint f() = () + entrypoint g(x : int, y : string) = f(1) + entrypoint h() = g(1) + entrypoint i() = g("Litwo, ojczyzno moja") + diff --git a/test/contracts/bad_unnamed_map_update_default.aes b/test/contracts/bad_unnamed_map_update_default.aes new file mode 100644 index 0000000..7eb39c1 --- /dev/null +++ b/test/contracts/bad_unnamed_map_update_default.aes @@ -0,0 +1,5 @@ +contract C = + entrypoint f() = + let z = 123 + {}{ [1 = 0] = z + 1 } + 2 \ No newline at end of file diff --git a/test/contracts/empty_typedecl.aes b/test/contracts/empty_typedecl.aes new file mode 100644 index 0000000..51b0286 --- /dev/null +++ b/test/contracts/empty_typedecl.aes @@ -0,0 +1,3 @@ +contract C = + type t + entrypoint f() = 123 \ No newline at end of file diff --git a/test/contracts/higher_kinded_type.aes b/test/contracts/higher_kinded_type.aes new file mode 100644 index 0000000..2f71537 --- /dev/null +++ b/test/contracts/higher_kinded_type.aes @@ -0,0 +1,3 @@ +contract IWantToBelieve = + type stateT('s, 'm, 'a) = 's => 'm('a * 's) + entrypoint s() = 123 \ No newline at end of file diff --git a/test/contracts/multi_sig.aes b/test/contracts/multi_sig.aes index cb1363f..f6ae20b 100644 --- a/test/contracts/multi_sig.aes +++ b/test/contracts/multi_sig.aes @@ -15,7 +15,7 @@ contract MultiSig = | OwnerRemoved (address) // of { .removedOwner : Address } | ReqChanged (int) // of { .newReq : int } - let maxOwners : int = 250 + function maxOwners() : int = 250 record state = { nRequired : int , nOwners : int @@ -68,7 +68,7 @@ contract MultiSig = switch(check_pending(callhash())) CheckFail(state') => { state = state' } CheckOk(state') => - if(state.nOwners >= maxOwners) () /* TODO */ + if(state.nOwners >= maxOwners()) () /* TODO */ else let nOwners' = state'.nOwners + 1 { state = state' { owners = Map.insert(nOwners', newOwner, state'.owners) diff --git a/test/contracts/non_functional_entrypoint.aes b/test/contracts/non_functional_entrypoint.aes new file mode 100644 index 0000000..4162e38 --- /dev/null +++ b/test/contracts/non_functional_entrypoint.aes @@ -0,0 +1,5 @@ +contract C1 = + entrypoint f : int + +contract C = + entrypoint f() = 123 \ No newline at end of file diff --git a/test/contracts/not_toplevel_contract.aes b/test/contracts/not_toplevel_contract.aes new file mode 100644 index 0000000..4d2c579 --- /dev/null +++ b/test/contracts/not_toplevel_contract.aes @@ -0,0 +1,6 @@ +namespace BadNs = + contract Con = + entrypoint e : () => int + +contract Con = + entrypoint foo() = 43 diff --git a/test/contracts/not_toplevel_include.aes b/test/contracts/not_toplevel_include.aes new file mode 100644 index 0000000..0b08c88 --- /dev/null +++ b/test/contracts/not_toplevel_include.aes @@ -0,0 +1,5 @@ +namespace BadNs = + include "included.aes" + +contract Con = + entrypoint foo() = 43 diff --git a/test/contracts/bad_include_and_ns.aes b/test/contracts/not_toplevel_namespace.aes similarity index 62% rename from test/contracts/bad_include_and_ns.aes rename to test/contracts/not_toplevel_namespace.aes index 42ebe67..df27823 100644 --- a/test/contracts/bad_include_and_ns.aes +++ b/test/contracts/not_toplevel_namespace.aes @@ -1,5 +1,4 @@ -contract Bad = - include "included.aes" +contract BadCon = namespace Foo = function foo() = 42 diff --git a/test/contracts/toplevel_let.aes b/test/contracts/toplevel_let.aes new file mode 100644 index 0000000..adca04c --- /dev/null +++ b/test/contracts/toplevel_let.aes @@ -0,0 +1,3 @@ +contract C = + let this_is_illegal = 2/0 + entrypoint this_is_legal() = 2/0 \ No newline at end of file diff --git a/test/contracts/voting.aes b/test/contracts/voting.aes index d786f6d..2a5ec05 100644 --- a/test/contracts/voting.aes +++ b/test/contracts/voting.aes @@ -1,16 +1,3 @@ - -/* Contract type */ -contract VotingType = - type state - function init : list(string) => state - - function giveRightToVote : address => unit - function delegate : address => unit - function vote : int => unit - function winnerName : unit => string - function currentTally : unit => list(string * int) - -/* Contract implementation */ contract Voting = // Types