diff --git a/.circleci/config.yml b/.circleci/config.yml index c192e4e..88ad945 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,6 +8,15 @@ executors: working_directory: ~/aesophia jobs: + verify_rebar_lock: + executor: aebuilder + steps: + - checkout + - run: + name: Ensure lock file is up-to-date + command: | + ./rebar3 upgrade + git diff --quiet -- rebar.lock || (echo "rebar.lock is not up-to-date" && exit 1) build: executor: aebuilder steps: @@ -35,3 +44,10 @@ jobs: - _build/default/rebar3_20.3.8_plt - store_artifacts: path: _build/test/logs + +workflows: + version: 2 + build_test: + jobs: + - build + - verify_rebar_lock diff --git a/docs/sophia.md b/docs/sophia.md index 228dea9..ffcf2f8 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -2,9 +2,11 @@ **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) @@ -26,6 +28,7 @@ - [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) @@ -34,6 +37,7 @@ - [Example](#example) - [Sanity checks](#sanity-checks) - [AENS interface](#aens-interface) + - [Example](#example-1) - [Events](#events) - [Argument order](#argument-order) - [Compiler pragmas](#compiler-pragmas) @@ -52,6 +56,7 @@ - [Operators types](#operators-types) - [Operator precendences](#operator-precendences) - [Examples](#examples) + - [Delegation signature](#delegation-signature) ## The Sophia Language @@ -136,6 +141,36 @@ without calling it you can write 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 @@ -538,7 +573,16 @@ Strings can be compared for equality (`==`, `!=`), used as keys in maps and records, and used in builtin functions `String.length`, `String.concat` and the hash functions described below. -Please refer to the `Map` [library documentation](sophia_stdlib.md#String). +Please refer to the `String` [library documentation](sophia_stdlib.md#String). + +### 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 @@ -565,11 +609,10 @@ string`, `String.sha3(s)` and `Crypto.sha3(s)` will give different results on AE ### Authorization interface When a Generalized account is authorized, the authorization function needs -access to the transaction hash for the wrapped transaction. (A `GAMetaTx` -wrapping a transaction.) The transaction hash is available in the primitive -`Auth.tx_hash`, it is *only* available during authentication if invoked by a -normal contract call it returns `None`. - +access to the transaction and the transaction hash for the wrapped transaction. (A `GAMetaTx` +wrapping a transaction.) The transaction and the transaction hash is available in the primitive +`Auth.tx` and `Auth.tx_hash` respectively, they are *only* available during authentication if invoked by a +normal contract call they return `None`. ### Oracle interface You can attach an oracle to the current contract and you can interact with oracles @@ -645,6 +688,43 @@ 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 @@ -1069,3 +1149,11 @@ contract FundMe = amount = state.contributions[to]}) put(state{ contributions @ c = Map.delete(to, c) }) ``` + +### Delegation signature + +Some chain operations (`Oracle.` and `AENS.`) have an +optional delegation signature. This is typically used when a user/accounts +would like to allow a contract to act on it's behalf. The exact data to be +signed varies for the different operations, but in all cases you should prepend +the signature data with the `network_id` (`ae_mainnet` for the Aeternity mainnet, etc.). diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 6554810..9c58044 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -13,8 +13,8 @@ in the scope and do not need any actions to be used, while the others require so The out-of-the-box namespaces are: - [Bits](#Bits) -- [String](#String) - [Bytes](#Bytes) +- [Char](#Char) - [Int](#Int) - [Map](#Map) - [Address](#Address) @@ -33,6 +33,7 @@ include "List.aes" - [List](#List) - [Option](#Option) +- [String](#String) - [Func](#Func) - [Pair](#Pair) - [Triple](#Triple) @@ -45,7 +46,7 @@ They are available without any explicit includes. ## Bits -### none +#### none ``` Bits.none : bits ``` @@ -53,7 +54,7 @@ Bits.none : bits A bit field with all bits cleared -### all +#### all ``` Bits.all : bits ``` @@ -61,7 +62,7 @@ Bits.all : bits A bit field with all bits set -### set +#### set ``` Bits.set(b : bits, i : int) : bits ``` @@ -69,7 +70,7 @@ Bits.set(b : bits, i : int) : bits Set bit i -### clear +#### clear ``` Bits.clear(b : bits, i : int) : bits ``` @@ -77,7 +78,7 @@ Bits.clear(b : bits, i : int) : bits Clear bit i -### test +#### test ``` Bits.test(b : bits, i : int) : bool ``` @@ -85,7 +86,7 @@ Bits.test(b : bits, i : int) : bool Check if bit i is set -### sum +#### sum ``` Bits.sum(b : bits) : int ``` @@ -93,7 +94,7 @@ Bits.sum(b : bits) : int Count the number of set bits -### union +#### union ``` Bits.union(a : bits, b : bits) : bits ``` @@ -101,7 +102,7 @@ Bits.union(a : bits, b : bits) : bits Bitwise disjunction -### intersection +#### intersection ``` Bits.intersection(a : bits, b : bits) : bits ``` @@ -109,7 +110,7 @@ Bits.intersection(a : bits, b : bits) : bits Bitwise conjunction -### difference +#### difference ``` Bits.difference(a : bits, b : bits) : bits ``` @@ -117,51 +118,9 @@ Bits.difference(a : bits, b : bits) : bits Each bit is true if and only if it was 1 in `a` and 0 in `b` -## String - -### length -``` -String.length(s : string) : int -``` - -Returns the length of a string - - -### concat -``` -String.concat(s1 : string, s2 : string) : string -``` - -Concatenates two strings - - -### sha3 -``` -String.sha3(s : string) : hash -``` - -Calculates SHA3 sum of a string. - - -### sha256 -``` -String.sha256(s : string) : hash -``` - -Calculates SHA256 sum of a string - - -### blake2b -``` -String.blake2b(s : string) : hash -``` - -Calculates blake2b of a string - - ## Bytes -### to_int +#### to_int ``` Bytes.to_int(b : bytes(n)) : int ``` @@ -169,7 +128,7 @@ Bytes.to_int(b : bytes(n)) : int Interprets the byte array as a big endian integer -### to_str +#### to_str ``` Bytes.to_str(b : bytes(n)) : string ``` @@ -177,7 +136,7 @@ Bytes.to_str(b : bytes(n)) : string Returns the hexadecimal representation of the byte array -### concat +#### concat ``` Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n) ``` @@ -185,7 +144,7 @@ Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n) Concatenates two byte arrays -### split +#### split ``` Bytes.split(a : bytes(m + n)) : bytes(m) * bytes(n) ``` @@ -193,9 +152,28 @@ 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 +#### to_str ``` Int.to_str : int => string ``` @@ -205,45 +183,45 @@ Casts integer to string using decimal representation ## Map -### lookup +#### lookup `Map.lookup(k : 'k, m : map('k, 'v)) : option('v)` Returns the value under a key in given map as `Some` or `None` if the key is not present -### lookup_default +#### lookup_default `Map.lookup_default(k : 'k, m : map('k, 'v), v : 'v) : 'v` Returns the value under a key in given map or the default value `v` if the key is not present -### member +#### member `Map.member(k : 'k, m : map('k, 'v)) : bool` Checks if the key is present in the map -### delete +#### delete `Map.delete(k : 'k, m : map('k, 'v)) : map('k, 'v)` Removes the key from the map -### size +#### size `Map.size(m : map('k, 'v)) : int` Returns the number of elements in the map -### to_list +#### to_list `Map.to_list(m : map('k, 'v)) : list('k * 'v)` Returns a list containing pairs of keys and their respective elements. -### from_list +#### from_list `Map.from_list(m : list('k * 'v)) : map('k, 'v)` Turns a list of pairs of form `(key, value)` into a map @@ -252,7 +230,7 @@ Turns a list of pairs of form `(key, value)` into a map ## Address -### to_str +#### to_str ``` Address.to_str(a : address) : string ``` @@ -260,7 +238,7 @@ Address.to_str(a : address) : string Base58 encoded string -### is_contract +#### is_contract ``` Address.is_contract(a : address) : bool ``` @@ -268,7 +246,7 @@ Address.is_contract(a : address) : bool Is the address a contract -### is_oracle +#### is_oracle ``` Address.is_oracle(a : address) : bool ``` @@ -276,7 +254,7 @@ Address.is_oracle(a : address) : bool Is the address a registered oracle -### is_payable +#### is_payable ``` Address.is_payable(a : address) : bool ``` @@ -284,7 +262,7 @@ Address.is_payable(a : address) : bool Can the address be spent to -### to_contract +#### to_contract ``` Address.to_contract(a : address) : C ``` @@ -294,7 +272,7 @@ Cast address to contract type C (where `C` is a contract) ## Crypto -### sha3 +#### sha3 ``` Crypto.sha3(x : 'a) : hash ``` @@ -302,7 +280,7 @@ Crypto.sha3(x : 'a) : hash Hash any object to SHA3 -### sha256 +#### sha256 ``` Crypto.sha256(x : 'a) : hash ``` @@ -310,7 +288,7 @@ Crypto.sha256(x : 'a) : hash Hash any object to SHA256 -### blake2b +#### blake2b ``` Crypto.blake2b(x : 'a) : hash ``` @@ -318,7 +296,7 @@ Crypto.blake2b(x : 'a) : hash Hash any object to blake2b -### verify_sig +#### verify_sig ``` Crypto.verify_sig(msg : hash, pubkey : address, sig : signature) : bool ``` @@ -326,7 +304,7 @@ Crypto.verify_sig(msg : hash, pubkey : address, sig : signature) : bool Checks if the signature of `msg` was made using private key corresponding to the `pubkey` -### ecverify_secp256k1 +#### ecverify_secp256k1 ``` Crypto.ecverify_secp256k1(msg : hash, addr : bytes(20), sig : bytes(65)) : bool ``` @@ -334,7 +312,7 @@ Crypto.ecverify_secp256k1(msg : hash, addr : bytes(20), sig : bytes(65)) : bool Verifies a signature for a msg against an Ethereum style address -### ecrecover_secp256k1 +#### ecrecover_secp256k1 ``` Crypto.ecrecover_secp256k1(msg : hash, sig : bytes(65)) : option(bytes(20)) ``` @@ -342,7 +320,7 @@ Crypto.ecrecover_secp256k1(msg : hash, sig : bytes(65)) : option(bytes(20)) Recovers the Ethereum style address from a msg hash and respective signature -### verify_sig_secp256k1 +#### verify_sig_secp256k1 ``` Crypto.verify_sig_secp256k1(msg : hash, pubkey : bytes(64), sig : bytes(64)) : bool ``` @@ -351,16 +329,16 @@ Crypto.verify_sig_secp256k1(msg : hash, pubkey : bytes(64), sig : bytes(64)) : b ## Auth -### tx_hash +#### tx_hash ``` -Auth.tx_hash : option(hash) +Auth.tx_hash : option(Chain.tx) ``` Gets the transaction hash during authentication. ## Oracle -### register +#### register ``` Oracle.register(, acct : address, qfee : int, ttl : Chain.ttl) : oracle('a, 'b) ``` @@ -370,7 +348,7 @@ Registers new oracle answering questions of type `'a` with answers of type `'b`. * The `acct` is the address of the oracle to register (can be the same as the contract). * `signature` is a signature proving that the contract is allowed to register the account - the `network id` + `account address` + `contract address` (concatenated as byte arrays) is - signed with the + [signed](./sophia.md#delegation-signature) with the private key of the account, proving you have the private key of the oracle to be. If the address is the same as the contract `sign` is ignored and can be left out entirely. * The `qfee` is the minimum query fee to be paid by a user when asking a question of the oracle. @@ -386,7 +364,7 @@ Examples: ``` -### get_question +#### get_question ``` Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a ``` @@ -394,7 +372,7 @@ Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a Checks what was the question of query `q` on oracle `o` -### respond +#### respond ``` Oracle.respond(, o : oracle('a, 'b), q : oracle_query('a, 'b), 'b) : unit ``` @@ -403,10 +381,11 @@ Responds to the question `q` on `o`. Unless the contract address is the same as the oracle address the `signature` (which is an optional, named argument) needs to be provided. Proving that we have the private key of the oracle by -signing the `network id` + `oracle query id` + `contract address` +[signing](./sophia.md#delegation-signature) +the `network id` + `oracle query id` + `contract address` -### extend +#### extend ``` Oracle.extend(, o : oracle('a, 'b), ttl : Chain.ttl) : unit ``` @@ -416,7 +395,7 @@ Extends TTL of an oracle. * `o` is the oracle being extended * `ttl` must be `RelativeTTL`. The time to live of `o` will be extended by this value. -### query_fee +#### query_fee ``` Oracle.query_fee(o : oracle('a, 'b)) : int ``` @@ -424,7 +403,7 @@ Oracle.query_fee(o : oracle('a, 'b)) : int Returns the query fee of the oracle -### query +#### query ``` Oracle.query(o : oracle('a, 'b), q : 'a, qfee : int, qttl : Chain.ttl, rttl : Chain.ttl) : oracle_query('a, 'b) ``` @@ -437,7 +416,7 @@ Asks the oracle a question. The call fails if the oracle could expire before an answer. -### get_answer +#### get_answer ``` Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b) ``` @@ -445,7 +424,16 @@ Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b) Checks what is the optional query answer -### check +#### expire + +``` +Oracle.expire(o : oracle('a, 'b)) : int +``` + +Ask the oracle when it expires. The result is the block height at which it will happen. + + +#### check ``` Oracle.check(o : oracle('a, 'b)) : bool ``` @@ -453,7 +441,7 @@ Oracle.check(o : oracle('a, 'b)) : bool Returns `true` iff the oracle `o` exists and has correct type -### check_query +#### check_query ``` Oracle.check_query(o : oracle('a, 'b), q : oracle_query('a, 'b)) : bool ``` @@ -471,7 +459,25 @@ a signature to prove that we are allowed to do AENS operations on behalf of `owner`. The [signature is tied to a network id](https://github.com/aeternity/protocol/blob/iris/consensus/consensus.md#transaction-signature), i.e. the signature material should be prefixed by the network id. -### resolve +### Types + +#### name +``` +datatype name = Name(address, Chain.ttl, map(string, AENS.pointee)) +``` + + +#### pointee + +``` +datatype pointee = AccountPt(address) | OraclePt(address) + | ContractPt(address) | ChannelPt(address) +``` + + +### Functions + +#### resolve ``` AENS.resolve(name : string, key : string) : option('a) ``` @@ -482,51 +488,82 @@ associated with this name (for instance `"account_pubkey"`). The return type type checked against this type at run time. -### preclaim +#### lookup +``` +AENS.lookup(name : string) : option(AENS.name) +``` + +If `name` is an active name `AENS.lookup` returns a name object. +The three arguments to `Name` are `owner`, `expiry` and a map of the +`pointees` for the name. Note: the expiry of the name is always a fixed TTL. +For example: +``` +let Some(Name(owner, FixedTTL(expiry), ptrs)) = AENS.lookup("example.chain") +``` + + +#### preclaim ``` AENS.preclaim(owner : address, commitment_hash : hash, ) : unit ``` -The signature should be over `network id` + `owner address` + `Contract.address` -(concatenated as byte arrays). +The [signature](./sophia.md#delegation-signature) should be over +`network id` + `owner address` + `Contract.address` (concatenated as byte arrays). -### claim +#### claim ``` AENS.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit ``` -The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address` +The [signature](./sophia.md#delegation-signature) should be over +`network id` + `owner address` + `name_hash` + `Contract.address` +(concatenated as byte arrays) using the private key of the `owner` account for signing. -### transfer +#### transfer ``` AENS.transfer(owner : address, new_owner : address, name : string, ) : unit ``` Transfers name to the new owner. -The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address` +The [signature](./sophia.md#delegation-signature) should be over +`network id` + `owner address` + `name_hash` + `Contract.address` +(concatenated as byte arrays) using the private key of the `owner` account for signing. -### revoke +#### revoke ``` AENS.revoke(owner : address, name : string, ) : unit ``` Revokes the name to extend the ownership time. -The signature should be over `network id` + `owner address` + `name_hash` + `Contract.address` +The [signature](./sophia.md#delegation-signature) should be over +`network id` + `owner address` + `name_hash` + `Contract.address` +(concatenated as byte arrays) using the private key of the `owner` account for signing. +#### update +``` +AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int), + new_ptrs : map(string, AENS.pointee), ) : unit +``` + +Updates the name. If the optional parameters are set to `None` that parameter +will not be updated, for example if `None` is passed as `expiry` the expiry +block of the name is not changed. + + ## Contract Values related to the current contract -### creator +#### creator ``` Contract.creator : address ``` @@ -534,7 +571,7 @@ Contract.creator : address Address of the entity that signed the contract creation transaction -### address +#### address ``` Contract.address : address ``` @@ -542,7 +579,7 @@ Contract.address : address Address of the contract account -### balance +#### balance ``` Contract.balance : int ``` @@ -554,7 +591,7 @@ Amount of coins in the contract account Values related to the call to the current contract -### origin +#### origin ``` Call.origin : address ``` @@ -562,14 +599,14 @@ Call.origin : address The address of the account that signed the call transaction that led to this call. -### caller +#### caller ``` Call.caller : address ``` The address of the entity (possibly another contract) calling the contract. -### value +#### value ``` Call.value : int ``` @@ -577,7 +614,7 @@ Call.value : int The amount of coins transferred to the contract in the call. -### gas +#### gas ``` Call.gas_price : int ``` @@ -585,7 +622,7 @@ Call.gas_price : int The gas price of the current call. -### gas +#### gas ``` Call.gas_left() : int ``` @@ -597,7 +634,46 @@ The amount of gas left for the current call. Values and functions related to the chain itself and other entities that live on it. -### balance +### Types + +#### tx +``` +record tx = { paying_for : option(Chain.paying_for_tx) + , ga_metas : list(Chain.ga_meta_tx) + , actor : address + , fee : int + , ttl : int + , tx : Chain.base_tx } +``` + +#### ga_meta_tx +``` +datatype ga_meta_tx = GAMetaTx(address, int) +``` + +#### paying_for_tx +``` +datatype paying_for_tx = PayingForTx(address, int) +``` + + +#### base_tx +``` +datatype base_tx = SpendTx(address, int, string) + | OracleRegisterTx | OracleQueryTx | OracleResponseTx | OracleExtendTx + | NamePreclaimTx | NameClaimTx(hash) | NameUpdateTx(string) + | NameRevokeTx(hash) | NameTransferTx(address, string) + | ChannelCreateTx(address) | ChannelDepositTx(address, int) | ChannelWithdrawTx(address, int) | + | ChannelForceProgressTx(address) | ChannelCloseMutualTx(address) | ChannelCloseSoloTx(address) + | ChannelSlashTx(address) | ChannelSettleTx(address) | ChannelSnapshotSoloTx(address) + | ContractCreateTx(int) | ContractCallTx(address, int) + | GAAttachTx +``` + + +### Functions + +#### balance ``` Chain.balance(a : address) : int ``` @@ -605,15 +681,20 @@ Chain.balance(a : address) : int The balance of account `a`. -### block_hash +#### block_hash ``` Chain.block_hash(h : int) : option(bytes(32)) ``` -The hash of the block at height `h`. +The hash of the block at height `h`. `h` has to be within 256 blocks from the +current height of the chain or else the function will return `None`. + +NOTE: In AEVM and FATE VM version 1 `Chain.block_height` was not considered an +allowed height. From FATE VM version 2 (IRIS) it will return the block hash of +the current generation. -### block_height +#### block_height ``` Chain.block_height : int" ``` @@ -621,7 +702,7 @@ Chain.block_height : int" The height of the current block (i.e. the block in which the current call will be included). -### coinbase +#### coinbase ``` Chain.coinbase : address ``` @@ -629,7 +710,7 @@ Chain.coinbase : address The address of the account that mined the current block. -### timestamp +#### timestamp ``` Chain.timestamp : int ``` @@ -637,7 +718,7 @@ Chain.timestamp : int The timestamp of the current block. -### difficulty +#### difficulty ``` Chain.difficulty : int ``` @@ -645,7 +726,7 @@ Chain.difficulty : int The difficulty of the current block. -### gas +#### gas ``` Chain.gas_limit : int ``` @@ -653,7 +734,7 @@ Chain.gas_limit : int The gas limit of the current block. -### event +#### event ``` Chain.event(e : event) : unit ``` @@ -668,7 +749,7 @@ These need to be explicitly included (with `.aes` suffix) This module contains common operations on lists like constructing, querying, traversing etc. -### is_empty +#### is_empty ``` List.is_empty(l : list('a)) : bool ``` @@ -676,7 +757,7 @@ List.is_empty(l : list('a)) : bool Returns `true` iff the list is equal to `[]`. -### first +#### first ``` List.first(l : list('a)) : option('a) ``` @@ -684,7 +765,7 @@ List.first(l : list('a)) : option('a) Returns `Some` of the first element of a list or `None` if the list is empty. -### tail +#### tail ``` List.tail(l : list('a)) : option(list('a)) ``` @@ -692,7 +773,7 @@ List.tail(l : list('a)) : option(list('a)) Returns `Some` of a list without its first element or `None` if the list is empty. -### last +#### last ``` List.last(l : list('a)) : option('a) ``` @@ -700,14 +781,14 @@ List.last(l : list('a)) : option('a) Returns `Some` of the last element of a list or `None` if the list is empty. -### contains +#### contains ``` List.contains(e : 'a, l : list('a)) : bool ``` Checks if list `l` contains element `e`. Equivalent to `List.find(x => x == e, l) != None`. -### find +#### find ``` List.find(p : 'a => bool, l : list('a)) : option('a) ``` @@ -715,7 +796,7 @@ List.find(p : 'a => bool, l : list('a)) : option('a) Finds first element of `l` fulfilling predicate `p` as `Some` or `None` if no such element exists. -### find_indices +#### find_indices ``` List.find_indices(p : 'a => bool, l : list('a)) : list(int) ``` @@ -723,7 +804,7 @@ List.find_indices(p : 'a => bool, l : list('a)) : list(int) Returns list of all indices of elements from `l` that fulfill the predicate `p`. -### nth +#### nth ``` List.nth(n : int, l : list('a)) : option('a) ``` @@ -731,7 +812,7 @@ List.nth(n : int, l : list('a)) : option('a) Gets `n`th element of `l` as `Some` or `None` if `l` is shorter than `n + 1` or `n` is negative. -### get +#### get ``` List.get(n : int, l : list('a)) : 'a ``` @@ -739,7 +820,7 @@ List.get(n : int, l : list('a)) : 'a Gets `n`th element of `l` forcefully, throwing and error if `l` is shorter than `n + 1` or `n` is negative. -### length +#### length ``` List.length(l : list('a)) : int ``` @@ -747,7 +828,7 @@ List.length(l : list('a)) : int Returns length of a list. -### from_to +#### from_to ``` List.from_to(a : int, b : int) : list(int) ``` @@ -755,7 +836,7 @@ List.from_to(a : int, b : int) : list(int) Creates an ascending sequence of all integer numbers between `a` and `b` (including `a` and `b`). -### from_to_step +#### from_to_step ``` List.from_to_step(a : int, b : int, step : int) : list(int) ``` @@ -763,7 +844,7 @@ List.from_to_step(a : int, b : int, step : int) : list(int) Creates an ascending sequence of integer numbers betweeen `a` and `b` jumping by given `step`. Includes `a` and takes `b` only if `(b - a) mod step == 0`. `step` should be bigger than 0. -### replace_at +#### replace_at ``` List.replace_at(n : int, e : 'a, l : list('a)) : list('a) ``` @@ -771,7 +852,7 @@ List.replace_at(n : int, e : 'a, l : list('a)) : list('a) Replaces `n`th element of `l` with `e`. Throws an error if `n` is negative or would cause an overflow. -### insert_at +#### insert_at ``` List.insert_at(n : int, e : 'a, l : list('a)) : list('a) ``` @@ -783,7 +864,7 @@ insert_at(2, 9, [1,2,3,4]) will yield `[1,2,9,3,4]`. -### insert_by +#### insert_by ``` List.insert_by(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) ``` @@ -795,7 +876,7 @@ insert_by((a, b) => a < b, 4, [1,2,3,5,6,7]) will yield `[1,2,3,4,5,6,7]` -### foldr +#### foldr ``` List.foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b ``` @@ -804,7 +885,7 @@ Right fold of a list. Assuming `l = [x, y, z]` will return `f(x, f(y, f(z, nil)) Not tail recursive. -### foldl +#### foldl ``` List.foldl(rcons : ('b, 'a) => 'b, acc : 'b, l : list('a)) : 'b ``` @@ -812,7 +893,7 @@ List.foldl(rcons : ('b, 'a) => 'b, acc : 'b, l : list('a)) : 'b Left fold of a list. Assuming `l = [x, y, z]` will return `f(f(f(acc, x), y), z)`. Tail recursive. -### foreach +#### foreach ``` List.foreach(l : list('a), f : 'a => unit) : unit ``` @@ -820,7 +901,7 @@ List.foreach(l : list('a), f : 'a => unit) : unit Evaluates `f` on each element of a list. -### reverse +#### reverse ``` List.reverse(l : list('a)) : list('a) ``` @@ -828,7 +909,7 @@ List.reverse(l : list('a)) : list('a) Returns a copy of `l` with reversed order of elements. -### map +#### map ``` List.map(f : 'a => 'b, l : list('a)) : list('b) ``` @@ -840,7 +921,7 @@ map((x) => x == 0, [1, 2, 0, 3, 0]) will yield `[false, false, true, false, true]` -### flat_map +#### flat_map ``` List.flat_map(f : 'a => list('b), l : list('a)) : list('b) ``` @@ -852,7 +933,7 @@ flat_map((x) => [x, x * 10], [1, 2, 3]) will yield `[1, 10, 2, 20, 3, 30]` -### filter +#### filter ``` List.filter(p : 'a => bool, l : list('a)) : list('a) ``` @@ -864,7 +945,7 @@ filter((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3]) will yield `[1, 1, 2]` -### take +#### take ``` List.take(n : int, l : list('a)) : list('a) ``` @@ -872,7 +953,7 @@ List.take(n : int, l : list('a)) : list('a) Takes `n` first elements of `l`. Fails if `n` is negative. If `n` is greater than length of a list it will return whole list. -### drop +#### drop ``` List.drop(n : int, l : list('a)) : list('a) ``` @@ -880,7 +961,7 @@ List.drop(n : int, l : list('a)) : list('a) Removes `n` first elements of `l`. Fails if `n` is negative. If `n` is greater than length of a list it will return `[]`. -### take_while +#### take_while ``` List.take_while(p : 'a => bool, l : list('a)) : list('a) ``` @@ -888,7 +969,7 @@ List.take_while(p : 'a => bool, l : list('a)) : list('a) Returns longest prefix of `l` in which all elements fulfill `p`. -### drop_while +#### drop_while ``` List.drop_while(p : 'a => bool, l : list('a)) : list('a) ``` @@ -896,7 +977,7 @@ List.drop_while(p : 'a => bool, l : list('a)) : list('a) Removes longest prefix from `l` in which all elements fulfill `p`. -### partition +#### partition ``` List.partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) ``` @@ -908,7 +989,7 @@ partition((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3]) will yield `([1, 1, 2], [-1, -2, 0, -3])` -### flatten +#### flatten ``` List.flatten(ll : list(list('a))) : list('a) ``` @@ -916,7 +997,7 @@ List.flatten(ll : list(list('a))) : list('a) Flattens a list of lists into a one list. -### all +#### all ``` List.all(p : 'a => bool, l : list('a)) : bool ``` @@ -924,7 +1005,7 @@ List.all(p : 'a => bool, l : list('a)) : bool Checks if all elements of a list fulfill predicate `p`. -### any +#### any ``` List.any(p : 'a => bool, l : list('a)) : bool ``` @@ -932,7 +1013,7 @@ List.any(p : 'a => bool, l : list('a)) : bool Checks if any element of a list fulfills predicate `p`. -### sum +#### sum ``` List.sum(l : list(int)) : int ``` @@ -940,7 +1021,7 @@ List.sum(l : list(int)) : int Sums elements of a list. Returns 0 if the list is empty. -### product +#### product ``` List.product(l : list(int)) : int ``` @@ -948,7 +1029,7 @@ List.product(l : list(int)) : int Multiplies elements of a list. Returns 1 if the list is empty. -### zip_with +#### zip_with ``` List.zip_with(f : ('a, 'b) => 'c, l1 : list('a), l2 : list('b)) : list('c) ``` @@ -960,14 +1041,14 @@ zip_with((a, b) => a + b, [1,2], [1,2,3]) will yield `[2,4]` -### zip +#### zip ``` List.zip(l1 : list('a), l2 : list('b)) : list('a * 'b) ``` Special case of [zip_with](#zip_with) where the zipping function is `(a, b) => (a, b)`. -### unzip +#### unzip ``` List.unzip(l : list('a * 'b)) : list('a) * list('b) ``` @@ -975,7 +1056,7 @@ List.unzip(l : list('a * 'b)) : list('a) * list('b) Opposite to the `zip` operation. Takes a list of pairs and returns pair of lists with respective elements on same indices. -### sort +#### sort ``` List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) ``` @@ -983,7 +1064,7 @@ List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) Sorts a list using given comparator. `lesser_cmp(x, y)` should return `true` iff `x < y`. If `lesser_cmp` is not transitive or there exists an element `x` such that `lesser_cmp(x, x)` or there exists a pair of elements `x` and `y` such that `lesser_cmp(x, y) && lesser_cmp(y, x)` then the result is undefined. Currently O(n^2). -### intersperse +#### intersperse ``` List.intersperse(delim : 'a, l : list('a)) : list('a) ``` @@ -995,7 +1076,7 @@ intersperse(0, [1, 2, 3, 4]) will yield `[1, 0, 2, 0, 3, 0, 4]` -### enumerate +#### enumerate ``` List.enumerate(l : list('a)) : list(int * 'a) ``` @@ -1007,7 +1088,7 @@ Equivalent to [zip](#zip) with `[0..length(l)]`, but slightly faster. Common operations on `option` types and lists of `option`s. -### is_none +#### is_none ``` Option.is_none(o : option('a)) : bool ``` @@ -1015,7 +1096,7 @@ Option.is_none(o : option('a)) : bool Returns true iff `o == None` -### is_some +#### is_some ``` Option.is_some(o : option('a)) : bool ``` @@ -1023,7 +1104,7 @@ Option.is_some(o : option('a)) : bool Returns true iff `o` is not `None`. -### match +#### match ``` Option.match(n : 'b, s : 'a => 'b, o : option('a)) : 'b ``` @@ -1031,7 +1112,7 @@ Option.match(n : 'b, s : 'a => 'b, o : option('a)) : 'b Behaves like pattern matching on `option` using two case functions. -### default +#### default ``` Option.default(def : 'a, o : option('a)) : 'a ``` @@ -1039,7 +1120,7 @@ Option.default(def : 'a, o : option('a)) : 'a Escapes `option` wrapping by providing default value for `None`. -### force +#### force ``` Option.force(o : option('a)) : 'a ``` @@ -1047,14 +1128,14 @@ Option.force(o : option('a)) : 'a Forcefully escapes `option` wrapping assuming it is `Some`. Throws error on `None`. -### contains +#### contains ``` Option.contains(e : 'a, o : option('a)) : bool ``` Returns `true` if and only if `o` contains element equal to `e`. Equivalent to `Option.match(false, x => x == e, o)`. -### on_elem +#### on_elem ``` Option.on_elem(o : option('a), f : 'a => unit) : unit ``` @@ -1062,7 +1143,7 @@ Option.on_elem(o : option('a), f : 'a => unit) : unit Evaluates `f` on element under `Some`. Does nothing on `None`. -### map +#### map ``` Option.map(f : 'a => 'b, o : option('a)) : option('b) ``` @@ -1070,7 +1151,7 @@ Option.map(f : 'a => 'b, o : option('a)) : option('b) Maps element under `Some`. Leaves `None` unchanged. -### map2 +#### map2 ``` Option.map2(f : ('a, 'b) => 'c, o1 : option('a), o2 : option('b)) : option('c) ``` @@ -1086,7 +1167,7 @@ map2((a, b) => a + b, Some(1), None) will yield `None`. -### map3 +#### map3 ``` Option.map3(f : ('a, 'b, 'c) => 'd, o1 : option('a), o2 : option('b), o3 : option('c)) : option('d) ``` @@ -1094,7 +1175,7 @@ Option.map3(f : ('a, 'b, 'c) => 'd, o1 : option('a), o2 : option('b), o3 : optio Same as [map2](#map2) but with arity 3 function. -### app_over +#### app_over ``` Option.app_over(f : option ('a => 'b), o : option('a)) : option('b) ``` @@ -1110,7 +1191,7 @@ app_over(Some((x) => x + 1), None) will yield `None`. -### flat_map +#### flat_map ``` Option.flat_map(f : 'a => option('b), o : option('a)) : option('b) ``` @@ -1126,7 +1207,7 @@ flat_map((x) => Some(x + 1), None) will yield `None`. -### to_list +#### to_list ``` Option.to_list(o : option('a)) : list('a) ``` @@ -1134,7 +1215,7 @@ Option.to_list(o : option('a)) : list('a) Turns `o` into an empty (if `None`) or singleton (if `Some`) list. -### filter_options +#### filter_options ``` Option.filter_options(l : list(option('a))) : list('a) ``` @@ -1146,7 +1227,7 @@ filter_options([Some(1), None, Some(2)]) will yield `[1, 2]`. -### seq_options +#### seq_options ``` Option.seq_options(l : list (option('a))) : option (list('a)) ``` @@ -1162,7 +1243,7 @@ seq_options([Some(1), Some(2), None]) will yield `None`. -### choose +#### choose ``` Option.choose(o1 : option('a), o2 : option('a)) : option('a) ``` @@ -1170,7 +1251,7 @@ Option.choose(o1 : option('a), o2 : option('a)) : option('a) Out of two `option`s choose the one that is `Some`, or `None` if both are `None`s. -### choose_first +#### choose_first ``` Option.choose_first(l : list(option('a))) : option('a) ``` @@ -1178,11 +1259,127 @@ Option.choose_first(l : list(option('a))) : option('a) Same as [choose](#choose), but chooses from a list insted of two arguments. +## String + +Operations on the `string` type. A `string` is a UTF-8 encoded byte array. + +#### length +`length(s : string) : int` + +The length of a string. + +Note: not equivalent to byte size of the string, rather `List.length(String.to_list(s))` + +#### concat +``` +concat(s1 : string, s2 : string) : string +``` + +Concatenates `s1` and `s2`. + +#### concats +``` +concats(ss : list(string)) : string +``` + +Concatenates a list of strings. + +#### to\_list +``` +to_list(s : string) : list(char) +``` + +Converts a `string` to a list of `char` - the code points are normalized, but +composite characters are possibly converted to multiple `char`s. For example the +string "😜i̇" is converted to `[128540,105,775]` - where the smiley is the first +code point and the strangely dotted `i` becomes `[105, 775]`. + +#### from\_list +``` +from_list(cs : list(char)) : string +``` + +Converts a list of characters into a normalized UTF-8 string. + +#### to\_lower +``` +to_lower(s : string) : string +``` + +Converts a string to lowercase. + +#### to\_upper +``` +to_upper(s : string) : string +``` + +Converts a string to uppercase. + +#### at +``` +at(ix : int, s : string) : option(char) +``` + +Returns the character/codepoint at (zero-based) index `ix`. Basically the equivalent to +`List.nth(ix, String.to_list(s))`. + +#### split +``` +split(ix : int, s:string) : string * string +``` + +Splits a string at (zero-based) index `ix`. + +#### contains +``` +contains(str : string, pat : string) : option(int) +``` + +Searches for `pat` in `str`, returning `Some(ix)` if `pat` is a substring of +`str` starting at position `ix`, otherwise returns `None`. + +#### tokens +``` +tokens(str : string, pat : string) : list(string) +``` + +Splits `str` into tokens, `pat` is the divider of tokens. + +#### to\_int +``` +to_int(s : string) : option(int) +``` + +Converts a decimal ("123", "-253") or a hexadecimal ("0xa2f", "-0xBBB") string into +an integer. If the string doesn't contain a valid number `None` is returned. + +#### sha3 +``` +sha3(s : string) : hash +``` + +Computes the SHA3/Keccak hash of the string. + +#### sha256 +``` +sha256(s : string) : hash +``` + +Computes the SHA256 hash of the string. + +#### blake2b +``` +blake2b(s : string) : hash +``` + +Computes the Blake2B hash of the string. + + ## Func Functional combinators. -### id +#### id ``` Func.id(x : 'a) : 'a ``` @@ -1190,7 +1387,7 @@ Func.id(x : 'a) : 'a Identity function. Returns its argument. -### const +#### const ``` Func.const(x : 'a) : 'b => 'a = (y) => x ``` @@ -1198,7 +1395,7 @@ Func.const(x : 'a) : 'b => 'a = (y) => x Constant function constructor. Given `x` returns a function that returns `x` regardless of its argument. -### flip +#### flip ``` Func.flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c ``` @@ -1206,7 +1403,7 @@ Func.flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c Switches order of arguments of arity 2 function. -### comp +#### comp ``` Func.comp(f : 'b => 'c, g : 'a => 'b) : 'a => 'c ``` @@ -1214,7 +1411,7 @@ Func.comp(f : 'b => 'c, g : 'a => 'b) : 'a => 'c Function composition. `comp(f, g)(x) == f(g(x))`. -### pipe +#### pipe ``` Func.pipe(f : 'a => 'b, g : 'b => 'c) : 'a => 'c ``` @@ -1222,7 +1419,7 @@ Func.pipe(f : 'a => 'b, g : 'b => 'c) : 'a => 'c Flipped function composition. `pipe(f, g)(x) == g(f(x))`. -### rapply +#### rapply ``` Func.rapply(x : 'a, f : 'a => 'b) : 'b ``` @@ -1230,7 +1427,7 @@ Func.rapply(x : 'a, f : 'a => 'b) : 'b Reverse application. `rapply(x, f) == f(x)`. -### recur +#### recur ``` Func.recur(f : ('arg => 'res, 'arg) => 'res) : 'arg => 'res ``` @@ -1262,7 +1459,7 @@ let factorial_c(n, step) = ``` -### iter +#### iter ``` Func.iter(n : int, f : 'a => 'a) : 'a => 'a ``` @@ -1270,7 +1467,7 @@ Func.iter(n : int, f : 'a => 'a) : 'a => 'a `n`th composition of f with itself, for instance `iter(3, f)` is equivalent to `(x) => f(f(f(x)))`. -### curry +#### curry ``` Func.curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c) Func.curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd)) @@ -1281,7 +1478,7 @@ one argument and returns a function that waits for the rest in the same manner. For instance `curry2((a, b) => a + b)(1)(2) == 3`. -### uncurry +#### uncurry ``` Func.uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c Func.uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd @@ -1290,7 +1487,7 @@ Func.uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd Opposite to [curry](#curry). -### tuplify +#### tuplify ``` Func.tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c Func.tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd @@ -1299,7 +1496,7 @@ Func.tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd Turns a function that takes n arguments into a function that takes an n-tuple. -### untuplify +#### untuplify ``` Func.untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c Func.untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd @@ -1312,7 +1509,7 @@ Opposite to [tuplify](#tuplify). Common operations on 2-tuples. -### fst +#### fst ``` Pair.fst(t : ('a * 'b)) : 'a ``` @@ -1320,7 +1517,7 @@ Pair.fst(t : ('a * 'b)) : 'a First element projection. -### snd +#### snd ``` Pair.snd(t : ('a * 'b)) : 'b ``` @@ -1328,7 +1525,7 @@ Pair.snd(t : ('a * 'b)) : 'b Second element projection. -### map1 +#### map1 ``` Pair.map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) ``` @@ -1336,7 +1533,7 @@ Pair.map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) Applies function over first element. -### map2 +#### map2 ``` Pair.map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) ``` @@ -1344,7 +1541,7 @@ Pair.map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) Applies function over second element. -### bimap +#### bimap ``` Pair.bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd) ``` @@ -1352,7 +1549,7 @@ Pair.bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd) Applies functions over respective elements. -### swap +#### swap ``` Pair.swap(t : ('a * 'b)) : ('b * 'a) ``` @@ -1362,7 +1559,7 @@ Swaps elements. ## Triple -### fst +#### fst ``` Triple.fst(t : ('a * 'b * 'c)) : 'a ``` @@ -1370,7 +1567,7 @@ Triple.fst(t : ('a * 'b * 'c)) : 'a First element projection. -### snd +#### snd ``` Triple.snd(t : ('a * 'b * 'c)) : 'b ``` @@ -1378,7 +1575,7 @@ Triple.snd(t : ('a * 'b * 'c)) : 'b Second element projection. -### thd +#### thd ``` Triple.thd(t : ('a * 'b * 'c)) : 'c ``` @@ -1386,7 +1583,7 @@ Triple.thd(t : ('a * 'b * 'c)) : 'c Third element projection. -### map1 +#### map1 ``` Triple.map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) ``` @@ -1394,7 +1591,7 @@ Triple.map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) Applies function over first element. -### map2 +#### map2 ``` Triple.map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) ``` @@ -1402,7 +1599,7 @@ Triple.map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) Applies function over second element. -### map3 +#### map3 ``` Triple.map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) ``` @@ -1410,7 +1607,7 @@ Triple.map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) Applies function over third element. -### trimap +#### trimap ``` Triple.trimap(f : 'a => 'x, g : 'b => 'y, h : 'c => 'z, t : ('a * 'b * 'c)) : ('x * 'y * 'z) ``` @@ -1418,7 +1615,7 @@ Triple.trimap(f : 'a => 'x, g : 'b => 'y, h : 'c => 'z, t : ('a * 'b * 'c)) : (' Applies functions over respective elements. -### swap +#### swap ``` Triple.swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a) ``` @@ -1426,7 +1623,7 @@ Triple.swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a) Swaps first and third element. -### rotr +#### rotr ``` Triple.rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) ``` @@ -1434,7 +1631,7 @@ Triple.rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) Cyclic rotation of the elements to the right. -### rotl +#### rotl ``` Triple.rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a) ``` @@ -1444,196 +1641,224 @@ Cyclic rotation of the elements to the left. ## BLS12\_381 ### Types -- `fp // Built-in (Montgomery) integer representation 32 bytes` -- `fr // Built-in (Montgomery) integer representation 48 bytes` -- `record fp2 = { x1 : fp, x2 : fp }` -- `record g1 = { x : fp, y : fp, z : fp }` -- `record g2 = { x : fp2, y : fp2, z : fp2 }` -- `record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp, x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp }` -### pairing\_check +#### fp + +Built-in (Montgomery) integer representation 32 bytes + + +#### fr + +Built-in (Montgomery) integer representation 48 bytes + + +#### fp2 +``` +record fp2 = { x1 : fp, x2 : fp }` +``` + +#### g1 +``` +record g1 = { x : fp, y : fp, z : fp } +``` + + +#### g2 +``` +record g2 = { x : fp2, y : fp2, z : fp2 } +``` + + +#### gt +``` +record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp, x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp } +``` + +### Functions + +#### pairing\_check ``` BLS12_381.pairing_check(xs : list(g1), ys : list(g2)) : bool ``` Pairing check of a list of points, `xs` and `ys` should be of equal length. -### int_to_fr +#### int_to_fr ``` BLS12_381.int_to_fr(x : int) : fr ``` Convert an integer to an `fr` - a 32 bytes internal (Montgomery) integer representation. -### int_to_fp +#### int_to_fp ``` BLS12_381.int_to_fp(x : int) : fp ``` Convert an integer to an `fp` - a 48 bytes internal (Montgomery) integer representation. -### fr_to_int +#### fr_to_int ``` BLS12_381.fr_to_int(x : fr) : int ``` Convert a `fr` value into an integer. -### fp_to_int +#### fp_to_int ``` BLS12_381.fp_to_int(x : fp) : int ``` Convert a `fp` value into an integer. -### mk_g1 +#### mk_g1 ``` BLS12_381.mk_g1(x : int, y : int, z : int) : g1 ``` Construct a `g1` point from three integers. -### mk_g2 +#### mk_g2 ``` BLS12_381.mk_g2(x1 : int, x2 : int, y1 : int, y2 : int, z1 : int, z2 : int) : g2 ``` Construct a `g2` point from six integers. -### g1_neg +#### g1_neg ``` BLS12_381.g1_neg(p : g1) : g1 ``` Negate a `g1` value. -### g1_norm +#### g1_norm ``` BLS12_381.g1_norm(p : g1) : g1 ``` Normalize a `g1` value. -### g1_valid +#### g1_valid ``` BLS12_381.g1_valid(p : g1) : bool ``` Check that a `g1` value is a group member. -### g1_is_zero +#### g1_is_zero ``` BLS12_381.g1_is_zero(p : g1) : bool ``` Check if a `g1` value corresponds to the zero value of the group. -### g1_add +#### g1_add ``` BLS12_381.g1_add(p : g1, q : g1) : g1 ``` Add two `g1` values. -### g1_mul +#### g1_mul ``` BLS12_381.g1_mul(k : fr, p : g1) : g1 ``` Scalar multiplication for `g1`. -### g2_neg +#### g2_neg ``` BLS12_381.g2_neg(p : g2) : g2 ``` Negate a `g2` value. -### g2_norm +#### g2_norm ``` BLS12_381.g2_norm(p : g2) : g2 ``` Normalize a `g2` value. -### g2_valid +#### g2_valid ``` BLS12_381.g2_valid(p : g2) : bool ``` Check that a `g2` value is a group member. -### g2_is_zero +#### g2_is_zero ``` BLS12_381.g2_is_zero(p : g2) : bool ``` Check if a `g2` value corresponds to the zero value of the group. -### g2_add +#### g2_add ``` BLS12_381.g2_add(p : g2, q : g2) : g2 ``` Add two `g2` values. -### g2_mul +#### g2_mul ``` BLS12_381.g2_mul(k : fr, p : g2) : g2 ``` Scalar multiplication for `g2`. -### gt_inv +#### gt_inv ``` BLS12_381.gt_inv(p : gt) : gt ``` Invert a `gt` value. -### gt_add +#### gt_add ``` BLS12_381.gt_add(p : gt, q : gt) : gt ``` Add two `gt` values. -### gt_mul +#### gt_mul ``` BLS12_381.gt_mul(p : gt, q : gt) : gt ``` Multiply two `gt` values. -### gt_pow +#### gt_pow ``` BLS12_381.gt_pow(p : gt, k : fr) : gt ``` Calculate exponentiation `p ^ k`. -### gt_is_one +#### gt_is_one ``` BLS12_381.gt_is_one(p : gt) : bool ``` Compare a `gt` value to the unit value of the Gt group. -### pairing +#### pairing ``` BLS12_381.pairing(p : g1, q : g2) : gt ``` Compute the pairing of a `g1` value and a `g2` value. -### miller_loop +#### miller_loop ``` BLS12_381.miller_loop(p : g1, q : g2) : gt ``` Do the Miller loop stage of pairing for `g1` and `g2`. -### final_exp +#### final_exp ``` BLS12_381.final_exp(p : gt) : gt ``` @@ -1664,192 +1889,205 @@ language provides checkers to prevent unintended usage of them. Therefore the ty **will** allow that and the results of such comparison will be unspecified. You should use [lt](#lt), [geq](#geq), [eq](#eq) etc instead. -### make_frac +### Types + +#### frac +``` +datatype frac = Pos(int, int) | Zero | Neg(int, int) +``` + +Internal representation of fractional numbers. First integer encodes the numerator and the second the denominator – +both must be always positive, as the sign is being handled by the choice of the constructor. + + +### Functions + +#### make_frac `Frac.make_frac(n : int, d : int) : frac` Creates a fraction out of numerator and denominator. Automatically normalizes, so `make_frac(2, 4)` and `make_frac(1, 2)` will yield same results. -### num +#### num `Frac.num(f : frac) : int` Returns the numerator of a fraction. -### den +#### den `Frac.den(f : frac) : int` Returns the denominator of a fraction. -### to_pair +#### to_pair `Frac.to_pair(f : frac) : int * int` Turns a fraction into a pair of numerator and denominator. -### sign +#### sign `Frac.sign(f : frac) : int` Returns the signum of a fraction, -1, 0, 1 if negative, zero, positive respectively. -### to_str +#### to_str `Frac.to_str(f : frac) : string` Conversion to string. Does not display division by 1 or denominator if equals zero. -### simplify +#### simplify `Frac.simplify(f : frac) : frac` Reduces fraction to normal form if for some reason it is not in it. -### eq +#### eq `Frac.eq(a : frac, b : frac) : bool` Checks if `a` is equal to `b`. -### neq +#### neq `Frac.neq(a : frac, b : frac) : bool` Checks if `a` is not equal to `b`. -### geq +#### geq `Frac.geq(a : frac, b : frac) : bool` Checks if `a` is greater or equal to `b`. -### leq +#### leq `Frac.leq(a : frac, b : frac) : bool` Checks if `a` is lesser or equal to `b`. -### gt +#### gt `Frac.gt(a : frac, b : frac) : bool` Checks if `a` is greater than `b`. -### lt +#### lt `Frac.lt(a : frac, b : frac) : bool` Checks if `a` is lesser than `b`. -### min +#### min `Frac.min(a : frac, b : frac) : frac` Chooses lesser of the two fractions. -### max +#### max `Frac.max(a : frac, b : frac) : frac` Chooses greater of the two fractions. -### abs +#### abs `Frac.abs(f : frac) : frac` Absolute value. -### from_int +#### from_int `Frac.from_int(n : int) : frac` From integer conversion. Effectively `make_frac(n, 1)`. -### floor +#### floor `Frac.floor(f : frac) : int` Rounds a fraction to the nearest lesser or equal integer. -### ceil +#### ceil `Frac.ceil(f : frac) : int` Rounds a fraction to the nearest greater or equal integer. -### round_to_zero +#### round_to_zero `Frac.round_to_zero(f : frac) : int` Rounds a fraction towards zero. Effectively `ceil` if lesser than zero and `floor` if greater. -### round_from_zero +#### round_from_zero `Frac.round_from_zero(f : frac) : int` Rounds a fraction from zero. Effectively `ceil` if greater than zero and `floor` if lesser. -### round +#### round `Frac.round(f : frac) : int` Rounds a fraction to a nearest integer. If two integers are in the same distance it will choose the even one. -### add +#### add `Frac.add(a : frac, b : frac) : frac` Sum of the fractions. -### neg +#### neg `Frac.neg(a : frac) : frac` Negation of the fraction. -### sub +#### sub `Frac.sub(a : frac, b : frac) : frac` Subtraction of two fractions. -### inv +#### inv `Frac.inv(a : frac) : frac` Inverts a fraction. Throws error if `a` is zero. -### mul +#### mul `Frac.mul(a : frac, b : frac) : frac` Multiplication of two fractions. -### div +#### div `Frac.div(a : frac, b : frac) : frac` Division of two fractions. -### int_exp +#### int_exp `Frac.int_exp(b : frac, e : int) : frac` Takes `b` to the power of `e`. The exponent can be a negative value. -### optimize +#### optimize `Frac.optimize(f : frac, loss : frac) : frac` Shrink the internal size of a fraction as much as possible by approximating it to the point where the error would exceed the `loss` value. -### is_sane +#### is_sane `Frac.is_sane(f : frac) : bool` For debugging. If it ever returns false in a code that doesn't call `frac` constructors or diff --git a/priv/stdlib/BLS12_381.aes b/priv/stdlib/BLS12_381.aes new file mode 100644 index 0000000..4bc27ff --- /dev/null +++ b/priv/stdlib/BLS12_381.aes @@ -0,0 +1,68 @@ +namespace BLS12_381 = + type fr = MCL_BLS12_381.fr + type fp = MCL_BLS12_381.fp + record fp2 = { x1 : fp, x2 : fp } + record g1 = { x : fp, y : fp, z : fp } + record g2 = { x : fp2, y : fp2, z : fp2 } + record gt = { x1 : fp, x2 : fp, x3 : fp, x4 : fp, x5 : fp, x6 : fp, + x7 : fp, x8 : fp, x9 : fp, x10 : fp, x11 : fp, x12 : fp } + + function pairing_check(xs : list(g1), ys : list(g2)) = + switch((xs, ys)) + ([], []) => true + (x :: xs, y :: ys) => pairing_check_(pairing(x, y), xs, ys) + + function pairing_check_(acc : gt, xs : list(g1), ys : list(g2)) = + switch((xs, ys)) + ([], []) => gt_is_one(acc) + (x :: xs, y :: ys) => + pairing_check_(gt_mul(acc, pairing(x, y)), xs, ys) + + function int_to_fr(x : int) = MCL_BLS12_381.int_to_fr(x) + function int_to_fp(x : int) = MCL_BLS12_381.int_to_fp(x) + function fr_to_int(x : fr) = MCL_BLS12_381.fr_to_int(x) + function fp_to_int(x : fp) = MCL_BLS12_381.fp_to_int(x) + + function mk_g1(x : int, y : int, z : int) : g1 = + { x = int_to_fp(x), y = int_to_fp(y), z = int_to_fp(z) } + + function mk_g2(x1 : int, x2 : int, y1 : int, y2 : int, z1 : int, z2 : int) : g2 = + { x = {x1 = int_to_fp(x1), x2 = int_to_fp(x2)}, + y = {x1 = int_to_fp(y1), x2 = int_to_fp(y2)}, + z = {x1 = int_to_fp(z1), x2 = int_to_fp(z2)} } + + function pack_g1(t) = switch(t) + (x, y, z) => {x = x, y = y, z = z} : g1 + function pack_g2(t) = switch(t) + ((x1, x2), (y1, y2), (z1, z2)) => + {x = {x1 = x1, x2 = x2}, y = {x1 = y1, x2 = y2}, z = {x1 = z1, x2 = z2}} : g2 + function pack_gt(t) = switch(t) + (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12) => + {x1 = x1, x2 = x2, x3 = x3, x4 = x4, x5 = x5, x6 = x6, + x7 = x7, x8 = x8, x9 = x9, x10 = x10, x11 = x11, x12 = x12} : gt + + function g1_neg(p : g1) = pack_g1(MCL_BLS12_381.g1_neg((p.x, p.y, p.z))) + function g1_norm(p : g1) = pack_g1(MCL_BLS12_381.g1_norm((p.x, p.y, p.z))) + function g1_valid(p : g1) = MCL_BLS12_381.g1_valid((p.x, p.y, p.z)) + function g1_is_zero(p : g1) = MCL_BLS12_381.g1_is_zero((p.x, p.y, p.z)) + function g1_add(p : g1, q : g1) = pack_g1(MCL_BLS12_381.g1_add((p.x, p.y, p.z), (q.x, q.y, q.z))) + function g1_mul(k : fr, p : g1) = pack_g1(MCL_BLS12_381.g1_mul(k, (p.x, p.y, p.z))) + + function g2_neg(p : g2) = pack_g2(MCL_BLS12_381.g2_neg(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2)))) + function g2_norm(p : g2) = pack_g2(MCL_BLS12_381.g2_norm(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2)))) + function g2_valid(p : g2) = MCL_BLS12_381.g2_valid(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2))) + function g2_is_zero(p : g2) = MCL_BLS12_381.g2_is_zero(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2))) + function g2_add(p : g2, q : g2) = pack_g2(MCL_BLS12_381.g2_add(((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2)), + ((q.x.x1, q.x.x2), (q.y.x1, q.y.x2), (q.z.x1, q.z.x2)))) + function g2_mul(k : fr, p : g2) = pack_g2(MCL_BLS12_381.g2_mul(k, ((p.x.x1, p.x.x2), (p.y.x1, p.y.x2), (p.z.x1, p.z.x2)))) + + function gt_inv(p : gt) = pack_gt(MCL_BLS12_381.gt_inv((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12))) + function gt_add(p : gt, q : gt) = pack_gt(MCL_BLS12_381.gt_add((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12), + (q.x1, q.x2, q.x3, q.x4, q.x5, q.x6, q.x7, q.x8, q.x9, q.x10, q.x11, q.x12))) + function gt_mul(p : gt, q : gt) = pack_gt(MCL_BLS12_381.gt_mul((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12), + (q.x1, q.x2, q.x3, q.x4, q.x5, q.x6, q.x7, q.x8, q.x9, q.x10, q.x11, q.x12))) + function gt_pow(p : gt, k : fr) = pack_gt(MCL_BLS12_381.gt_pow((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12), k)) + function gt_is_one(p : gt) = MCL_BLS12_381.gt_is_one((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12)) + function pairing(p : g1, q : g2) = pack_gt(MCL_BLS12_381.pairing((p.x, p.y, p.z), ((q.x.x1, q.x.x2), (q.y.x1, q.y.x2), (q.z.x1, q.z.x2)))) + function miller_loop(p : g1, q : g2) = pack_gt(MCL_BLS12_381.miller_loop((p.x, p.y, p.z), ((q.x.x1, q.x.x2), (q.y.x1, q.y.x2), (q.z.x1, q.z.x2)))) + function final_exp(p : gt) = pack_gt(MCL_BLS12_381.final_exp((p.x1, p.x2, p.x3, p.x4, p.x5, p.x6, p.x7, p.x8, p.x9, p.x10, p.x11, p.x12))) diff --git a/priv/stdlib/List.aes b/priv/stdlib/List.aes index 3a56856..5354014 100644 --- a/priv/stdlib/List.aes +++ b/priv/stdlib/List.aes @@ -19,6 +19,16 @@ namespace List = [x] => Some(x) _::t => last(t) + function drop_last(l : list('a)) : option(list('a)) = switch(l) + [] => None + _ => Some(drop_last_unsafe(l)) + + function drop_last_unsafe(l : list('a)) : list('a) = switch(l) + [_] => [] + h::t => h::drop_last_unsafe(t) + [] => abort("drop_last_unsafe: list empty") + + function contains(e : 'a, l : list('a)) = switch(l) [] => false h::t => h == e || contains(e, t) @@ -32,14 +42,15 @@ namespace List = /** 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, []) + 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) , n : int - , acc : list(int) ) : list(int) = switch(l) - [] => reverse(acc) - h::t => find_indices_(p, t, n+1, if(p(h)) n::acc else acc) + [] => [] + h::t => + let rest = find_indices_(p, t, n+1) + if(p(h)) n::rest else rest function nth(n : int, l : list('a)) : option('a) = switch(l) @@ -68,44 +79,44 @@ namespace List = * `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, s, []) - private function from_to_step_(a, b, s, acc) = - if (a > b) reverse(acc) else from_to_step_(a + s, b, s, a :: acc) + 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) = + if(b < a) acc + else from_to_step_(a, b - s, s, b::acc) /** 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), acc : 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) = switch(l) [] => abort("replace_at overflow") - h::t => if (n == 0) reverse(e::acc) ++ t - else replace_at_(n-1, e, t, h::acc) + 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 */ 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), acc : list('a)) : list('a) = - if (n == 0) reverse(e::acc) ++ l + 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) = + if (n == 0) e::l else switch(l) [] => abort("insert_at overflow") - h::t => insert_at_(n-1, e, t, h::acc) + 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) = - insert_by_(cmp, x, l, []) - private function insert_by_(cmp : (('a, 'a) => bool), x : 'a, l : list('a), acc : list('a)) : list('a) = switch(l) - [] => reverse(x::acc) + [] => [x] h::t => if(cmp(x, h)) // x < h - reverse(acc) ++ (x::l) + x::l else - insert_by_(cmp, x, t, h::acc) + h::insert_by(cmp, x, t) function foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b = switch(l) @@ -123,49 +134,52 @@ namespace List = f(e) foreach(l', f) - function reverse(l : list('a)) : list('a) = foldl((lst, el) => el :: lst, [], l) + function reverse(l : list('a)) : list('a) = reverse_(l, []) + private function reverse_(l : list('a), acc : list('a)) : list('a) = switch(l) + [] => acc + h::t => reverse_(t, h::acc) - function map(f : 'a => 'b, l : list('a)) : list('b) = map_(f, l, []) - private function map_(f : 'a => 'b, l : list('a), acc : list('b)) : list('b) = switch(l) - [] => reverse(acc) - h::t => map_(f, t, f(h)::acc) + function map(f : 'a => 'b, l : list('a)) : list('b) = switch(l) + [] => [] + 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) - function filter(p : 'a => bool, l : list('a)) : list('a) = filter_(p, l, []) - private function filter_(p : 'a => bool, l : list('a), acc : list('a)) : list('a) = switch(l) - [] => reverse(acc) - h::t => filter_(p, t, if(p(h)) h::acc else acc) + function filter(p : 'a => bool, l : list('a)) : list('a) = switch(l) + [] => [] + h::t => + let rest = filter(p, t) + if(p(h)) h::rest else rest -/** Take `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), acc : list('a)) : list('a) = - if(n == 0) reverse(acc) + if(n < 0) abort("Take negative number of elements") else take_(n, l) + private function take_(n : int, l : list('a)) : list('a) = + if(n == 0) [] else switch(l) - [] => reverse(acc) - h::t => take_(n-1, t, h::acc) + [] => [] + h::t => h::take_(n-1, t) -/** Drop `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") - elif (n == 0) l + if(n < 0) abort("Drop negative number of elements") else drop_(n, l) + private function drop_(n : int, l : list('a)) : list('a) = + if (n == 0) l else switch(l) [] => [] - h::t => drop(n-1, t) + h::t => drop_(n-1, t) /** 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) = take_while_(p, l, []) - private function take_while_(p : 'a => bool, l : list('a), acc : list('a)) : list('a) = switch(l) - [] => reverse(acc) - h::t => if(p(h)) take_while_(p, t, h::acc) else reverse(acc) + 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 */ @@ -176,18 +190,15 @@ namespace List = /** 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)) = partition_(p, l, [], []) - private function partition_( p : 'a => bool - , l : list('a) - , acc_t : list('a) - , acc_f : list('a) - ) : (list('a) * list('a)) = switch(l) - [] => (reverse(acc_t), reverse(acc_f)) - h::t => if(p(h)) partition_(p, t, h::acc_t, acc_f) else partition_(p, t, acc_t, h::acc_f) + function partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) = switch(l) + [] => ([], []) + h::t => + let (l, r) = partition(p, t) + if(p(h)) (h::l, r) else (l, h::r) -/** Flattens list of lists into a single list - */ - function flatten(ll : list(list('a))) : list('a) = foldr((l1, l2) => l1 ++ l2, [], ll) + function flatten(l : list(list('a))) : list('a) = switch(l) + [] => [] + h::t => h ++ flatten(t) function all(p : 'a => bool, l : list('a)) : bool = switch(l) [] => true @@ -203,28 +214,25 @@ namespace List = /** Zips two list by applying bimapping function on respective elements. - * Drops longer tail. + * Drops the tail of the longer list. */ - function zip_with(f : ('a, 'b) => 'c, l1 : list('a), l2 : list('b)) : list('c) = zip_with_(f, l1, l2, []) - private function zip_with_( f : ('a, 'b) => 'c + private function zip_with( f : ('a, 'b) => 'c , l1 : list('a) , l2 : list('b) - , acc : list('c) ) : list('c) = switch ((l1, l2)) - (h1::t1, h2::t2) => zip_with_(f, t1, t2, f(h1, h2)::acc) - _ => reverse(acc) + (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) = unzip_(l, [], []) - private function unzip_( l : list('a * 'b) - , acc_l : list('a) - , acc_r : list('b) - ) : (list('a) * list('b)) = switch(l) - [] => (reverse(acc_l), reverse(acc_r)) - (left, right)::t => unzip_(t, left::acc_l, right::acc_r) + function unzip(l : list('a * 'b)) : (list('a) * list('b)) = switch(l) + [] => ([], []) + (h1, h2)::t => + let (t1, t2) = unzip(t) + (h1::t1, h2::t2) // TODO: Improve? @@ -235,17 +243,14 @@ namespace List = /** Puts `delim` between every two members of the list */ - function intersperse(delim : 'a, l : list('a)) : list('a) = intersperse_(delim, l, []) - private function intersperse_(delim : 'a, l : list('a), acc : list('a)) : list('a) = switch(l) - [] => reverse(acc) - [e] => reverse(e::acc) - h::t => intersperse_(delim, t, delim::h::acc) - + 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, acc : list(int * 'a)) : list(int * 'a) = switch(l) - [] => reverse(acc) - h::t => enumerate_(t, n + 1, (n, h)::acc) - + 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) \ No newline at end of file diff --git a/priv/stdlib/Option.aes b/priv/stdlib/Option.aes index e364363..0e00cd4 100644 --- a/priv/stdlib/Option.aes +++ b/priv/stdlib/Option.aes @@ -69,20 +69,21 @@ namespace Option = /** Turns list of options into a list of elements that are under `Some`s. * Safe. */ - function filter_options(l : list(option('a))) : list('a) = filter_options_(l, []) - private function filter_options_(l : list (option('a)), acc : list('a)) : list('a) = switch(l) - [] => List.reverse(acc) - None::t => filter_options_(t, acc) - Some(x)::t => filter_options_(t, x::acc) + 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)) = seq_options_(l, []) - private function seq_options_(l : list (option('a)), acc : list('a)) : option(list('a)) = switch(l) - [] => Some(List.reverse(acc)) - None::t => None - Some(x)::t => seq_options_(t, x::acc) + function seq_options(l : list (option('a))) : option (list('a)) = switch(l) + [] => Some([]) + None::_ => None + Some(x)::t => switch(seq_options(t)) + None => None + Some(st) => Some(x::st) + /** Choose `Some` out of two if possible */ @@ -95,4 +96,3 @@ namespace Option = [] => None None::t => choose_first(t) Some(x)::_ => Some(x) - diff --git a/priv/stdlib/String.aes b/priv/stdlib/String.aes new file mode 100644 index 0000000..36c54ba --- /dev/null +++ b/priv/stdlib/String.aes @@ -0,0 +1,117 @@ +include "List.aes" +namespace String = + // Computes the SHA3/Keccak hash of the string + function sha3(s : string) : hash = StringInternal.sha3(s) + // Computes the SHA256 hash of the string. + function sha256(s : string) : hash = StringInternal.sha256(s) + // Computes the Blake2B hash of the string. + function blake2b(s : string) : hash = StringInternal.blake2b(s) + + // The length of a string - equivalent to List.lenght(to_list(s)) + function length(s : string) : int = StringInternal.length(s) + // Concatenates `s1` and `s2`. + function concat(s1 : string, s2 : string) : string = StringInternal.concat(s1, s2) + // Concatenates a list of strings. + function + concats : (list(string)) => string + concats([]) = "" + concats(s :: ss) = List.foldl(StringInternal.concat, s, ss) + + // Converts a `string` to a list of `char` - the code points are normalized, but + // composite characters are possibly converted to multiple `char`s. + function from_list(cs : list(char)) : string = StringInternal.from_list(cs) + // Converts a list of characters into a normalized UTF-8 string. + function to_list(s : string) : list(char) = StringInternal.to_list(s) + + // Converts a string to lowercase. + function to_lower(s : string) = StringInternal.to_lower(s) + // Converts a string to uppercase. + function to_upper(s : string) = StringInternal.to_upper(s) + + // Splits a string at (zero-based) index `ix`. + function split(i : int, s : string) : string * string = + let cs = StringInternal.to_list(s) + (StringInternal.from_list(List.take(i, cs)), StringInternal.from_list(List.drop(i, cs))) + + // Returns the character/codepoint at (zero-based) index `ix`. + function at(ix : int, s : string) = + switch(List.drop(ix, StringInternal.to_list(s))) + [] => None + x :: _ => Some(x) + + // Searches for `pat` in `str`, returning `Some(ix)` if `pat` is a substring + // of `str` starting at position `ix`, otherwise returns `None`. + function contains(str : string, substr : string) : option(int) = + if(substr == "") Some(0) + else + contains_(0, StringInternal.to_list(str), StringInternal.to_list(substr)) + + // Splits `s` into tokens, `pat` is the divider of tokens. + function tokens(s : string, pat : string) = + require(pat != "", "String.tokens: empty pattern") + tokens_(StringInternal.to_list(pat), StringInternal.to_list(s), []) + + // 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. + function to_int(s : string) : option(int) = + let s = StringInternal.to_list(s) + switch(is_prefix(['-'], s)) + None => to_int_pos(s) + Some(s) => switch(to_int_pos(s)) + None => None + Some(x) => Some(-x) + + // Private helper functions below + private function to_int_pos(s : list(char)) = + switch(is_prefix(['0', 'x'], s)) + None => + to_int_(s, ch_to_int_10, 0, 10) + Some(s) => + to_int_(s, ch_to_int_16, 0, 16) + + private function + tokens_(_, [], acc) = [StringInternal.from_list(List.reverse(acc))] + tokens_(pat, str, acc) = + switch(is_prefix(pat, str)) + Some(str') => + StringInternal.from_list(List.reverse(acc)) :: tokens_(pat, str', []) + None => + let c :: cs = str + tokens_(pat, cs, c :: acc) + + private function + contains_(_, [], _) = None + contains_(ix, str, substr) = + switch(is_prefix(substr, str)) + None => + let _ :: str = str + contains_(ix + 1, str, substr) + Some(_) => + Some(ix) + + private function + is_prefix([], ys) = Some(ys) + is_prefix(_, []) = None + is_prefix(x :: xs, y :: ys) = + if(x == y) is_prefix(xs, ys) + else None + + private function + to_int_([], _, x, _) = Some(x) + to_int_(i :: is, value, x, b) = + switch(value(i)) + None => None + Some(i) => to_int_(is, value, x * b + i, b) + + private function ch_to_int_10(c) = + let c = Char.to_int(c) + if(c >= 48 && c =< 57) Some(c - 48) + else None + + private function ch_to_int_16(c) = + let c = Char.to_int(c) + if(c >= 48 && c =< 57) Some(c - 48) + elif(c >= 65 && c =< 70) Some(c - 55) + elif(c >= 97 && c =< 102) Some(c - 87) + else None + diff --git a/rebar.config b/rebar.config index 160f89e..4bc4501 100644 --- a/rebar.config +++ b/rebar.config @@ -2,7 +2,7 @@ {erl_opts, [debug_info]}. -{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"4f4d6d3"}}} +{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"7f0d309"}}} , {getopt, "1.0.1"} , {eblake2, "1.0.0"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", diff --git a/rebar.lock b/rebar.lock index f5cf812..54e0e24 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.1.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"4f4d6d30cd2c46b3830454d650a424d513f69134"}}, + {ref,"7f0d3090d4dc6c4d5fca7645b0c21eb0e65ad208"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index f420f02..fa1fee5 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -24,6 +24,7 @@ | aeso_syntax:id() | aeso_syntax:qid() | aeso_syntax:con() | aeso_syntax:qcon() %% contracts | aeso_syntax:tvar() + | {if_t, aeso_syntax:ann(), aeso_syntax:id(), utype(), utype()} %% Can branch on named argument (protected) | uvar(). -type uvar() :: {uvar, aeso_syntax:ann(), reference()}. @@ -47,7 +48,14 @@ name :: aeso_syntax:id(), type :: utype()}). --type named_argument_constraint() :: #named_argument_constraint{}. +-record(dependent_type_constraint, + { named_args_t :: named_args_t() + , named_args :: [aeso_syntax:arg_expr()] + , general_type :: utype() + , specialized_type :: utype() + , context :: term() }). + +-type named_argument_constraint() :: #named_argument_constraint{} | #dependent_type_constraint{}. -record(field_constraint, { record_t :: utype() @@ -236,16 +244,21 @@ bind_fields([], Env) -> Env; bind_fields([{Id, Info} | Rest], Env) -> bind_fields(Rest, bind_field(Id, Info, Env)). -%% Contract entrypoints take two named arguments (gas : int = Call.gas_left(), value : int = 0). +%% Contract entrypoints take three named arguments +%% gas : int = Call.gas_left() +%% value : int = 0 +%% protected : bool = false contract_call_type({fun_t, Ann, [], Args, Ret}) -> Id = fun(X) -> {id, Ann, X} end, Int = Id("int"), Typed = fun(E, T) -> {typed, Ann, E, T} end, - Named = fun(Name, Default) -> {named_arg_t, Ann, Id(Name), Int, Default} end, + Named = fun(Name, Default = {typed, _, _, T}) -> {named_arg_t, Ann, Id(Name), T, Default} end, {fun_t, Ann, [Named("gas", Typed({app, Ann, Typed({qid, Ann, ["Call", "gas_left"]}, {fun_t, Ann, [], [], Int}), []}, Int)), - Named("value", Typed({int, Ann, 0}, Int))], Args, Ret}. + Named("value", Typed({int, Ann, 0}, Int)), + Named("protected", Typed({bool, Ann, false}, Id("bool")))], + Args, {if_t, Ann, Id("protected"), {app_t, Ann, {id, Ann, "option"}, [Ret]}, Ret}}. -spec bind_contract(aeso_syntax:decl(), env()) -> env(). bind_contract({contract, Ann, Id, Contents}, Env) -> @@ -368,6 +381,7 @@ is_private(Ann) -> proplists:get_value(private, Ann, false). global_env() -> Ann = [{origin, system}], Int = {id, Ann, "int"}, + Char = {id, Ann, "char"}, Bool = {id, Ann, "bool"}, String = {id, Ann, "string"}, Address = {id, Ann, "address"}, @@ -393,6 +407,24 @@ global_env() -> Signature = {named_arg_t, Ann, SignId, SignId, {typed, Ann, SignDef, SignId}}, SignFun = fun(Ts, T) -> {type_sig, [stateful|Ann], none, [Signature], Ts, T} end, TTL = {qid, Ann, ["Chain", "ttl"]}, + Pointee = {qid, Ann, ["AENS", "pointee"]}, + AENSName = {qid, Ann, ["AENS", "name"]}, + Fr = {qid, Ann, ["MCL_BLS12_381", "fr"]}, + Fp = {qid, Ann, ["MCL_BLS12_381", "fp"]}, + Fp2 = {tuple_t, Ann, [Fp, Fp]}, + G1 = {tuple_t, Ann, [Fp, Fp, Fp]}, + G2 = {tuple_t, Ann, [Fp2, Fp2, Fp2]}, + GT = {tuple_t, Ann, lists:duplicate(12, Fp)}, + Tx = {qid, Ann, ["Chain", "tx"]}, + GAMetaTx = {qid, Ann, ["Chain", "ga_meta_tx"]}, + BaseTx = {qid, Ann, ["Chain", "base_tx"]}, + PayForTx = {qid, Ann, ["Chain", "paying_for_tx"]}, + + FldT = fun(Id, T) -> {field_t, Ann, {id, Ann, Id}, T} end, + TxFlds = [{"paying_for", Option(PayForTx)}, {"ga_metas", List(GAMetaTx)}, + {"actor", Address}, {"fee", Int}, {"ttl", Int}, {"tx", BaseTx}], + TxType = {record_t, [FldT(N, T) || {N, T} <- TxFlds ]}, + Fee = Int, [A, Q, R, K, V] = lists:map(TVar, ["a", "q", "r", "k", "v"]), @@ -430,8 +462,36 @@ global_env() -> {"timestamp", Int}, {"block_height", Int}, {"difficulty", Int}, - {"gas_limit", Int}]) - , types = MkDefs([{"ttl", 0}]) }, + {"gas_limit", Int}, + %% Tx constructors + {"GAMetaTx", Fun([Address, Int], GAMetaTx)}, + {"PayingForTx", Fun([Address, Int], PayForTx)}, + {"SpendTx", Fun([Address, Int, String], BaseTx)}, + {"OracleRegisterTx", BaseTx}, + {"OracleQueryTx", BaseTx}, + {"OracleResponseTx", BaseTx}, + {"OracleExtendTx", BaseTx}, + {"NamePreclaimTx", BaseTx}, + {"NameClaimTx", Fun([String], BaseTx)}, + {"NameUpdateTx", Fun([Hash], BaseTx)}, + {"NameRevokeTx", Fun([Hash], BaseTx)}, + {"NameTransferTx", Fun([Address, Hash], BaseTx)}, + {"ChannelCreateTx", Fun([Address], BaseTx)}, + {"ChannelDepositTx", Fun([Address, Int], BaseTx)}, + {"ChannelWithdrawTx", Fun([Address, Int], BaseTx)}, + {"ChannelForceProgressTx", Fun([Address], BaseTx)}, + {"ChannelCloseMutualTx", Fun([Address], BaseTx)}, + {"ChannelCloseSoloTx", Fun([Address], BaseTx)}, + {"ChannelSlashTx", Fun([Address], BaseTx)}, + {"ChannelSettleTx", Fun([Address], BaseTx)}, + {"ChannelSnapshotSoloTx", Fun([Address], BaseTx)}, + {"ContractCreateTx", Fun([Int], BaseTx)}, + {"ContractCallTx", Fun([Address, Int], BaseTx)}, + {"GAAttachTx", BaseTx} + ]) + , types = MkDefs([{"ttl", 0}, {"tx", {[], TxType}}, + {"base_tx", 0}, + {"paying_for_tx", 0}, {"ga_meta_tx", 0}]) }, ContractScope = #scope { funs = MkDefs( @@ -451,6 +511,7 @@ global_env() -> OracleScope = #scope { funs = MkDefs( [{"register", SignFun([Address, Fee, TTL], Oracle(Q, R))}, + {"expiry", Fun([Oracle(Q, R)], Fee)}, {"query_fee", Fun([Oracle(Q, R)], Fee)}, {"query", StateFun([Oracle(Q, R), Q, Fee, TTL, TTL], Query(Q, R))}, {"get_question", Fun([Oracle(Q, R), Query(Q, R)], Q)}, @@ -466,7 +527,18 @@ global_env() -> {"preclaim", SignFun([Address, Hash], Unit)}, {"claim", SignFun([Address, String, Int, Int], Unit)}, {"transfer", SignFun([Address, Address, String], Unit)}, - {"revoke", SignFun([Address, String], Unit)}]) }, + {"revoke", SignFun([Address, String], Unit)}, + {"update", SignFun([Address, String, Option(TTL), Option(Int), Option(Map(String, Pointee))], Unit)}, + {"lookup", Fun([String], option_t(Ann, AENSName))}, + %% AENS pointee constructors + {"AccountPt", Fun1(Address, Pointee)}, + {"OraclePt", Fun1(Address, Pointee)}, + {"ContractPt", Fun1(Address, Pointee)}, + {"ChannelPt", Fun1(Address, Pointee)}, + %% Name object constructor + {"Name", Fun([Address, TTL, Map(String, Pointee)], AENSName)} + ]) + , types = MkDefs([{"pointee", 0}, {"name", 0}]) }, MapScope = #scope { funs = MkDefs( @@ -489,19 +561,65 @@ global_env() -> {"sha256", Fun1(A, Hash)}, {"blake2b", Fun1(A, Hash)}]) }, + %% Fancy BLS12-381 crypto operations + MCL_BLS12_381_Scope = #scope + { funs = MkDefs( + [{"g1_neg", Fun1(G1, G1)}, + {"g1_norm", Fun1(G1, G1)}, + {"g1_valid", Fun1(G1, Bool)}, + {"g1_is_zero", Fun1(G1, Bool)}, + {"g1_add", Fun ([G1, G1], G1)}, + {"g1_mul", Fun ([Fr, G1], G1)}, + + {"g2_neg", Fun1(G2, G2)}, + {"g2_norm", Fun1(G2, G2)}, + {"g2_valid", Fun1(G2, Bool)}, + {"g2_is_zero", Fun1(G2, Bool)}, + {"g2_add", Fun ([G2, G2], G2)}, + {"g2_mul", Fun ([Fr, G2], G2)}, + + {"gt_inv", Fun1(GT, GT)}, + {"gt_add", Fun ([GT, GT], GT)}, + {"gt_mul", Fun ([GT, GT], GT)}, + {"gt_pow", Fun ([GT, Fr], GT)}, + {"gt_is_one", Fun1(GT, Bool)}, + {"pairing", Fun ([G1, G2], GT)}, + {"miller_loop", Fun ([G1, G2], GT)}, + {"final_exp", Fun1(GT, GT)}, + + {"int_to_fr", Fun1(Int, Fr)}, + {"int_to_fp", Fun1(Int, Fp)}, + {"fr_to_int", Fun1(Fr, Int)}, + {"fp_to_int", Fun1(Fp, Int)} + ]), + types = MkDefs( + [{"fr", 0}, {"fp", 0}]) }, + %% Authentication AuthScope = #scope { funs = MkDefs( - [{"tx_hash", Option(Hash)}]) }, + [{"tx_hash", Option(Hash)}, + {"tx", Option(Tx)} ]) }, %% Strings StringScope = #scope { funs = MkDefs( - [{"length", Fun1(String, Int)}, - {"concat", Fun([String, String], String)}, - {"sha3", Fun1(String, Hash)}, - {"sha256", Fun1(String, Hash)}, - {"blake2b", Fun1(String, Hash)}]) }, + [{"length", Fun1(String, Int)}, + {"concat", Fun([String, String], String)}, + {"to_list", Fun1(String, List(Char))}, + {"from_list", Fun1(List(Char), String)}, + {"to_upper", Fun1(String, String)}, + {"to_lower", Fun1(String, String)}, + {"sha3", Fun1(String, Hash)}, + {"sha256", Fun1(String, Hash)}, + {"blake2b", Fun1(String, Hash)} + ]) }, + + %% Chars + CharScope = #scope + { funs = MkDefs( + [{"to_int", Fun1(Char, Int)}, + {"from_int", Fun1(Int, Option(Char))}]) }, %% Bits BitsScope = #scope @@ -533,6 +651,7 @@ global_env() -> {"is_contract", Fun1(Address, Bool)}, {"is_payable", Fun1(Address, Bool)}]) }, + #env{ scopes = #{ [] => TopScope , ["Chain"] => ChainScope @@ -543,12 +662,19 @@ global_env() -> , ["Map"] => MapScope , ["Auth"] => AuthScope , ["Crypto"] => CryptoScope - , ["String"] => StringScope + , ["MCL_BLS12_381"] => MCL_BLS12_381_Scope + , ["StringInternal"] => StringScope + , ["Char"] => CharScope , ["Bits"] => BitsScope , ["Bytes"] => BytesScope , ["Int"] => IntScope , ["Address"] => AddressScope - } }. + } + , fields = + maps:from_list([{N, [#field_info{ ann = [], field_t = T, record_t = Tx, kind = record }]} + || {N, T} <- TxFlds ]) + }. + option_t(As, T) -> {app_t, As, {id, As, "option"}, [T]}. map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}. @@ -706,7 +832,12 @@ expose_internals(Defs, What) -> main_contract -> [{entrypoint, true}|Ann]; % minor duplication contract -> Ann end, - setelement(2, Def, NewAnn) + Def1 = setelement(2, Def, NewAnn), + case Def1 of % fix inner clauses + {fun_clauses, Ans, Id, T, Clauses} -> + {fun_clauses, Ans, Id, T, expose_internals(Clauses, What)}; + _ -> Def1 + end end || Def <- Defs ]. @@ -1274,7 +1405,7 @@ infer_expr(Env, {typed, As, Body, Type}) -> Type1 = check_type(Env, Type), {typed, _, NewBody, NewType} = check_expr(Env, Body, Type1), {typed, As, NewBody, NewType}; -infer_expr(Env, {app, Ann, Fun, Args0}) -> +infer_expr(Env, {app, Ann, Fun, Args0} = App) -> NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ], Args = Args0 -- NamedArgs, case aeso_syntax:get_ann(format, Ann) of @@ -1289,8 +1420,16 @@ infer_expr(Env, {app, Ann, Fun, Args0}) -> NewFun={typed, _, _, FunType} = infer_expr(Env, Fun), NewArgs = [infer_expr(Env, A) || A <- Args], ArgTypes = [T || {typed, _, _, T} <- NewArgs], + GeneralResultType = fresh_uvar(Ann), ResultType = fresh_uvar(Ann), - unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, ResultType}, {infer_app, Fun, Args, FunType, ArgTypes}), + When = {infer_app, Fun, NamedArgs1, Args, FunType, ArgTypes}, + unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, GeneralResultType}, When), + add_named_argument_constraint( + #dependent_type_constraint{ named_args_t = NamedArgsVar, + named_args = NamedArgs1, + general_type = GeneralResultType, + specialized_type = ResultType, + context = {check_return, App} }), {typed, Ann, {app, Ann, NewFun, NamedArgs1 ++ NewArgs}, dereference(ResultType)} end; infer_expr(Env, {'if', Attrs, Cond, Then, Else}) -> @@ -1449,7 +1588,7 @@ infer_op(Env, As, Op, Args, InferOp) -> TypedArgs = [infer_expr(Env, A) || A <- Args], ArgTypes = [T || {typed, _, _, T} <- TypedArgs], Inferred = {fun_t, _, _, OperandTypes, ResultType} = InferOp(Op), - unify(Env, ArgTypes, OperandTypes, {infer_app, Op, Args, Inferred, ArgTypes}), + unify(Env, ArgTypes, OperandTypes, {infer_app, Op, [], Args, Inferred, ArgTypes}), {typed, As, {app, As, Op, TypedArgs}, ResultType}. infer_pattern(Env, Pattern) -> @@ -1620,12 +1759,12 @@ create_constraints() -> create_field_constraints(). destroy_and_report_unsolved_constraints(Env) -> + solve_field_constraints(Env), solve_named_argument_constraints(Env), solve_bytes_constraints(Env), - solve_field_constraints(Env), - destroy_and_report_unsolved_field_constraints(Env), destroy_and_report_unsolved_bytes_constraints(Env), - destroy_and_report_unsolved_named_argument_constraints(Env). + destroy_and_report_unsolved_named_argument_constraints(Env), + destroy_and_report_unsolved_field_constraints(Env). %% -- Named argument constraints -- @@ -1665,8 +1804,43 @@ check_named_argument_constraint(Env, type_error({bad_named_argument, Args, Id}), false; [T] -> unify(Env, T, Type, {check_named_arg_constraint, C}), true + end; +check_named_argument_constraint(Env, + #dependent_type_constraint{ named_args_t = NamedArgsT0, + named_args = NamedArgs, + general_type = GenType, + specialized_type = SpecType, + context = {check_return, App} }) -> + NamedArgsT = dereference(NamedArgsT0), + case dereference(NamedArgsT0) of + [_ | _] = NamedArgsT -> + GetVal = fun(Name, Default) -> + hd([ Val || {named_arg, _, {id, _, N}, Val} <- NamedArgs, N == Name] ++ + [ Default ]) + end, + ArgEnv = maps:from_list([ {Name, GetVal(Name, Default)} + || {named_arg_t, _, {id, _, Name}, _, Default} <- NamedArgsT ]), + GenType1 = specialize_dependent_type(ArgEnv, GenType), + unify(Env, GenType1, SpecType, {check_expr, App, GenType1, SpecType}), + true; + _ -> unify(Env, GenType, SpecType, {check_expr, App, GenType, SpecType}), true end. +specialize_dependent_type(Env, Type) -> + case dereference(Type) of + {if_t, _, {id, _, Arg}, Then, Else} -> + Val = maps:get(Arg, Env), + case Val of + {typed, _, {bool, _, true}, _} -> Then; + {typed, _, {bool, _, false}, _} -> Else; + _ -> + type_error({named_argument_must_be_literal_bool, Arg, Val}), + fresh_uvar(aeso_syntax:get_ann(Val)) + end; + _ -> Type %% Currently no deep dependent types + end. + + destroy_and_report_unsolved_named_argument_constraints(Env) -> Unsolved = solve_named_argument_constraints(Env, get_named_argument_constraints()), [ type_error({unsolved_named_argument_constraint, C}) || C <- Unsolved ], @@ -1892,9 +2066,11 @@ destroy_and_report_unsolved_field_constraints(Env) -> {FieldCs, OtherCs} = lists:partition(fun(#field_constraint{}) -> true; (_) -> false end, get_field_constraints()), - {CreateCs, ContractCs} = + {CreateCs, OtherCs1} = lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end, OtherCs), + {ContractCs, []} = + lists:partition(fun(#is_contract_constraint{}) -> true; (_) -> false end, OtherCs1), Unknown = solve_known_record_types(Env, FieldCs), if Unknown == [] -> ok; true -> @@ -1968,7 +2144,8 @@ unfold_record_types(Env, T) -> unfold_types(Env, T, [unfold_record_types]). unfold_types(Env, {typed, Attr, E, Type}, Options) -> - {typed, Attr, unfold_types(Env, E, Options), unfold_types_in_type(Env, Type, Options)}; + Options1 = [{ann, Attr} | lists:keydelete(ann, 1, Options)], + {typed, Attr, unfold_types(Env, E, Options), unfold_types_in_type(Env, Type, Options1)}; unfold_types(Env, {arg, Attr, Id, Type}, Options) -> {arg, Attr, Id, unfold_types_in_type(Env, Type, Options)}; unfold_types(Env, {type_sig, Ann, Constr, NamedArgs, Args, Ret}, Options) -> @@ -1994,7 +2171,8 @@ unfold_types_in_type(Env, T) -> unfold_types_in_type(Env, {app_t, Ann, Id = {id, _, "map"}, Args = [KeyType0, _]}, Options) -> Args1 = [KeyType, _] = unfold_types_in_type(Env, Args, Options), - [ type_error({map_in_map_key, KeyType0}) || has_maps(KeyType) ], + Ann1 = proplists:get_value(ann, Options, aeso_syntax:get_ann(KeyType0)), + [ type_error({map_in_map_key, Ann1, KeyType0}) || has_maps(KeyType) ], {app_t, Ann, Id, Args1}; unfold_types_in_type(Env, {app_t, Ann, Id, Args}, Options) when ?is_type_id(Id) -> UnfoldRecords = proplists:get_value(unfold_record_types, Options, false), @@ -2068,8 +2246,13 @@ subst_tvars1(_Env, X) -> unify(_, {id, _, "_"}, _, _When) -> true; unify(_, _, {id, _, "_"}, _When) -> true; unify(Env, A, B, When) -> - A1 = dereference(unfold_types_in_type(Env, A)), - B1 = dereference(unfold_types_in_type(Env, B)), + Options = + case When of %% Improve source location for map_in_map_key errors + {check_expr, E, _, _} -> [{ann, aeso_syntax:get_ann(E)}]; + _ -> [] + end, + A1 = dereference(unfold_types_in_type(Env, A, Options)), + B1 = dereference(unfold_types_in_type(Env, B, Options)), unify1(Env, A1, B1, When). unify1(_Env, {uvar, _, R}, {uvar, _, R}, _When) -> @@ -2100,6 +2283,9 @@ unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) -> true; unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) -> true; +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) when length(Args1) == length(Args2) -> unify(Env, Named1, Named2, When) andalso @@ -2161,6 +2347,8 @@ occurs_check1(R, {record_t, Fields}) -> occurs_check(R, Fields); occurs_check1(R, {field_t, _, _, T}) -> occurs_check(R, T); +occurs_check1(R, {if_t, _, _, Then, Else}) -> + occurs_check(R, [Then, Else]); occurs_check1(R, [H | T]) -> occurs_check(R, H) orelse occurs_check(R, T); occurs_check1(_, []) -> false. @@ -2528,10 +2716,10 @@ mk_error({new_tuple_syntax, Ann, Ts}) -> Msg = io_lib:format("Invalid type\n~s (at ~s)\nThe syntax of tuple types changed in Sophia version 4.0. Did you mean\n~s\n", [pp_type(" ", {args_t, Ann, Ts}), pp_loc(Ann), pp_type(" ", {tuple_t, Ann, Ts})]), mk_t_err(pos(Ann), Msg); -mk_error({map_in_map_key, KeyType}) -> +mk_error({map_in_map_key, Ann, KeyType}) -> Msg = io_lib:format("Invalid key type\n~s\n", [pp_type(" ", KeyType)]), Cxt = "Map keys cannot contain other maps.\n", - mk_t_err(pos(KeyType), Msg, Cxt); + mk_t_err(pos(Ann), Msg, Cxt); mk_error({cannot_call_init_function, Ann}) -> Msg = "The 'init' function is called exclusively by the create contract transaction\n" "and cannot be called from the contract code.\n", @@ -2581,6 +2769,9 @@ mk_error({mixed_record_and_map, Expr}) -> Msg = io_lib:format("Mixed record fields and map keys in\n~s", [pp_expr(" ", Expr)]), mk_t_err(pos(Expr), Msg); +mk_error({named_argument_must_be_literal_bool, Name, Arg}) -> + Msg = io_lib:format("Invalid '~s' argument\n~s\nIt must be either 'true' or 'false'.", [Name, pp_expr(" ", instantiate(Arg))]), + mk_t_err(pos(Arg), Msg); mk_error(Err) -> Msg = io_lib:format("Unknown error: ~p\n", [Err]), mk_t_err(pos(0, 0), Msg). @@ -2599,7 +2790,7 @@ pp_when({check_typesig, Name, Inferred, Given}) -> " inferred type: ~s\n" " given type: ~s\n", [Name, pp_loc(Given), pp(instantiate(Inferred)), pp(instantiate(Given))])}; -pp_when({infer_app, Fun, Args, Inferred0, ArgTypes0}) -> +pp_when({infer_app, Fun, NamedArgs, Args, Inferred0, ArgTypes0}) -> Inferred = instantiate(Inferred0), ArgTypes = instantiate(ArgTypes0), {pos(Fun), @@ -2608,6 +2799,7 @@ pp_when({infer_app, Fun, Args, Inferred0, ArgTypes0}) -> "to arguments\n~s", [pp_loc(Fun), pp_typed(" ", Fun, Inferred), + [ [pp_expr(" ", NamedArg), "\n"] || NamedArg <- NamedArgs ] ++ [ [pp_typed(" ", Arg, ArgT), "\n"] || {Arg, ArgT} <- lists:zip(Args, ArgTypes) ] ])}; pp_when({field_constraint, FieldType0, InferredType0, Fld}) -> @@ -2684,6 +2876,12 @@ pp_when({list_comp, BindExpr, Inferred0, Expected0}) -> io_lib:format("when checking rvalue of list comprehension binding at ~s\n~s\n" "against type \n~s\n", [pp_loc(BindExpr), pp_typed(" ", BindExpr, Inferred), pp_type(" ", Expected)])}; +pp_when({check_named_arg_constraint, C}) -> + {id, _, Name} = Arg = C#named_argument_constraint.name, + [Type | _] = [ Type || {named_arg_t, _, {id, _, Name1}, Type, _} <- C#named_argument_constraint.args, Name1 == Name ], + Err = io_lib:format("when checking named argument\n~s\nagainst inferred type\n~s", + [pp_typed(" ", Arg, Type), pp_type(" ", C#named_argument_constraint.type)]), + {pos(Arg), Err}; pp_when(unknown) -> {pos(0,0), ""}. -spec pp_why_record(why_record()) -> {pos(), iolist()}. @@ -2761,6 +2959,8 @@ pp({uvar, _, Ref}) -> ["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ]; pp({tvar, _, Name}) -> Name; +pp({if_t, _, Id, Then, Else}) -> + ["if(", pp([Id, Then, Else]), ")"]; pp({tuple_t, _, []}) -> "unit"; pp({tuple_t, _, Cpts}) -> @@ -2772,8 +2972,8 @@ pp({app_t, _, T, []}) -> pp(T); pp({app_t, _, Type, Args}) -> [pp(Type), "(", pp(Args), ")"]; -pp({named_arg_t, _, Name, Type, Default}) -> - [pp(Name), " : ", pp(Type), " = ", pp(Default)]; +pp({named_arg_t, _, Name, Type, _Default}) -> + [pp(Name), " : ", pp(Type)]; pp({fun_t, _, Named = {uvar, _, _}, As, B}) -> ["(", pp(Named), " | ", pp(As), ") => ", pp(B)]; pp({fun_t, _, Named, As, B}) when is_list(Named) -> diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 50ea216..07f4e01 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -36,7 +36,14 @@ bits_intersection | bits_union | bits_difference | contract_to_address | address_to_contract | crypto_verify_sig | crypto_verify_sig_secp256k1 | crypto_sha3 | crypto_sha256 | crypto_blake2b | - crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1. + crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1 | + mcl_bls12_381_g1_neg | mcl_bls12_381_g1_norm | mcl_bls12_381_g1_valid | + mcl_bls12_381_g1_is_zero | mcl_bls12_381_g1_add | mcl_bls12_381_g1_mul | + mcl_bls12_381_g2_neg | mcl_bls12_381_g2_norm | mcl_bls12_381_g2_valid | + mcl_bls12_381_g2_is_zero | mcl_bls12_381_g2_add | mcl_bls12_381_g2_mul | + mcl_bls12_381_gt_inv | mcl_bls12_381_gt_add | mcl_bls12_381_gt_mul | mcl_bls12_381_gt_pow | + mcl_bls12_381_gt_is_one | mcl_bls12_381_pairing | mcl_bls12_381_miller_loop | mcl_bls12_381_final_exp | + mcl_bls12_381_int_to_fr | mcl_bls12_381_int_to_fp | mcl_bls12_381_fr_to_int | mcl_bls12_381_fp_to_int. -type flit() :: {int, integer()} | {string, binary()} @@ -171,13 +178,43 @@ ast_to_fcode(Code, Options) -> -spec init_env([option()]) -> env(). init_env(Options) -> + ChainTxArities = [3, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 0], #{ type_env => init_type_env(), fun_env => #{}, builtins => builtins(), con_env => #{["None"] => #con_tag{ tag = 0, arities = [0, 1] }, ["Some"] => #con_tag{ tag = 1, arities = [0, 1] }, ["RelativeTTL"] => #con_tag{ tag = 0, arities = [1, 1] }, - ["FixedTTL"] => #con_tag{ tag = 1, arities = [1, 1] } + ["FixedTTL"] => #con_tag{ tag = 1, arities = [1, 1] }, + ["AENS", "AccountPt"] => #con_tag{ tag = 0, arities = [1, 1, 1, 1] }, + ["AENS", "OraclePt"] => #con_tag{ tag = 1, arities = [1, 1, 1, 1] }, + ["AENS", "ContractPt"] => #con_tag{ tag = 2, arities = [1, 1, 1, 1] }, + ["AENS", "ChannelPt"] => #con_tag{ tag = 3, arities = [1, 1, 1, 1] }, + ["AENS", "Name"] => #con_tag{ tag = 0, arities = [3] }, + ["Chain", "GAMetaTx"] => #con_tag{ tag = 0, arities = [2] }, + ["Chain", "PayingForTx"] => #con_tag{ tag = 0, arities = [2] }, + ["Chain", "SpendTx"] => #con_tag{ tag = 0, arities = ChainTxArities }, + ["Chain", "OracleRegisterTx"] => #con_tag{ tag = 1, arities = ChainTxArities }, + ["Chain", "OracleQueryTx"] => #con_tag{ tag = 2, arities = ChainTxArities }, + ["Chain", "OracleResponseTx"] => #con_tag{ tag = 3, arities = ChainTxArities }, + ["Chain", "OracleExtendTx"] => #con_tag{ tag = 4, arities = ChainTxArities }, + ["Chain", "NamePreclaimTx"] => #con_tag{ tag = 5, arities = ChainTxArities }, + ["Chain", "NameClaimTx"] => #con_tag{ tag = 6, arities = ChainTxArities }, + ["Chain", "NameUpdateTx"] => #con_tag{ tag = 7, arities = ChainTxArities }, + ["Chain", "NameRevokeTx"] => #con_tag{ tag = 8, arities = ChainTxArities }, + ["Chain", "NameTransferTx"] => #con_tag{ tag = 9, arities = ChainTxArities }, + ["Chain", "ChannelCreateTx"] => #con_tag{ tag = 10, arities = ChainTxArities }, + ["Chain", "ChannelDepositTx"] => #con_tag{ tag = 11, arities = ChainTxArities }, + ["Chain", "ChannelWithdrawTx"] => #con_tag{ tag = 12, arities = ChainTxArities }, + ["Chain", "ChannelForceProgressTx"] => #con_tag{ tag = 13, arities = ChainTxArities }, + ["Chain", "ChannelCloseMutualTx"] => #con_tag{ tag = 14, arities = ChainTxArities }, + ["Chain", "ChannelCloseSoloTx"] => #con_tag{ tag = 15, arities = ChainTxArities }, + ["Chain", "ChannelSlashTx"] => #con_tag{ tag = 16, arities = ChainTxArities }, + ["Chain", "ChannelSettleTx"] => #con_tag{ tag = 17, arities = ChainTxArities }, + ["Chain", "ChannelSnapshotSoloTx"] => #con_tag{ tag = 18, arities = ChainTxArities }, + ["Chain", "ContractCreateTx"] => #con_tag{ tag = 19, arities = ChainTxArities }, + ["Chain", "ContractCallTx"] => #con_tag{ tag = 20, arities = ChainTxArities }, + ["Chain", "GAAttachTx"] => #con_tag{ tag = 21, arities = ChainTxArities } }, options => Options, functions => #{} }. @@ -194,18 +231,25 @@ builtins() -> {["Contract"], [{"address", none}, {"balance", none}, {"creator", none}]}, {["Call"], [{"origin", none}, {"caller", none}, {"value", none}, {"gas_price", none}, {"gas_left", 0}]}, - {["Oracle"], [{"register", 4}, {"query_fee", 1}, {"query", 5}, {"get_question", 2}, + {["Oracle"], [{"register", 4}, {"expiry", 1}, {"query_fee", 1}, {"query", 5}, {"get_question", 2}, {"respond", 4}, {"extend", 3}, {"get_answer", 2}, {"check", 1}, {"check_query", 2}]}, {["AENS"], [{"resolve", 2}, {"preclaim", 3}, {"claim", 5}, {"transfer", 4}, - {"revoke", 3}]}, + {"revoke", 3}, {"update", 6}, {"lookup", 1}]}, {["Map"], [{"from_list", 1}, {"to_list", 1}, {"lookup", 2}, {"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]}, {["Crypto"], [{"verify_sig", 3}, {"verify_sig_secp256k1", 3}, {"ecverify_secp256k1", 3}, {"ecrecover_secp256k1", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, - {["Auth"], [{"tx_hash", none}]}, - {["String"], [{"length", 1}, {"concat", 2}, {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, + {["MCL_BLS12_381"], [{"g1_neg", 1}, {"g1_norm", 1}, {"g1_valid", 1}, {"g1_is_zero", 1}, {"g1_add", 2}, {"g1_mul", 2}, + {"g2_neg", 1}, {"g2_norm", 1}, {"g2_valid", 1}, {"g2_is_zero", 1}, {"g2_add", 2}, {"g2_mul", 2}, + {"gt_inv", 1}, {"gt_add", 2}, {"gt_mul", 2}, {"gt_pow", 2}, {"gt_is_one", 1}, + {"pairing", 2}, {"miller_loop", 2}, {"final_exp", 1}, + {"int_to_fr", 1}, {"int_to_fp", 1}, {"fr_to_int", 1}, {"fp_to_int", 1}]}, + {["StringInternal"], [{"length", 1}, {"concat", 2}, {"to_list", 1}, {"from_list", 1}, + {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}, {"to_lower", 1}, {"to_upper", 1}]}, + {["Char"], [{"to_int", 1}, {"from_int", 1}]}, + {["Auth"], [{"tx_hash", none}, {"tx", none}]}, {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]}, @@ -224,20 +268,32 @@ state_layout(Env) -> maps:get(state_layout, Env, {reg, 1}). -spec init_type_env() -> type_env(). init_type_env() -> - #{ ["int"] => ?type(integer), - ["bool"] => ?type(boolean), - ["bits"] => ?type(bits), - ["char"] => ?type(integer), - ["string"] => ?type(string), - ["address"] => ?type(address), - ["hash"] => ?type(hash), - ["signature"] => ?type(signature), - ["oracle"] => ?type(Q, R, {oracle, Q, R}), - ["oracle_query"] => ?type(_, _, oracle_query), - ["list"] => ?type(T, {list, T}), - ["map"] => ?type(K, V, {map, K, V}), - ["option"] => ?type(T, {variant, [[], [T]]}), - ["Chain", "ttl"] => ?type({variant, [[integer], [integer]]}) + BaseTx = {variant, [[address, integer, string], [], [], [], [], [], [string], + [hash], [hash], [address, hash], [address], + [address, integer], [address, integer], [address], + [address], [address], [address], [address], [address], + [integer], [address, integer], []]}, + #{ ["int"] => ?type(integer), + ["bool"] => ?type(boolean), + ["bits"] => ?type(bits), + ["char"] => ?type(integer), + ["string"] => ?type(string), + ["address"] => ?type(address), + ["hash"] => ?type(hash), + ["signature"] => ?type(signature), + ["oracle"] => ?type(Q, R, {oracle, Q, R}), + ["oracle_query"] => ?type(_, _, oracle_query), %% TODO: not in Fate + ["list"] => ?type(T, {list, T}), + ["map"] => ?type(K, V, {map, K, V}), + ["option"] => ?type(T, {variant, [[], [T]]}), + ["Chain", "ttl"] => ?type({variant, [[integer], [integer]]}), + ["AENS", "pointee"] => ?type({variant, [[address], [address], [address], [address]]}), + ["AENS", "name"] => ?type({variant, [[address, {variant, [[integer], [integer]]}, {map, string, {variant, [[address], [address], [address], [address]]}}]]}), + ["Chain", "ga_meta_tx"] => ?type({variant, [[address, integer]]}), + ["Chain", "paying_for_tx"] => ?type({variant, [[address, integer]]}), + ["Chain", "base_tx"] => ?type(BaseTx), + ["MCL_BLS12_381", "fr"] => ?type({bytes, 32}), + ["MCL_BLS12_381", "fp"] => ?type({bytes, 48}) }. is_no_code(Env) -> @@ -411,6 +467,8 @@ type_to_fcode(Env, Sub, {fun_t, _, Named, Args, Res}) -> FNamed = [type_to_fcode(Env, Sub, Arg) || {named_arg_t, _, _, Arg, _} <- Named], FArgs = [type_to_fcode(Env, Sub, Arg) || Arg <- Args], {function, FNamed ++ FArgs, type_to_fcode(Env, Sub, Res)}; +type_to_fcode(Env, Sub, {if_t, _, _, _, Else}) -> + type_to_fcode(Env, Sub, Else); %% Hacky: this is only for remote calls, in which case we want the unprotected type type_to_fcode(_Env, _Sub, Type) -> error({todo, Type}). @@ -985,12 +1043,21 @@ stmts_to_fcode(Env, [Expr | Stmts]) -> op_builtins() -> [map_from_list, map_to_list, map_delete, map_member, map_size, - string_length, string_concat, string_sha3, string_sha256, string_blake2b, + stringinternal_length, stringinternal_concat, stringinternal_to_list, stringinternal_from_list, + stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b, + char_to_int, char_from_int, stringinternal_to_lower, stringinternal_to_upper, bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, bits_difference, int_to_str, address_to_str, crypto_verify_sig, address_to_contract, crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, - crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1 + crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1, + mcl_bls12_381_g1_neg, mcl_bls12_381_g1_norm, mcl_bls12_381_g1_valid, + mcl_bls12_381_g1_is_zero, mcl_bls12_381_g1_add, mcl_bls12_381_g1_mul, + mcl_bls12_381_g2_neg, mcl_bls12_381_g2_norm, mcl_bls12_381_g2_valid, + mcl_bls12_381_g2_is_zero, mcl_bls12_381_g2_add, mcl_bls12_381_g2_mul, + mcl_bls12_381_gt_inv, mcl_bls12_381_gt_add, mcl_bls12_381_gt_mul, mcl_bls12_381_gt_pow, + mcl_bls12_381_gt_is_one, mcl_bls12_381_pairing, mcl_bls12_381_miller_loop, mcl_bls12_381_final_exp, + mcl_bls12_381_int_to_fr, mcl_bls12_381_int_to_fp, mcl_bls12_381_fr_to_int, mcl_bls12_381_fp_to_int ]. set_state({reg, R}, Val) -> @@ -1151,8 +1218,8 @@ lambda_lift_expr(Layout, UExpr) when element(1, UExpr) == def_u; element(1, UExp lambda_lift_expr(Layout, {remote_u, ArgsT, RetT, Ct, F}) -> FVs = free_vars(Ct), Ct1 = lambda_lift_expr(Layout, Ct), - GasAndValueArgs = 2, - Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + GasAndValueArgs) ], + NamedArgCount = 3, + Xs = [ lists:concat(["arg", I]) || I <- lists:seq(1, length(ArgsT) + NamedArgCount) ], Args = [{var, X} || X <- Xs], make_closure(FVs, Xs, {remote, ArgsT, RetT, Ct1, F, Args}); lambda_lift_expr(Layout, Expr) -> diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 48f6b82..b1a1874 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -469,6 +469,7 @@ is_builtin_fun({qid, _, ["AENS", "preclaim"]}, _Icode) -> is_builtin_fun({qid, _, ["AENS", "claim"]}, _Icode) -> true; is_builtin_fun({qid, _, ["AENS", "transfer"]}, _Icode) -> true; is_builtin_fun({qid, _, ["AENS", "revoke"]}, _Icode) -> true; +is_builtin_fun({qid, _, ["AENS", "update"]}, _Icode) -> true; is_builtin_fun({qid, _, ["Map", "lookup"]}, _Icode) -> true; is_builtin_fun({qid, _, ["Map", "lookup_default"]}, _Icode) -> true; is_builtin_fun({qid, _, ["Map", "member"]}, _Icode) -> true; @@ -624,6 +625,12 @@ builtin_code(_, {qid, _, ["AENS", "revoke"]}, Args, _, _, Icode) -> [ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(Sign, Icode)], [word, word, sign_t()], {tuple, []}); +builtin_code(_, {qid, _, ["AENS", "update"]}, Args, _, _, Icode) -> + {Sign, [Addr, Name, TTL, ClientTTL, Pointers]} = get_signature_arg(Args), + prim_call(?PRIM_CALL_AENS_UPDATE, #integer{value = 0}, + [ast_body(Addr, Icode), ast_body(Name, Icode), ast_body(TTL, Icode), ast_body(ClientTTL, Icode), ast_body(Pointers, Icode), ast_body(Sign, Icode)], + [word, string, word, word, word, sign_t()], {tuple, []}); + %% -- Maps %% -- lookup functions builtin_code(_, {qid, _, ["Map", "lookup"]}, [Key, Map], _, _, Icode) -> @@ -927,11 +934,16 @@ ast_typerep1({variant_t, Cons}, Icode) -> {variant, [ begin {constr_t, _, _, Args} = Con, [ ast_typerep1(Arg, Icode) || Arg <- Args ] - end || Con <- Cons ]}. + end || Con <- Cons ]}; +ast_typerep1({if_t, _, _, _, Else}, Icode) -> + ast_typerep1(Else, Icode). %% protected remote calls are not in AEVM ttl_t(Icode) -> ast_typerep({qid, [], ["Chain", "ttl"]}, Icode). +%% pointee_t(Icode) -> +%% ast_typerep({qid, [], ["AENS", "pointee"]}, Icode). + sign_t() -> bytes_t(64). bytes_t(Len) when Len =< 32 -> word; bytes_t(Len) -> {tuple, lists:duplicate((31 + Len) div 32, word)}. diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 47ff709..623b2ec 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -302,18 +302,27 @@ to_scode1(Env, {funcall, Fun, Args}) -> to_scode1(Env, {builtin, B, Args}) -> builtin_to_scode(Env, B, Args); -to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value | Args]}) -> +to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) -> Lbl = make_function_id(Fun), {ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT), ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})), RetType = ?i(aeb_fate_data:make_typerep(RetType0)), - case Gas of - {builtin, call_gas_left, _} -> - Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a), - call_to_scode(Env, Call, [Ct, Value | Args]); + case Protected of + {lit, {bool, false}} -> + case Gas of + {builtin, call_gas_left, _} -> + Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a), + call_to_scode(Env, Call, [Ct, Value | Args]); + _ -> + Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a), + call_to_scode(Env, Call, [Ct, Value, Gas | Args]) + end; + {lit, {bool, true}} -> + Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?i(true)), + call_to_scode(Env, Call, [Ct, Value, Gas | Args]); _ -> - Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a), - call_to_scode(Env, Call, [Ct, Value, Gas | Args]) + Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?a), + call_to_scode(Env, Call, [Ct, Value, Gas, Protected | Args]) end; to_scode1(_Env, {get_state, Reg}) -> @@ -498,6 +507,8 @@ builtin_to_scode(_Env, call_gas_left, []) -> [aeb_fate_ops:gas(?a)]; builtin_to_scode(Env, oracle_register, [_Sign,_Account,_QFee,_TTL,_QType,_RType] = Args) -> call_to_scode(Env, aeb_fate_ops:oracle_register(?a, ?a, ?a, ?a, ?a, ?a, ?a), Args); +builtin_to_scode(Env, oracle_expiry, [_Oracle] = Args) -> + call_to_scode(Env, aeb_fate_ops:oracle_expiry(?a, ?a), Args); builtin_to_scode(Env, oracle_query_fee, [_Oracle] = Args) -> call_to_scode(Env, aeb_fate_ops:oracle_query_fee(?a, ?a), Args); builtin_to_scode(Env, oracle_query, [_Oracle, _Question, _QFee, _QTTL, _RTTL, _QType, _RType] = Args) -> @@ -536,8 +547,15 @@ builtin_to_scode(Env, aens_transfer, [_Sign, _From, _To, _Name] = Args) -> builtin_to_scode(Env, aens_revoke, [_Sign, _Account, _Name] = Args) -> call_to_scode(Env, [aeb_fate_ops:aens_revoke(?a, ?a, ?a), tuple(0)], Args); +builtin_to_scode(Env, aens_update, [_Sign, _Account, _NameString, _TTL, _ClientTTL, _Pointers] = Args) -> + call_to_scode(Env, [aeb_fate_ops:aens_update(?a, ?a, ?a, ?a, ?a, ?a), + tuple(0)], Args); +builtin_to_scode(Env, aens_lookup, [_Name] = Args) -> + call_to_scode(Env, aeb_fate_ops:aens_lookup(?a, ?a), Args); builtin_to_scode(_Env, auth_tx_hash, []) -> - [aeb_fate_ops:auth_tx_hash(?a)]. + [aeb_fate_ops:auth_tx_hash(?a)]; +builtin_to_scode(_Env, auth_tx, []) -> + [aeb_fate_ops:auth_tx(?a)]. %% -- Operators -- @@ -564,8 +582,14 @@ op_to_scode(map_to_list) -> aeb_fate_ops:map_to_list(?a, ?a); op_to_scode(map_delete) -> aeb_fate_ops:map_delete(?a, ?a, ?a); op_to_scode(map_member) -> aeb_fate_ops:map_member(?a, ?a, ?a); op_to_scode(map_size) -> aeb_fate_ops:map_size_(?a, ?a); -op_to_scode(string_length) -> aeb_fate_ops:str_length(?a, ?a); -op_to_scode(string_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a); +op_to_scode(stringinternal_length) -> aeb_fate_ops:str_length(?a, ?a); +op_to_scode(stringinternal_concat) -> aeb_fate_ops:str_join(?a, ?a, ?a); +op_to_scode(stringinternal_to_list) -> aeb_fate_ops:str_to_list(?a, ?a); +op_to_scode(stringinternal_from_list) -> aeb_fate_ops:str_from_list(?a, ?a); +op_to_scode(stringinternal_to_lower) -> aeb_fate_ops:str_to_lower(?a, ?a); +op_to_scode(stringinternal_to_upper) -> aeb_fate_ops:str_to_upper(?a, ?a); +op_to_scode(char_to_int) -> aeb_fate_ops:char_to_int(?a, ?a); +op_to_scode(char_from_int) -> aeb_fate_ops:char_from_int(?a, ?a); op_to_scode(bits_set) -> aeb_fate_ops:bits_set(?a, ?a, ?a); op_to_scode(bits_clear) -> aeb_fate_ops:bits_clear(?a, ?a, ?a); op_to_scode(bits_test) -> aeb_fate_ops:bits_test(?a, ?a, ?a); @@ -584,9 +608,33 @@ op_to_scode(crypto_ecrecover_secp256k1) -> aeb_fate_ops:ecrecover_secp256k1(?a, op_to_scode(crypto_sha3) -> aeb_fate_ops:sha3(?a, ?a); op_to_scode(crypto_sha256) -> aeb_fate_ops:sha256(?a, ?a); op_to_scode(crypto_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); -op_to_scode(string_sha3) -> aeb_fate_ops:sha3(?a, ?a); -op_to_scode(string_sha256) -> aeb_fate_ops:sha256(?a, ?a); -op_to_scode(string_blake2b) -> aeb_fate_ops:blake2b(?a, ?a). +op_to_scode(stringinternal_sha3) -> aeb_fate_ops:sha3(?a, ?a); +op_to_scode(stringinternal_sha256) -> aeb_fate_ops:sha256(?a, ?a); +op_to_scode(stringinternal_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); +op_to_scode(mcl_bls12_381_g1_neg) -> aeb_fate_ops:bls12_381_g1_neg(?a, ?a); +op_to_scode(mcl_bls12_381_g1_norm) -> aeb_fate_ops:bls12_381_g1_norm(?a, ?a); +op_to_scode(mcl_bls12_381_g1_valid) -> aeb_fate_ops:bls12_381_g1_valid(?a, ?a); +op_to_scode(mcl_bls12_381_g1_is_zero) -> aeb_fate_ops:bls12_381_g1_is_zero(?a, ?a); +op_to_scode(mcl_bls12_381_g1_add) -> aeb_fate_ops:bls12_381_g1_add(?a, ?a, ?a); +op_to_scode(mcl_bls12_381_g1_mul) -> aeb_fate_ops:bls12_381_g1_mul(?a, ?a, ?a); +op_to_scode(mcl_bls12_381_g2_neg) -> aeb_fate_ops:bls12_381_g2_neg(?a, ?a); +op_to_scode(mcl_bls12_381_g2_norm) -> aeb_fate_ops:bls12_381_g2_norm(?a, ?a); +op_to_scode(mcl_bls12_381_g2_valid) -> aeb_fate_ops:bls12_381_g2_valid(?a, ?a); +op_to_scode(mcl_bls12_381_g2_is_zero) -> aeb_fate_ops:bls12_381_g2_is_zero(?a, ?a); +op_to_scode(mcl_bls12_381_g2_add) -> aeb_fate_ops:bls12_381_g2_add(?a, ?a, ?a); +op_to_scode(mcl_bls12_381_g2_mul) -> aeb_fate_ops:bls12_381_g2_mul(?a, ?a, ?a); +op_to_scode(mcl_bls12_381_gt_inv) -> aeb_fate_ops:bls12_381_gt_inv(?a, ?a); +op_to_scode(mcl_bls12_381_gt_add) -> aeb_fate_ops:bls12_381_gt_add(?a, ?a, ?a); +op_to_scode(mcl_bls12_381_gt_mul) -> aeb_fate_ops:bls12_381_gt_mul(?a, ?a, ?a); +op_to_scode(mcl_bls12_381_gt_pow) -> aeb_fate_ops:bls12_381_gt_pow(?a, ?a, ?a); +op_to_scode(mcl_bls12_381_gt_is_one) -> aeb_fate_ops:bls12_381_gt_is_one(?a, ?a); +op_to_scode(mcl_bls12_381_pairing) -> aeb_fate_ops:bls12_381_pairing(?a, ?a, ?a); +op_to_scode(mcl_bls12_381_miller_loop) -> aeb_fate_ops:bls12_381_miller_loop(?a, ?a, ?a); +op_to_scode(mcl_bls12_381_final_exp) -> aeb_fate_ops:bls12_381_final_exp(?a, ?a); +op_to_scode(mcl_bls12_381_int_to_fr) -> aeb_fate_ops:bls12_381_int_to_fr(?a, ?a); +op_to_scode(mcl_bls12_381_int_to_fp) -> aeb_fate_ops:bls12_381_int_to_fp(?a, ?a); +op_to_scode(mcl_bls12_381_fr_to_int) -> aeb_fate_ops:bls12_381_fr_to_int(?a, ?a); +op_to_scode(mcl_bls12_381_fp_to_int) -> aeb_fate_ops:bls12_381_fp_to_int(?a, ?a). %% PUSH and STORE ?a are the same, so we use STORE to make optimizations %% easier, and specialize to PUSH (which is cheaper) at the end. @@ -734,6 +782,7 @@ attributes(I) -> {'CALL', A} -> Impure(?a, [A]); {'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]); {'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]); + {'CALL_PGR', A, _, B, C, D, E, F} -> Impure(?a, [A, B, C, D, E, F]); {'CALL_T', A} -> Impure(pc, [A]); {'CALL_VALUE', A} -> Pure(A, []); {'JUMP', _} -> Impure(pc, []); @@ -815,6 +864,7 @@ attributes(I) -> {'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]); {'ADDRESS_TO_CONTRACT', A, B} -> Pure(A, [B]); {'AUTH_TX_HASH', A} -> Pure(A, []); + {'AUTH_TX', A} -> Pure(A, []); {'BYTES_TO_INT', A, B} -> Pure(A, [B]); {'BYTES_TO_STR', A, B} -> Pure(A, [B]); {'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]); @@ -853,12 +903,44 @@ attributes(I) -> {'ORACLE_GET_ANSWER', A, B, C, D, E} -> Pure(A, [B, C, D, E]); {'ORACLE_GET_QUESTION', A, B, C, D, E}-> Pure(A, [B, C, D, E]); {'ORACLE_QUERY_FEE', A, B} -> Pure(A, [B]); - {'AENS_RESOLVE', A, B, C, D} -> Pure(A, [B, C, D]); + {'ORACLE_EXPIRY', A, B} -> Impure(A, [B]); + {'AENS_RESOLVE', A, B, C, D} -> Impure(A, [B, C, D]); {'AENS_PRECLAIM', A, B, C} -> Impure(none, [A, B, C]); {'AENS_CLAIM', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]); - 'AENS_UPDATE' -> Impure(none, []);%% TODO + {'AENS_UPDATE', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]); {'AENS_TRANSFER', A, B, C, D} -> Impure(none, [A, B, C, D]); {'AENS_REVOKE', A, B, C} -> Impure(none, [A, B, C]); + {'AENS_LOOKUP', A, B} -> Impure(A, [B]); + {'BLS12_381_G1_NEG', A, B} -> Pure(A, [B]); + {'BLS12_381_G1_NORM', A, B} -> Pure(A, [B]); + {'BLS12_381_G1_VALID', A, B} -> Pure(A, [B]); + {'BLS12_381_G1_IS_ZERO', A, B} -> Pure(A, [B]); + {'BLS12_381_G1_ADD', A, B, C} -> Pure(A, [B, C]); + {'BLS12_381_G1_MUL', A, B, C} -> Pure(A, [B, C]); + {'BLS12_381_G2_NEG', A, B} -> Pure(A, [B]); + {'BLS12_381_G2_NORM', A, B} -> Pure(A, [B]); + {'BLS12_381_G2_VALID', A, B} -> Pure(A, [B]); + {'BLS12_381_G2_IS_ZERO', A, B} -> Pure(A, [B]); + {'BLS12_381_G2_ADD', A, B, C} -> Pure(A, [B, C]); + {'BLS12_381_G2_MUL', A, B, C} -> Pure(A, [B, C]); + {'BLS12_381_GT_INV', A, B} -> Pure(A, [B]); + {'BLS12_381_GT_ADD', A, B, C} -> Pure(A, [B, C]); + {'BLS12_381_GT_MUL', A, B, C} -> Pure(A, [B, C]); + {'BLS12_381_GT_POW', A, B, C} -> Pure(A, [B, C]); + {'BLS12_381_GT_IS_ONE', A, B} -> Pure(A, [B]); + {'BLS12_381_PAIRING', A, B, C} -> Pure(A, [B, C]); + {'BLS12_381_MILLER_LOOP', A, B, C} -> Pure(A, [B, C]); + {'BLS12_381_FINAL_EXP', A, B} -> Pure(A, [B]); + {'BLS12_381_INT_TO_FR', A, B} -> Pure(A, [B]); + {'BLS12_381_INT_TO_FP', A, B} -> Pure(A, [B]); + {'BLS12_381_FR_TO_INT', A, B} -> Pure(A, [B]); + {'BLS12_381_FP_TO_INT', A, B} -> Pure(A, [B]); + {'STR_TO_LIST', A, B} -> Pure(A, [B]); + {'STR_FROM_LIST', A, B} -> Pure(A, [B]); + {'STR_TO_UPPER', A, B} -> Pure(A, [B]); + {'STR_TO_LOWER', A, B} -> Pure(A, [B]); + {'CHAR_TO_INT', A, B} -> Pure(A, [B]); + {'CHAR_FROM_INT', A, B} -> Pure(A, [B]); {'ABORT', A} -> Impure(pc, A); {'EXIT', A} -> Impure(pc, A); 'NOP' -> Pure(none, []) @@ -1611,6 +1693,7 @@ split_calls(Ref, [], Acc, Blocks) -> split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL'; element(1, I) == 'CALL_R'; element(1, I) == 'CALL_GR'; + element(1, I) == 'CALL_PGR'; element(1, I) == 'jumpif' -> split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]); split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) -> diff --git a/src/aeso_icode.erl b/src/aeso_icode.erl index e6d50f9..0b6357b 100644 --- a/src/aeso_icode.erl +++ b/src/aeso_icode.erl @@ -73,28 +73,33 @@ new(Options) -> builtin_types() -> Word = fun([]) -> word end, - #{ "bool" => Word - , "int" => Word - , "char" => Word - , "bits" => Word - , "string" => fun([]) -> string end - , "address" => Word - , "hash" => Word - , "unit" => fun([]) -> {tuple, []} end - , "signature" => fun([]) -> {tuple, [word, word]} end - , "oracle" => fun([_, _]) -> word end - , "oracle_query" => fun([_, _]) -> word end - , "list" => fun([A]) -> {list, A} end - , "option" => fun([A]) -> {variant, [[], [A]]} end - , "map" => fun([K, V]) -> map_typerep(K, V) end - , ["Chain", "ttl"] => fun([]) -> {variant, [[word], [word]]} end + #{ "bool" => Word + , "int" => Word + , "char" => Word + , "bits" => Word + , "string" => fun([]) -> string end + , "address" => Word + , "hash" => Word + , "unit" => fun([]) -> {tuple, []} end + , "signature" => fun([]) -> {tuple, [word, word]} end + , "oracle" => fun([_, _]) -> word end + , "oracle_query" => fun([_, _]) -> word end + , "list" => fun([A]) -> {list, A} end + , "option" => fun([A]) -> {variant, [[], [A]]} end + , "map" => fun([K, V]) -> map_typerep(K, V) end + , ["Chain", "ttl"] => fun([]) -> {variant, [[word], [word]]} end + , ["AENS", "pointee"] => fun([]) -> {variant, [[word], [word], [word]]} end }. builtin_constructors() -> - #{ ["RelativeTTL"] => 0 - , ["FixedTTL"] => 1 - , ["None"] => 0 - , ["Some"] => 1 }. + #{ ["RelativeTTL"] => 0 + , ["FixedTTL"] => 1 + , ["None"] => 0 + , ["Some"] => 1 + , ["AccountPointee"] => 0 + , ["OraclePointee"] => 1 + , ["ContractPointee"] => 2 + }. map_typerep(K, V) -> {map, K, V}. @@ -146,4 +151,3 @@ get_constructor_tag(Name, #{constructors := Constructors}) -> undefined -> error({undefined_constructor, Name}); Tag -> Tag end. - diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index ac80edb..dfbd577 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -264,6 +264,8 @@ type({args_t, _, Args}) -> type({bytes_t, _, any}) -> text("bytes(_)"); type({bytes_t, _, Len}) -> text(lists:concat(["bytes(", Len, ")"])); +type({if_t, _, Id, Then, Else}) -> + beside(text("if"), args_type([Id, Then, Else])); type({named_arg_t, _, Name, Type, _Default}) -> %% Drop the default value %% follow(hsep(typed(name(Name), Type), text("=")), expr(Default)); @@ -290,12 +292,9 @@ tuple_type(Factors) -> , text(")") ]). --spec arg_expr(aeso_syntax:arg_expr()) -> doc(). -arg_expr({named_arg, _, Name, E}) -> - follow(hsep(expr(Name), text("=")), expr(E)); -arg_expr(E) -> expr(E). - --spec expr_p(integer(), aeso_syntax:expr()) -> doc(). +-spec expr_p(integer(), aeso_syntax:arg_expr()) -> doc(). +expr_p(P, {named_arg, _, Name, E}) -> + paren(P > 100, follow(hsep(expr(Name), text("=")), expr(E))); expr_p(P, {lam, _, Args, E}) -> paren(P > 100, follow(hsep(args(Args), text("=>")), expr_p(100, E))); expr_p(P, If = {'if', Ann, Cond, Then, Else}) -> @@ -377,8 +376,13 @@ expr_p(_, {char, _, C}) -> case C of $' -> text("'\\''"); $" -> text("'\"'"); - _ -> S = lists:flatten(io_lib:format("~p", [[C]])), - text("'" ++ tl(lists:droplast(S)) ++ "'") + _ when C < 16#80 -> + S = lists:flatten(io_lib:format("~p", [[C]])), + text("'" ++ tl(lists:droplast(S)) ++ "'"); + _ -> + S = lists:flatten( + io_lib:format("'~ts'", [list_to_binary(aeso_scan:utf8_encode([C]))])), + text(S) end; %% -- Names expr_p(_, E = {id, _, _}) -> name(E); @@ -450,7 +454,7 @@ prefix(P, Op, A) -> app(P, F, Args) -> paren(P > 900, beside(expr_p(900, F), - tuple(lists:map(fun arg_expr/1, Args)))). + tuple(lists:map(fun expr/1, Args)))). field({field, _, LV, E}) -> follow(hsep(lvalue(LV), text("=")), expr(E)); diff --git a/src/aeso_scan.erl b/src/aeso_scan.erl index 1c30016..e81757f 100644 --- a/src/aeso_scan.erl +++ b/src/aeso_scan.erl @@ -7,7 +7,7 @@ %%%------------------------------------------------------------------- -module(aeso_scan). --export([scan/1]). +-export([scan/1, utf8_encode/1]). -import(aeso_scan_lib, [token/1, token/2, symbol/0, skip/0, override/2, push/2, pop/1]). @@ -28,7 +28,13 @@ lexer() -> QID = ["(", CON, "\\.)+", ID], QCON = ["(", CON, "\\.)+", CON], OP = "[=!<>+\\-*/:&|?~@^]+", - CHAR = "'([^'\\\\]|(\\\\.))'", + %% Five cases for a character + %% * 1 7-bit ascii, not \ or ' + %% * 2-4 8-bit values (UTF8) + %% * \ followed by a known modifier [aernrtv] + %% * \xhh + %% * \x{hhh...} + CHAR = "'(([\\x00-\\x26\\x28-\\x5b\\x5d-\\x7f])|([\\x00-\\xff][\\x80-\\xff]{1,3})|(\\\\[befnrtv'\\\\])|(\\\\x[0-9a-fA-F]{2,2})|(\\\\x\\{[0-9a-fA-F]*\\}))'", STRING = "\"([^\"\\\\]|(\\\\.))*\"", CommentStart = {"/\\*", push(comment, skip())}, @@ -77,34 +83,34 @@ scan(String) -> %% -- Helpers ---------------------------------------------------------------- parse_string([$" | Chars]) -> - unescape(Chars). + unicode:characters_to_nfc_binary(unescape(Chars)). -parse_char([$', $\\, Code, $']) -> - case Code of - $' -> $'; - $\\ -> $\\; - $b -> $\b; - $e -> $\e; - $f -> $\f; - $n -> $\n; - $r -> $\r; - $t -> $\t; - $v -> $\v; - _ -> {error, "Bad control sequence: \\" ++ [Code]} - end; -parse_char([$', C, $']) -> C. +parse_char([$' | Chars]) -> + case unicode:characters_to_nfc_list(unescape($', Chars, [])) of + [Char] -> Char; + _Bad -> {error, "Bad character literal: '" ++ Chars} + end. -unescape(Str) -> unescape(Str, []). +utf8_encode(Cs) -> + binary_to_list(unicode:characters_to_binary(Cs)). -unescape([$"], Acc) -> +unescape(Str) -> unescape($", Str, []). + +unescape(Delim, [Delim], Acc) -> list_to_binary(lists:reverse(Acc)); -unescape([$\\, $x, D1, D2 | Chars ], Acc) -> +unescape(Delim, [$\\, $x, ${ | Chars ], Acc) -> + {Ds, [_ | Cs]} = lists:splitwith(fun($}) -> false ; (_) -> true end, Chars), + C = list_to_integer(Ds, 16), + Utf8Cs = binary_to_list(unicode:characters_to_binary([C])), + unescape(Delim, Cs, [Utf8Cs | Acc]); +unescape(Delim, [$\\, $x, D1, D2 | Chars ], Acc) -> C = list_to_integer([D1, D2], 16), - unescape(Chars, [C | Acc]); -unescape([$\\, Code | Chars], Acc) -> - Ok = fun(C) -> unescape(Chars, [C | Acc]) end, + Utf8Cs = binary_to_list(unicode:characters_to_binary([C])), + unescape(Delim, Chars, [Utf8Cs | Acc]); +unescape(Delim, [$\\, Code | Chars], Acc) -> + Ok = fun(C) -> unescape(Delim, Chars, [C | Acc]) end, case Code of - $" -> Ok($"); + Delim -> Ok(Delim); $\\ -> Ok($\\); $b -> Ok($\b); $e -> Ok($\e); @@ -115,8 +121,8 @@ unescape([$\\, Code | Chars], Acc) -> $v -> Ok($\v); _ -> error("Bad control sequence: \\" ++ [Code]) %% TODO end; -unescape([C | Chars], Acc) -> - unescape(Chars, [C | Acc]). +unescape(Delim, [C | Chars], Acc) -> + unescape(Delim, Chars, [C | Acc]). strip_underscores(S) -> lists:filter(fun(C) -> C /= $_ end, S). diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index 2ae2cbf..c939893 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -143,4 +143,4 @@ compilable_contracts() -> not_yet_compilable(fate) -> []; not_yet_compilable(aevm) -> - []. + ["funargs", "strings"]. diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index a6c76a6..425cdd1 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -154,6 +154,7 @@ compilable_contracts() -> "events", "include", "basic_auth", + "basic_auth_tx", "bitcoin_auth", "address_literals", "bytes_equality", @@ -162,6 +163,7 @@ compilable_contracts() -> "bytes_to_x", "bytes_concat", "aens", + "aens_update", "tuple_match", "cyclic_include", "stdlib_include", @@ -171,17 +173,22 @@ compilable_contracts() -> "payable", "unapplied_builtins", "underscore_number_literals", + "pairing_crypto", "qualified_constructor", "let_patterns", "lhs_matching", + "more_strings", + "protected_call", "hermetization_turnoff" ]. not_compilable_on(fate) -> []; not_compilable_on(aevm) -> - ["stdlib_include", - "manual_stdlib_include", - "hermetization_turnoff" + [ "stdlib_include", "manual_stdlib_include", "pairing_crypto" + , "aens_update", "basic_auth_tx", "more_strings" + , "unapplied_builtins", "bytes_to_x", "state_handling", "protected_call" + , "hermetization_turnoff" + ]. debug_mode_contracts() -> @@ -387,10 +394,10 @@ failing_contracts() -> [< Remote.themap\n" + "when checking the type of the expression at line 12, column 42\n" + " r.foo() : map(int, string)\n" "against the expected type\n" - " (gas : int, value : int) => map(string, int)">>]) + " map(string, int)">>]) , ?TYPE_ERROR(not_toplevel_include, [<>]) @@ -555,11 +562,15 @@ failing_contracts() -> "Cannot unify int\n and string\nwhen checking the type of the pattern at line 2, column 53\n x : int\nagainst the expected type\n string">> ]) , ?TYPE_ERROR(map_as_map_key, - [<>, - <>, + <>]) @@ -681,6 +692,12 @@ failing_contracts() -> "Empty record/map update\n" " r {}">> ]) + , ?TYPE_ERROR(bad_protected_call, + [<> + ]) , ?TYPE_ERROR(bad_function_block, [<>, diff --git a/test/aeso_parser_tests.erl b/test/aeso_parser_tests.erl index 21d7ff7..c978b0a 100644 --- a/test/aeso_parser_tests.erl +++ b/test/aeso_parser_tests.erl @@ -63,7 +63,8 @@ simple_contracts_test_() -> %% Parse tests of example contracts [ {lists:concat(["Parse the ", Contract, " contract."]), fun() -> roundtrip_contract(Contract) end} - || Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, multi_sig, simple_storage, fundme, dutch_auction] ] + || Contract <- [counter, voting, all_syntax, '05_greeter', aeproof, + multi_sig, simple_storage, fundme, dutch_auction, utf8] ] }. parse_contract(Name) -> @@ -85,7 +86,7 @@ parse_expr(Text) -> round_trip(Text) -> Contract = parse_string(Text), Text1 = prettypr:format(aeso_pretty:decls(strip_stdlib(Contract))), - Contract1 = parse_string(Text1), + Contract1 = parse_string(aeso_scan:utf8_encode(Text1)), NoSrcLoc = remove_line_numbers(Contract), NoSrcLoc1 = remove_line_numbers(Contract1), ?assertMatch(NoSrcLoc, diff(NoSrcLoc, NoSrcLoc1)). diff --git a/test/contracts/aens.aes b/test/contracts/aens.aes index ee1ba27..c342170 100644 --- a/test/contracts/aens.aes +++ b/test/contracts/aens.aes @@ -33,7 +33,22 @@ contract AENSTest = sign : signature) : unit = AENS.claim(addr, name, salt, name_fee, signature = sign) - // TODO: update() -- how to handle pointers? + + stateful entrypoint update(owner : address, + name : string, + ttl : option(Chain.ttl), + client_ttl : option(int), + pointers : option(map(string, AENS.pointee))) : unit = + AENS.update(owner, name, ttl, client_ttl, pointers) + + stateful entrypoint signedUpdate(owner : address, + name : string, + ttl : option(Chain.ttl), + client_ttl : option(int), + pointers : option(map(string, AENS.pointee)), + sign : signature) : unit = + AENS.update(owner, name, ttl, client_ttl, pointers, signature = sign) + stateful entrypoint transfer(owner : address, new_owner : address, diff --git a/test/contracts/aens_update.aes b/test/contracts/aens_update.aes new file mode 100644 index 0000000..5d65229 --- /dev/null +++ b/test/contracts/aens_update.aes @@ -0,0 +1,17 @@ +contract AENSUpdate = + stateful entrypoint update_name(owner : address, name : string) = + let p1 : AENS.pointee = AENS.AccountPt(Call.caller) + let p2 : AENS.pointee = AENS.OraclePt(Call.caller) + let p3 : AENS.pointee = AENS.ContractPt(Call.caller) + let p4 : AENS.pointee = AENS.ChannelPt(Call.caller) + AENS.update(owner, name, None, None, + Some({ ["account_pubkey"] = p1, ["oracle_pubkey"] = p2, + ["contract_pubkey"] = p3, ["misc"] = p4 })) + + entrypoint get_ttl(name : string) = + switch(AENS.lookup(name)) + Some(AENS.Name(_, FixedTTL(ttl), _)) => ttl + + entrypoint expiry(o : oracle(int, int)) : int = + Oracle.expiry(o) + diff --git a/test/contracts/all_syntax.aes b/test/contracts/all_syntax.aes index 6a40f27..ced918a 100644 --- a/test/contracts/all_syntax.aes +++ b/test/contracts/all_syntax.aes @@ -36,6 +36,8 @@ contract AllSyntax = entrypoint init() = { johann = 1000, wolfgang = -10, + +/* 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) @@ -43,11 +45,13 @@ contract AllSyntax = , Adam <- [Adam, Mickiewicz] , let x = f(l) ])), +*/ + von = (2 + 2, 0, List.sum([1,2,3,4])), goethe = () } function f() = let kp = "nietzsche" - let p = "Пушкин" + // 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 diff --git a/test/contracts/bad_protected_call.aes b/test/contracts/bad_protected_call.aes new file mode 100644 index 0000000..ba1f59c --- /dev/null +++ b/test/contracts/bad_protected_call.aes @@ -0,0 +1,6 @@ +contract Remote = + entrypoint id : int => int + +contract ProtectedCall = + entrypoint bad(r : Remote) = + r.id(protected = 0 == 1, 18) diff --git a/test/contracts/basic_auth_tx.aes b/test/contracts/basic_auth_tx.aes new file mode 100644 index 0000000..b57c498 --- /dev/null +++ b/test/contracts/basic_auth_tx.aes @@ -0,0 +1,74 @@ +// namespace Chain = +// 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 } + +// datatype ga_meta_tx = GAMetaTx(address, int) +// datatype paying_for_tx = PayingForTx(address, int) +// 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 + + +// Contract replicating "normal" Aeternity authentication +contract BasicAuthTx = + record state = { nonce : int, owner : address } + datatype foo = Bar | Baz() + + entrypoint init() = { nonce = 1, owner = Call.caller } + + stateful entrypoint authorize(n : int, s : signature) : bool = + require(n >= state.nonce, "Nonce too low") + require(n =< state.nonce, "Nonce too high") + put(state{ nonce = n + 1 }) + switch(Auth.tx_hash) + None => abort("Not in Auth context") + Some(tx_hash) => + let Some(tx0) = Auth.tx + let x : option(Chain.paying_for_tx) = tx0.paying_for + let x : list(Chain.ga_meta_tx) = tx0.ga_metas + let x : int = tx0.fee + tx0.ttl + let x : address = tx0.actor + let x : Chain.tx = { tx = Chain.NamePreclaimTx, paying_for = None, ga_metas = [], + fee = 123, ttl = 0, actor = Call.caller } + switch(tx0.tx) + Chain.SpendTx(receiver, amount, payload) => verify(tx_hash, n, s) + Chain.OracleRegisterTx => false + Chain.OracleQueryTx => false + Chain.OracleResponseTx => false + Chain.OracleExtendTx => false + Chain.NamePreclaimTx => false + Chain.NameClaimTx(name) => false + Chain.NameUpdateTx(name) => false + Chain.NameRevokeTx(name) => false + Chain.NameTransferTx(to, name) => false + Chain.ChannelCreateTx(other_party) => false + Chain.ChannelDepositTx(channel, amount) => false + Chain.ChannelWithdrawTx(channel, amount) => false + Chain.ChannelForceProgressTx(channel) => false + Chain.ChannelCloseMutualTx(channel) => false + Chain.ChannelCloseSoloTx(channel) => false + Chain.ChannelSlashTx(channel) => false + Chain.ChannelSettleTx(channel) => false + Chain.ChannelSnapshotSoloTx(channel) => false + Chain.ContractCreateTx(amount) => false + Chain.ContractCallTx(ct_address, amount) => false + Chain.GAAttachTx => false + + function verify(tx_hash, n, s) = + Crypto.verify_sig(to_sign(tx_hash, n), state.owner, s) + + entrypoint to_sign(h : hash, n : int) = + Crypto.blake2b((h, n)) + + entrypoint weird_string() : string = + "\x19Weird String\x42\nMore\n" diff --git a/test/contracts/bytes_to_x.aes b/test/contracts/bytes_to_x.aes index 6054301..6ab4852 100644 --- a/test/contracts/bytes_to_x.aes +++ b/test/contracts/bytes_to_x.aes @@ -1,4 +1,4 @@ - +include "String.aes" contract BytesToX = entrypoint to_int(b : bytes(42)) : int = Bytes.to_int(b) diff --git a/test/contracts/channel_on_chain_contract_oracle.aes b/test/contracts/channel_on_chain_contract_oracle.aes index d62ab41..de681aa 100644 --- a/test/contracts/channel_on_chain_contract_oracle.aes +++ b/test/contracts/channel_on_chain_contract_oracle.aes @@ -17,7 +17,7 @@ contract ChannelOnChainContractOracle = bets = {} } - public stateful function place_bet(answer: string) = + public stateful function place_bet(answer: string) = switch(Map.lookup(answer, state.bets)) None => put(state{ bets = state.bets{[answer] = Call.caller}}) @@ -25,6 +25,9 @@ contract ChannelOnChainContractOracle = Some(_value) => "bet_already_taken" + public function expiry() = + Oracle.expiry(state.oracle) + public function query_fee() = Oracle.query_fee(state.oracle) @@ -35,7 +38,7 @@ contract ChannelOnChainContractOracle = switch(Oracle.get_answer(state.oracle, q)) None => "no response" - Some(result) => + Some(result) => if(state.question == Oracle.get_question(state.oracle, q)) switch(Map.lookup(result, state.bets)) None => diff --git a/test/contracts/funargs.aes b/test/contracts/funargs.aes index 19c43a2..f2339e4 100644 --- a/test/contracts/funargs.aes +++ b/test/contracts/funargs.aes @@ -1,4 +1,4 @@ - +include "String.aes" contract FunctionArguments = entrypoint sum(n : int, m: int) = diff --git a/test/contracts/more_strings.aes b/test/contracts/more_strings.aes new file mode 100644 index 0000000..9579423 --- /dev/null +++ b/test/contracts/more_strings.aes @@ -0,0 +1,14 @@ +include "String.aes" +contract StringX = + entrypoint test() = + let s1 = "a string" + let s2 = "another string" + let s = String.concat(s1, s2) + String.sha256(s) + String.length(s1) + String.from_list(String.to_list(s)) + String.split(4, s1) + String.at(2, s2) + String.tokens(s, ",") + String.to_upper(s1) + String.to_lower(s2) diff --git a/test/contracts/pairing_crypto.aes b/test/contracts/pairing_crypto.aes new file mode 100644 index 0000000..93f4a24 --- /dev/null +++ b/test/contracts/pairing_crypto.aes @@ -0,0 +1,30 @@ +include "BLS12_381.aes" + +contract GrothVerify = + type fr = BLS12_381.fr + type g1 = BLS12_381.g1 + type g2 = BLS12_381.g2 + + record proof = { a : g1, b : g2, c : g1 } + record verify_key = { a : g1, b : g2, c : g2, d : g2, ic : list(g1) } + + record state = { vk : verify_key } + + entrypoint init(vk0 : verify_key) = {vk = vk0} + + entrypoint verify_proof(p : proof, input : list(fr)) = + let vk = state.vk + let vk_x = calc_vk_x(vk.ic, input) + + BLS12_381.pairing_check([BLS12_381.g1_neg(p.a), vk.a, vk_x, p.c], + [p.b, vk.b, vk.c, vk.d]) + + function calc_vk_x(ics : list(g1), xs : list(fr)) = + switch(ics) + (ic :: ics) => calc_vk_x_(ic, ics, xs) + + function calc_vk_x_(vk_x : g1, ics : list(g1), xs : list(fr)) = + switch((ics, xs)) + ([], []) => vk_x + (ic :: ics, x :: xs) => calc_vk_x_(BLS12_381.g1_add(vk_x, BLS12_381.g1_mul(x, ic)), ics, xs) + diff --git a/test/contracts/protected_call.aes b/test/contracts/protected_call.aes new file mode 100644 index 0000000..d377399 --- /dev/null +++ b/test/contracts/protected_call.aes @@ -0,0 +1,14 @@ +contract Remote = + entrypoint id : int => int + +contract ProtectedCall = + + function higher_order(r : Remote) = + r.id + + entrypoint test_ok(r : Remote) = + let f = higher_order(r) + let Some(n) = r.id(protected = true, 10) + let Some(m) = f(protected = true, 5) + n + m + r.id(protected = false, 100) + f(1) + diff --git a/test/contracts/state_handling.aes b/test/contracts/state_handling.aes index e4311d6..00c2fcb 100644 --- a/test/contracts/state_handling.aes +++ b/test/contracts/state_handling.aes @@ -1,3 +1,4 @@ +include "String.aes" contract Remote = record rstate = { i : int, s : string, m : map(int, int) } diff --git a/test/contracts/strings.aes b/test/contracts/strings.aes index cbde027..752266b 100644 --- a/test/contracts/strings.aes +++ b/test/contracts/strings.aes @@ -1,3 +1,4 @@ +include "String.aes" contract Strings = entrypoint str_len(s) = String.length(s) entrypoint str_concat(s1, s2) = String.concat(s1, s2) diff --git a/test/contracts/unapplied_builtins.aes b/test/contracts/unapplied_builtins.aes index 6055a87..678b085 100644 --- a/test/contracts/unapplied_builtins.aes +++ b/test/contracts/unapplied_builtins.aes @@ -7,6 +7,7 @@ // AENS.transfer // AENS.revoke // Oracle.extend +include "String.aes" contract UnappliedBuiltins = entrypoint main() = () type o = oracle(int, int) @@ -21,6 +22,7 @@ contract UnappliedBuiltins = function b_abort() = abort function b_require() = require function oracle_query_fee() = Oracle.query_fee + function oracle_expiry() = Oracle.expiry stateful function oracle_query() = Oracle.query : (o, _, _, _, _) => _ function oracle_get_question() = Oracle.get_question : (o, _) => _ function oracle_get_answer() = Oracle.get_answer : (o, _) => _ diff --git a/test/contracts/utf8.aes b/test/contracts/utf8.aes new file mode 100644 index 0000000..3e82b6b --- /dev/null +++ b/test/contracts/utf8.aes @@ -0,0 +1,21 @@ +contract UTF8 = + entrypoint f1() : char = '1' + entrypoint f2() : char = '+' + entrypoint f3() : char = 'd' + entrypoint f4() : char = 'X' + entrypoint f5() : char = 'å' + entrypoint f6() : char = 'Ä' + entrypoint f7() : char = 'æ' + entrypoint f8() : char = 'ë' + entrypoint f9() : char = 'ẻ' + entrypoint f10() : char = '\x27' + entrypoint f11() : char = '\x{2200}' + entrypoint f12() : char = '💩' + entrypoint f13() : char = '\n' + + + + // entrypoint f13() : char = 'e̊' + // entrypoint f14() : char = '\Ì' + + // '💩' vs. map('a,'b)