From 2bf65cfd98af3feac9904bef0608664ef1d23440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Thu, 13 Feb 2020 11:02:47 +0100 Subject: [PATCH 01/14] Add Frac (#222) Fix bugs in Frac Added optimizer --- priv/stdlib/Frac.aes | 171 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 priv/stdlib/Frac.aes diff --git a/priv/stdlib/Frac.aes b/priv/stdlib/Frac.aes new file mode 100644 index 0000000..27ad531 --- /dev/null +++ b/priv/stdlib/Frac.aes @@ -0,0 +1,171 @@ +namespace Frac = + + private function gcd(a : int, b : int) = + if (b == 0) a else gcd(b, a mod b) + + private function abs_int(a : int) = if (a < 0) -a else a + + datatype frac = Pos(int, int) | Zero | Neg(int, int) + + // Checks if internal representation is correct. Numerator and denominator must be positive. + function is_sane(f : frac) : bool = switch(f) + Pos(n, d) => n > 0 && d > 0 + Zero => true + Neg(n, d) => n > 0 && d > 0 + + function num(f : frac) : int = switch(f) + Pos(n, _) => n + Neg(n, _) => -n + Zero => 0 + + function den(f : frac) : int = switch(f) + Pos(_, d) => d + Neg(_, d) => d + Zero => 1 + + function to_pair(f : frac) : int * int = switch(f) + Pos(n, d) => (n, d) + Neg(n, d) => (-n, d) + Zero => (0, 1) + + function sign(f : frac) : int = switch(f) + Pos(_, _) => 1 + Neg(_, _) => -1 + Zero => 0 + + function to_str(f : frac) : string = switch(f) + Pos(n, d) => String.concat(Int.to_str(n), if (d == 1) "" else String.concat("/", Int.to_str(d))) + Neg(n, d) => String.concat("-", to_str(Pos(n, d))) + Zero => "0" + + // Reduce fraction to normal form + function simplify(f : frac) : frac = + switch(f) + Neg(n, d) => + let cd = gcd(n, d) + Neg(n / cd, d / cd) + Zero => Zero + Pos(n, d) => + let cd = gcd(n, d) + Pos(n / cd, d / cd) + + function make_frac(n : int, d : int) : frac = + if (d == 0) abort("Division by zero") + elif (n == 0) Zero + elif ((n < 0) == (d < 0)) simplify(Pos(abs_int(n), abs_int(d))) + else simplify(Neg(abs_int(n), abs_int(d))) + + function eq(a : frac, b : frac) : bool = + let na = num(a) + let nb = num(b) + let da = den(a) + let db = den(b) + (na == nb && da == db) || na * db == nb * da // they are more likely to be normalized + + function neq(a : frac, b : frac) : bool = + let na = num(a) + let nb = num(b) + let da = den(a) + let db = den(b) + (na != nb || da != db) && na * db != nb * da + + function geq(a : frac, b : frac) : bool = num(a) * den(b) >= num(b) * den(a) + + function leq(a : frac, b : frac) : bool = num(a) * den(b) =< num(b) * den(a) + + function gt(a : frac, b : frac) : bool = num(a) * den(b) > num(b) * den(a) + + function lt(a : frac, b : frac) : bool = num(a) * den(b) < num(b) * den(a) + + function min(a : frac, b : frac) : frac = if (leq(a, b)) a else b + + function max(a : frac, b : frac) : frac = if (geq(a, b)) a else b + + function abs(f : frac) : frac = switch(f) + Pos(n, d) => Pos(n, d) + Zero => Zero + Neg(n, d) => Pos(n, d) + + function from_int(n : int) : frac = + if (n > 0) Pos(n, 1) + elif (n < 0) Neg(-n, 1) + else Zero + + function floor(f : frac) : int = switch(f) + Pos(n, d) => n / d + Zero => 0 + Neg(n, d) => -(n + d - 1) / d + + function ceil(f : frac) : int = switch(f) + Pos(n, d) => (n + d - 1) / d + Zero => 0 + Neg(n, d) => -n / d + + function round_to_zero(f : frac) : int = switch(f) + Pos(n, d) => n / d + Zero => 0 + Neg(n, d) => -n / d + + function round_from_zero(f : frac) : int = switch(f) + Pos(n, d) => (n + d - 1) / d + Zero => 0 + Neg(n, d) => -(n + d - 1) / d + + // Round towards nearest integer. If two integers are in the same distance, choose the even one. + function round(f : frac) : int = + let fl = floor(f) + let cl = ceil(f) + let dif_fl = abs(sub(f, from_int(fl))) + let dif_cl = abs(sub(f, from_int(cl))) + if (gt(dif_fl, dif_cl)) cl + elif (gt(dif_cl, dif_fl)) fl + elif (fl mod 2 == 0) fl + else cl + + function add(a : frac, b : frac) : frac = + let na = num(a) + let nb = num(b) + let da = den(a) + let db = den(b) + if (da == db) make_frac(na + nb, da) + else make_frac(na * db + nb * da, da * db) + + function neg(a : frac) : frac = switch(a) + Neg(n, d) => Pos(n, d) + Zero => Zero + Pos(n, d) => Neg(n, d) + + function sub(a : frac, b : frac) : frac = add(a, neg(b)) + + function inv(a : frac) : frac = switch(a) + Neg(n, d) => Neg(d, n) + Zero => abort("Inversion of zero") + Pos(n, d) => Pos(d, n) + + function mul(a : frac, b : frac) : frac = make_frac(num(a) * num(b), den(a) * den(b)) + + function div(a : frac, b : frac) : frac = mul(a, inv(b)) + + function int_exp(b : frac, e : int) : frac = + if (sign(b) == 0 && e == 0) abort("Zero to the zero exponentation") + elif (e < 0) inv(int_exp_(b, -e)) + else int_exp_(b, e) + private function int_exp_(b : frac, e : int) = + if (e == 0) from_int(1) + elif (e == 1) b + else + let half = int_exp_(b, e / 2) + if (e mod 2 == 1) mul(mul(half, half), b) + else mul(half, half) + + // Reduces the fraction's in-memory size by dividing its components by two until the + // the error is bigger than `loss` value + function optimize(f : frac, loss : frac) : frac = + require(geq(loss, Zero), "negative loss optimize") + let s = sign(f) + mul(from_int(s), run_optimize(abs(f), loss)) + private function run_optimize(f : frac, loss : frac) : frac = + let t = make_frac((num(f) + 1) / 2, (den(f) + 1)/2) + if(gt(abs(sub(t, f)), loss)) f + elif (eq(t, f)) f + else run_optimize(t, loss) From bd7ed2ef8c57f5433512a9ab7e70883ce3d761a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Fri, 21 Feb 2020 10:28:55 +0100 Subject: [PATCH 02/14] Instant unification error on arguments count mismatch (#225) * Instant unification error on arguments count mismatch * add testcase * Add newline --- src/aeso_ast_infer_types.erl | 3 ++- test/aeso_compiler_tests.erl | 22 ++++++++++++++++++++++ test/contracts/bad_number_of_args.aes | 6 ++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 test/contracts/bad_number_of_args.aes diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index e5fcc68..868329c 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2072,7 +2072,8 @@ unify1(_Env, {qcon, _, Name}, {qcon, _, Name}, _When) -> true; unify1(_Env, {bytes_t, _, Len}, {bytes_t, _, Len}, _When) -> true; -unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) -> +unify1(Env, {fun_t, _, Named1, Args1, Result1}, {fun_t, _, Named2, Args2, Result2}, When) + when length(Args1) == length(Args2) -> unify(Env, Named1, Named2, When) andalso unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When); unify1(Env, {app_t, _, {Tag, _, F}, Args1}, {app_t, _, {Tag, _, F}, Args2}, When) diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index c45b9ec..ec75032 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -623,6 +623,28 @@ failing_contracts() -> "Empty record/map update\n" " r {}">> ]) + , ?TYPE_ERROR(bad_number_of_args, + [< unit\n" + " and (int) => 'a\n", + "when checking the application at line 3, column 39 of\n" + " f : () => unit\n" + "to arguments\n" + " 1 : int">>, + < 'e\n" + " and (int) => 'd\n" + "when checking the application at line 4, column 20 of\n" + " g : (int, string) => 'e\n" + "to arguments\n" + " 1 : int">>, + < 'c\n" + " and (string) => 'b\n" + "when checking the application at line 5, column 20 of\n" + " g : (int, string) => 'c\nto arguments\n" + " \"Litwo, ojczyzno moja\" : string">> + ]) ]. -define(Path(File), "code_errors/" ??File). diff --git a/test/contracts/bad_number_of_args.aes b/test/contracts/bad_number_of_args.aes new file mode 100644 index 0000000..3571cae --- /dev/null +++ b/test/contracts/bad_number_of_args.aes @@ -0,0 +1,6 @@ +contract Test = + entrypoint f() = () + entrypoint g(x : int, y : string) = f(1) + entrypoint h() = g(1) + entrypoint i() = g("Litwo, ojczyzno moja") + From d7fa4d65ecae956c0676442ca6f775c3075adace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Tue, 25 Feb 2020 12:56:51 +0100 Subject: [PATCH 03/14] More comments in stdlib (#237) --- priv/stdlib/Frac.aes | 21 ++++++++++++---- priv/stdlib/Func.aes | 51 +++++++++++++++++++++++++++++++-------- priv/stdlib/List.aes | 55 +++++++++++++++++++++++++++++++++--------- priv/stdlib/Option.aes | 22 +++++++++++++++-- priv/stdlib/Pair.aes | 6 +++++ priv/stdlib/Triple.aes | 12 +++++++++ 6 files changed, 139 insertions(+), 28 deletions(-) diff --git a/priv/stdlib/Frac.aes b/priv/stdlib/Frac.aes index 27ad531..1fdebb5 100644 --- a/priv/stdlib/Frac.aes +++ b/priv/stdlib/Frac.aes @@ -7,7 +7,10 @@ namespace Frac = datatype frac = Pos(int, int) | Zero | Neg(int, int) - // Checks if internal representation is correct. Numerator and denominator must be positive. +/** Checks if the internal representation is correct. + * Numerator and denominator must be positive. + * Exposed for debug purposes + */ function is_sane(f : frac) : bool = switch(f) Pos(n, d) => n > 0 && d > 0 Zero => true @@ -38,7 +41,8 @@ namespace Frac = Neg(n, d) => String.concat("-", to_str(Pos(n, d))) Zero => "0" - // Reduce fraction to normal form +/** Reduce fraction to normal form + */ function simplify(f : frac) : frac = switch(f) Neg(n, d) => @@ -49,6 +53,8 @@ namespace Frac = let cd = gcd(n, d) Pos(n / cd, d / cd) +/** Integer to rational division + */ function make_frac(n : int, d : int) : frac = if (d == 0) abort("Division by zero") elif (n == 0) Zero @@ -111,7 +117,9 @@ namespace Frac = Zero => 0 Neg(n, d) => -(n + d - 1) / d - // Round towards nearest integer. If two integers are in the same distance, choose the even one. +/** Round towards nearest integer. If two integers are in the same + * distance, choose the even one. + */ function round(f : frac) : int = let fl = floor(f) let cl = ceil(f) @@ -146,6 +154,8 @@ namespace Frac = function div(a : frac, b : frac) : frac = mul(a, inv(b)) +/** `b` to the power of `e` + */ function int_exp(b : frac, e : int) : frac = if (sign(b) == 0 && e == 0) abort("Zero to the zero exponentation") elif (e < 0) inv(int_exp_(b, -e)) @@ -158,8 +168,9 @@ namespace Frac = if (e mod 2 == 1) mul(mul(half, half), b) else mul(half, half) - // Reduces the fraction's in-memory size by dividing its components by two until the - // the error is bigger than `loss` value +/** Reduces the fraction's in-memory size by dividing its components by two until the + * the error is bigger than `loss` value + */ function optimize(f : frac, loss : frac) : frac = require(geq(loss, Zero), "negative loss optimize") let s = sign(f) diff --git a/priv/stdlib/Func.aes b/priv/stdlib/Func.aes index 7d633a2..42cef77 100644 --- a/priv/stdlib/Func.aes +++ b/priv/stdlib/Func.aes @@ -12,35 +12,66 @@ namespace Func = function rapply(x : 'a, f : 'a => 'b) : 'b = f(x) - /* The Z combinator - replacement for local and anonymous recursion. - */ +/** The Z combinator - replacement for local and anonymous recursion. + */ function recur(f : ('arg => 'res, 'arg) => 'res) : 'arg => 'res = (x) => f(recur(f), x) +/** n-times composition with itself + */ function iter(n : int, f : 'a => 'a) : 'a => 'a = iter_(n, f, (x) => x) private function iter_(n : int, f : 'a => 'a, acc : 'a => 'a) : 'a => 'a = if(n == 0) acc elif(n == 1) comp(f, acc) else iter_(n / 2, comp(f, f), if(n mod 2 == 0) acc else comp(f, acc)) - function curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c) = +/** Turns an ugly, bad and disgusting arity-n function into + * a beautiful and sweet function taking the first argument + * and returning a function watiting for the remaining ones + * in the same manner + */ + function curry2(f : ('a, 'b) => 'x) : 'a => ('b => 'x) = (x) => (y) => f(x, y) - function curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd)) = + function curry3(f : ('a, 'b, 'c) => 'x) : 'a => ('b => ('c => 'x)) = (x) => (y) => (z) => f(x, y, z) + function curry4(f : ('a, 'b, 'c, 'd) => 'x) : 'a => ('b => ('c => ('d => 'x))) = + (x) => (y) => (z) => (w) => f(x, y, z, w) + function curry5(f : ('a, 'b, 'c, 'd, 'e) => 'x) : 'a => ('b => ('c => ('d => ('e => 'x)))) = + (x) => (y) => (z) => (w) => (q) => f(x, y, z, w, q) - function uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c = +/** Opposite of curry. Gross + */ + function uncurry2(f : 'a => ('b => 'x)) : ('a, 'b) => 'x = (x, y) => f(x)(y) - function uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd = + function uncurry3(f : 'a => ('b => ('c => 'x))) : ('a, 'b, 'c) => 'x = (x, y, z) => f(x)(y)(z) + function uncurry4(f : 'a => ('b => ('c => ('d => 'x)))) : ('a, 'b, 'c, 'd) => 'x = + (x, y, z, w) => f(x)(y)(z)(w) + function uncurry5(f : 'a => ('b => ('c => ('d => ('e => 'x))))) : ('a, 'b, 'c, 'd, 'e) => 'x = + (x, y, z, w, q) => f(x)(y)(z)(w)(q) - function tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c = +/** Turns an arity-n function into a function taking n-tuple + */ + function tuplify2(f : ('a, 'b) => 'x) : (('a * 'b)) => 'x = (t) => switch(t) (x, y) => f(x, y) - function tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd = + function tuplify3(f : ('a, 'b, 'c) => 'x) : 'a * 'b * 'c => 'x = (t) => switch(t) (x, y, z) => f(x, y, z) + function tuplify4(f : ('a, 'b, 'c, 'd) => 'x) : 'a * 'b * 'c * 'd => 'x = + (t) => switch(t) + (x, y, z, w) => f(x, y, z, w) + function tuplify5(f : ('a, 'b, 'c, 'd, 'e) => 'x) : 'a * 'b * 'c * 'd * 'e => 'x = + (t) => switch(t) + (x, y, z, w, q) => f(x, y, z, w, q) - function untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c = +/** Opposite of tuplify + */ + function untuplify2(f : 'a * 'b => 'x) : ('a, 'b) => 'x = (x, y) => f((x, y)) - function untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd = + function untuplify3(f : 'a * 'b * 'c => 'x) : ('a, 'b, 'c) => 'x = (x, y, z) => f((x, y, z)) + function untuplify4(f : 'a * 'b * 'c * 'd => 'x) : ('a, 'b, 'c, 'd) => 'x = + (x, y, z, w) => f((x, y, z, w)) + function untuplify5(f : 'a * 'b * 'c * 'd * 'e => 'x) : ('a, 'b, 'c, 'd, 'e) => 'x = + (x, y, z, w, q) => f((x, y, z, w, q)) diff --git a/priv/stdlib/List.aes b/priv/stdlib/List.aes index 64c4fd5..3405035 100644 --- a/priv/stdlib/List.aes +++ b/priv/stdlib/List.aes @@ -19,10 +19,15 @@ namespace List = [x] => Some(x) _::t => last(t) +/** Finds first element of `l` fulfilling predicate `p` as `Some` or `None` + * if no such element exists. + */ function find(p : 'a => bool, l : list('a)) : option('a) = switch(l) [] => None h::t => if(p(h)) Some(h) else find(p, t) +/** Returns list of all indices of elements from `l` that fulfill the predicate `p`. + */ function find_indices(p : 'a => bool, l : list('a)) : list(int) = find_indices_(p, l, 0, []) private function find_indices_( p : 'a => bool , l : list('a) @@ -50,14 +55,22 @@ namespace List = _::t => length_(t, acc + 1) +/** Creates an ascending sequence of all integer numbers + * between `a` and `b` (including `a` and `b`) + */ function from_to(a : int, b : int) : list(int) = [a..b] +/** Creates an ascending sequence of integer numbers betweeen + * `a` and `b` jumping by given `step`. Includes `a` and takes + * `b` only if `(b - a) mod step == 0`. `step` should be bigger than 0. + */ function from_to_step(a : int, b : int, s : int) : list(int) = from_to_step_(a, b, 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) - /* Unsafe. Replaces `n`th element of `l` with `e`. Crashes on over/underflow */ +/** Unsafe. Replaces `n`th element of `l` with `e`. Crashes on over/underflow + */ function replace_at(n : int, e : 'a, l : list('a)) : list('a) = if(n<0) abort("insert_at underflow") else replace_at_(n, e, l, []) private function replace_at_(n : int, e : 'a, l : list('a), acc : list('a)) : list('a) = @@ -66,7 +79,8 @@ namespace List = h::t => if (n == 0) reverse(e::acc) ++ t else replace_at_(n-1, e, t, h::acc) - /* Unsafe. Adds `e` to `l` to be its `n`th element. Crashes on over/underflow */ +/** Unsafe. Adds `e` to `l` to be its `n`th element. Crashes on over/underflow + */ function insert_at(n : int, e : 'a, l : list('a)) : list('a) = if(n<0) abort("insert_at underflow") else insert_at_(n, e, l, []) private function insert_at_(n : int, e : 'a, l : list('a), acc : list('a)) : list('a) = @@ -75,6 +89,9 @@ namespace List = [] => abort("insert_at overflow") h::t => insert_at_(n-1, e, t, h::acc) +/** 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) = @@ -109,6 +126,8 @@ namespace List = [] => reverse(acc) h::t => map_(f, t, f(h)::acc) +/** Effectively composition of `map` and `flatten` + */ function flat_map(f : 'a => list('b), l : list('a)) : list('b) = ListInternal.flat_map(f, l) @@ -117,7 +136,8 @@ namespace List = [] => reverse(acc) h::t => filter_(p, t, if(p(h)) h::acc else acc) - /* Take `n` first elements */ +/** Take `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) = @@ -126,7 +146,8 @@ namespace List = [] => reverse(acc) h::t => take_(n-1, t, h::acc) - /* Drop `n` first elements */ +/** Drop `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 @@ -134,18 +155,23 @@ namespace List = [] => [] h::t => drop(n-1, t) - /* Get the longest prefix of a list in which every element matches predicate `p` */ +/** Get the longest prefix of a list in which every element + * matches predicate `p` + */ function take_while(p : 'a => bool, l : list('a)) : list('a) = 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) - /* Drop elements from `l` until `p` holds */ +/** Drop elements from `l` until `p` holds + */ function drop_while(p : 'a => bool, l : list('a)) : list('a) = switch(l) [] => [] h::t => if(p(h)) drop_while(p, t) else l - /* Splits list into two lists of elements that respectively match and don't match predicate `p` */ +/** Splits list into two lists of elements that respectively + * match and don't match predicate `p` + */ function partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) = partition_(p, l, [], []) private function partition_( p : 'a => bool , l : list('a) @@ -155,7 +181,8 @@ namespace List = [] => (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) - +/** Flattens list of lists into a single list + */ function flatten(ll : list(list('a))) : list('a) = foldr((l1, l2) => l1 ++ l2, [], ll) function all(p : 'a => bool, l : list('a)) : bool = switch(l) @@ -171,7 +198,9 @@ namespace List = function product(l : list(int)) : int = foldl((a, b) => a * b, 1, l) - /* Zips two list by applying bimapping function on respective elements. Drops longer tail. */ +/** Zips two list by applying bimapping function on respective elements. + * Drops longer tail. + */ 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 , l1 : list('a) @@ -181,7 +210,8 @@ namespace List = (h1::t1, h2::t2) => zip_with_(f, t1, t2, f(h1, h2)::acc) _ => reverse(acc) - /* Zips two lists into list of pairs. Drops longer tail. */ +/** Zips two lists into list of pairs. Drops longer tail. + */ 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, [], []) @@ -199,7 +229,8 @@ namespace List = h::t => switch (partition((x) => lesser_cmp(x, h), t)) (lesser, bigger) => sort(lesser_cmp, lesser) ++ h::sort(lesser_cmp, bigger) - +/** Puts `delim` between every two members of the list + */ function intersperse(delim : 'a, l : list('a)) : list('a) = intersperse_(delim, l, []) private function intersperse_(delim : 'a, l : list('a), acc : list('a)) : list('a) = switch(l) [] => reverse(acc) @@ -207,6 +238,8 @@ namespace List = h::t => intersperse_(delim, t, delim::h::acc) +/** 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) diff --git a/priv/stdlib/Option.aes b/priv/stdlib/Option.aes index 6ebf98c..485ace6 100644 --- a/priv/stdlib/Option.aes +++ b/priv/stdlib/Option.aes @@ -10,13 +10,18 @@ namespace Option = None => false Some(_) => true - +/** Catamorphism on `option`. Also known as inlined pattern matching. + */ function match(n : 'b, s : 'a => 'b, o : option('a)) : 'b = switch(o) None => n Some(x) => s(x) +/** Escape option providing default if `None` + */ function default(def : 'a, o : option('a)) : 'a = match(def, (x) => x, o) +/** Assume it is `Some` + */ function force(o : option('a)) : 'a = default(abort("Forced None value"), o) function on_elem(o : option('a), f : 'a => unit) : unit = match((), f, o) @@ -40,10 +45,14 @@ namespace Option = (Some(x1), Some(x2), Some(x3)) => Some(f(x1, x2, x3)) _ => None +/** Like `map`, but the function is in `option` + */ function app_over(f : option ('a => 'b), o : option('a)) : option('b) = switch((f, o)) (Some(ff), Some(xx)) => Some(ff(xx)) _ => None +/** Monadic bind + */ function flat_map(f : 'a => option('b), o : option('a)) : option('b) = switch(o) None => None Some(x) => f(x) @@ -53,22 +62,31 @@ namespace Option = None => [] Some(x) => [x] +/** Turns list of options into a list of elements that are under `Some`s. + * Safe. + */ function filter_options(l : list(option('a))) : list('a) = 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) +/** 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) - +/** Choose `Some` out of two if possible + */ function choose(o1 : option('a), o2 : option('a)) : option('a) = if(is_some(o1)) o1 else o2 +/** Choose `Some` from list of options if possible + */ function choose_first(l : list(option('a))) : option('a) = switch(l) [] => None None::t => choose_first(t) diff --git a/priv/stdlib/Pair.aes b/priv/stdlib/Pair.aes index 22312e3..16ec30f 100644 --- a/priv/stdlib/Pair.aes +++ b/priv/stdlib/Pair.aes @@ -6,12 +6,18 @@ namespace Pair = function snd(t : ('a * 'b)) : 'b = switch(t) (_, y) => y +/** Map over first + */ function map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) = switch(t) (x, y) => (f(x), y) +/** Map over second + */ function map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) = switch(t) (x, y) => (x, f(y)) +/** Map over both + */ function bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd) = switch(t) (x, y) => (f(x), g(y)) diff --git a/priv/stdlib/Triple.aes b/priv/stdlib/Triple.aes index 84f3ddd..07a716d 100644 --- a/priv/stdlib/Triple.aes +++ b/priv/stdlib/Triple.aes @@ -10,15 +10,23 @@ namespace Triple = (_, _, z) => z +/** Map over first + */ function map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) = switch(t) (x, y, z) => (f(x), y, z) +/** Map over second + */ function map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) = switch(t) (x, y, z) => (x, f(y), z) +/** Map over third + */ function map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) = switch(t) (x, y, z) => (x, y, f(z)) +/** Map over all elements + */ function trimap( f : 'a => 'x , g : 'b => 'y , h : 'c => 'z @@ -29,9 +37,13 @@ namespace Triple = function swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a) = switch(t) (x, y, z) => (z, y, x) +/** Right rotation + */ function rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) = switch(t) (x, y, z) => (z, x, y) +/** Left rotation + */ function rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a) = switch(t) (x, y, z) => (y, z, x) From 83e03f3013997bd094d74618fcae50389242dcfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Tue, 10 Mar 2020 12:39:39 +0100 Subject: [PATCH 04/14] Added documentation (#239) * Added documentation * Update readme * Update readme * Format fix * Events * Stdlib mention * Frac doc * Frac doc comparison warning * Typos * Format fix, TOC added * Fixed link * Update editor message * Split TOC * Moved out AEVM ABI * Minor format Co-Authored-By: Hans Svensson * Typo Co-Authored-By: Hans Svensson * Grammar Co-Authored-By: Hans Svensson * Language Co-authored-by: Hans Svensson --- README.md | 11 +- docs/sophia.md | 1064 ++++++++++++++++++++++++ docs/sophia_stdlib.md | 1845 +++++++++++++++++++++++++++++++++++++++++ priv/stdlib/Frac.aes | 18 +- 4 files changed, 2924 insertions(+), 14 deletions(-) create mode 100644 docs/sophia.md create mode 100644 docs/sophia_stdlib.md diff --git a/README.md b/README.md index 2f8bf0c..f4f9555 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,19 @@ This is the __sophia__ compiler for the æternity system which compiles contracts written in __sophia__ code to the æternity VM code. -For more information about æternity smart contracts and the sophia language see [Smart Contracts](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md) and the [Sophia Language](https://github.com/aeternity/protocol/blob/master/contracts/sophia.md). - It is an OTP application written in Erlang and is by default included in [the æternity node](https://github.com/aeternity/epoch). However, it can also be included in other systems to compile contracts coded in sophia which can then be loaded into the æternity system. + +## Documentation + +* [Smart Contracts on aeternity Blockchain](https://github.com/aeternity/protocol/blob/master/contracts/contracts.md). +* [Sophia Documentation](docs/sophia.md). +* [Sophia Standard Library](docs/sophia_stdlib.md). + + ## Versioning `aesophia` has a version that is only loosely connected to the version of the @@ -17,6 +23,7 @@ minor/patch version. The `aesophia` compiler version MUST be bumped whenever there is a change in how byte code is generated, but it MAY also be bumped upon API changes etc. + ## Interface Modules The basic modules for interfacing the compiler: diff --git a/docs/sophia.md b/docs/sophia.md new file mode 100644 index 0000000..3195870 --- /dev/null +++ b/docs/sophia.md @@ -0,0 +1,1064 @@ + + +**Table of Contents** + +- [-](#-) +- [Language Features](#language-features) + - [Contracts](#contracts) + - [Calling other contracts](#calling-other-contracts) + - [Mutable state](#mutable-state) + - [Stateful functions](#stateful-functions) + - [Payable](#payable) + - [Payable contracts](#payable-contracts) + - [Payable entrypoints](#payable-entrypoints) + - [Namespaces](#namespaces) + - [Splitting code over multiple files](#splitting-code-over-multiple-files) + - [Standard library](#standard-library) + - [Types](#types) + - [Literals](#literals) + - [Arithmetic](#arithmetic) + - [Bit fields](#bit-fields) + - [Type aliases](#type-aliases) + - [Algebraic data types](#algebraic-data-types) + - [Lists](#lists) + - [Maps and records](#maps-and-records) + - [Constructing maps and records](#constructing-maps-and-records) + - [Accessing values](#accessing-values) + - [Updating a value](#updating-a-value) + - [Map implementation](#map-implementation) + - [Strings](#strings) + - [Byte arrays](#byte-arrays) + - [Cryptographic builins](#cryptographic-builins) + - [AEVM note](#aevm-note) + - [Authorization interface](#authorization-interface) + - [Oracle interface](#oracle-interface) + - [Example](#example) + - [Sanity checks](#sanity-checks) + - [AENS interface](#aens-interface) + - [Events](#events) + - [Argument order](#argument-order) + - [Compiler pragmas](#compiler-pragmas) + - [Exceptions](#exceptions) + - [Syntax](#syntax) + - [Lexical syntax](#lexical-syntax) + - [Comments](#comments) + - [Keywords](#keywords) + - [Tokens](#tokens) + - [Layout blocks](#layout-blocks) + - [Notation](#notation) + - [Declarations](#declarations) + - [Types](#types-1) + - [Statements](#statements) + - [Expressions](#expressions) + - [Operators types](#operators-types) + - [Operator precendences](#operator-precendences) + - [Examples](#examples) + - [The lifetime of a contract](#the-lifetime-of-a-contract) + - [Killing a contract](#killing-a-contract) + +## The Sophia Language +An Æternity BlockChain Language + +The Sophia is a language in the ML family. It is strongly typed and has +restricted mutable state. + +Sophia is customized for smart contracts, which can be published +to a blockchain (the Æternity BlockChain). Thus some features of conventional +languages, such as floating point arithmetic, are not present in Sophia, and +some blockchain specific primitives, constructions and types have been added. +## Language Features +### Contracts + +The main unit of code in Sophia is the *contract*. + +- A contract implementation, or simply a contract, is the code for a + smart contract and consists of a list of types, entrypoints and local + functions. Only the entrypoints can be called from outside the contract. +- A contract instance is an entity living on the block chain (or in a state + channel). Each instance has an address that can be used to call its + entrypoints, either from another contract or in a call transaction. +- A contract may define a type `state` encapsulating its local + state. When creating a new contract the `init` entrypoint is executed and the + state is initialized to its return value. + +The language offers some primitive functions to interact with the blockchain and contracts. +Please refer to the [Chain](sophia_stdlib.md#Chain), [Contract](sophia_stdlib.md#Contract) +and the [Call](sophia_stdlib.md#Call) namespaces in the documentation. + +#### Calling other contracts + +To call a function in another contract you need the address to an instance of +the contract. The type of the address must be a contract type, which consists +of a number of type definitions and entrypoint declarations. For instance, + +```javascript +// A contract type +contract VotingType = + entrypoint vote : string => unit +``` + +Now given contract address of type `VotingType` you can call the `vote` +entrypoint of that contract: + +```javascript +contract VoteTwice = + entrypoint voteTwice(v : VotingType, alt : string) = + v.vote(alt) + v.vote(alt) +``` + +Contract calls take two optional named arguments `gas : int` and `value : int` +that lets you set a gas limit and provide tokens to a contract call. If omitted +the defaults are no gas limit and no tokens. Suppose there is a fee for voting: + +```javascript + entrypoint voteTwice(v : VotingType, fee : int, alt : string) = + v.vote(value = fee, alt) + v.vote(value = fee, alt) +``` + +Named arguments can be given in any order. + +Note that reentrant calls are not permitted. In other words, when calling +another contract it cannot call you back (directly or indirectly). + +To construct a value of a contract type you can give a contract address literal +(for instance `ct_2gPXZnZdKU716QBUFKaT4VdBZituK93KLvHJB3n4EnbrHHw4Ay`), or +convert an account address to a contract address using `Address.to_contract`. +Note that if the contract does not exist, or it doesn't have the entrypoint, or +the type of the entrypoint does not match the stated contract type, the call +fails. + +To recover the underlying `address` of a contract instance there is a field +`address : address`. For instance, to send tokens to the voting contract (given that it is payable) +without calling it you can write + +```javascript + entrypoint pay(v : VotingType, amount : int) = + Chain.spend(v.address, amount) +``` + +### Mutable state + +Sophia does not have arbitrary mutable state, but only a limited form of +state associated with each contract instance. + +- Each contract defines a type `state` encapsulating its mutable state. + The type `state` defaults to the `unit`. +- The initial state of a contract is computed by the contract's `init` + function. The `init` function is *pure* and returns the initial state as its + return value. + If the type `state` is `unit`, the `init` function defaults to returning the value `()`. + At contract creation time, the `init` function is executed and + its result is stored as the contract state. +- The value of the state is accessible from inside the contract + through an implicitly bound variable `state`. +- State updates are performed by calling a function `put : state => unit`. +- Aside from the `put` function (and similar functions for transactions + and events), the language is purely functional. +- Functions modifying the state need to be annotated with the `stateful` keyword (see below). + +To make it convenient to update parts of a deeply nested state Sophia +provides special syntax for map/record updates. + +#### Stateful functions + +Top-level functions and entrypoints must be annotated with the +`stateful` keyword to be allowed to affect the state of the running contract. +For instance, + +```javascript + stateful entrypoint set_state(s : state) = + put(s) +``` + +Without the `stateful` annotation the compiler does not allow the call to +`put`. A `stateful` annotation is required to + +* Use a stateful primitive function. These are + - `put` + - `Chain.spend` + - `Oracle.register` + - `Oracle.query` + - `Oracle.respond` + - `Oracle.extend` + - `AENS.preclaim` + - `AENS.claim` + - `AENS.transfer` + - `AENS.revoke` +* Call a `stateful` function in the current contract +* Call another contract with a non-zero `value` argument. + +A `stateful` annotation *is not* required to + +* Read the contract state. +* Issue an event using the `event` function. +* Call another contract with `value = 0`, even if the called function is stateful. + +### Payable + +#### Payable contracts + +A concrete contract is by default *not* payable. Any attempt at spending to such +a contract (either a `Chain.spend` or a normal spend transaction) will fail. If a +contract shall be able to receive funds in this way it has to be declared `payable`: + +```javascript +// A payable contract +payable contract ExampleContract = + stateful entrypoint do_stuff() = ... +``` + +If in doubt, it is possible to check if an address is payable using +`Address.is_payable(addr)`. + +#### Payable entrypoints + +A contract entrypoint is by default *not* payable. Any call to such a function +(either a [Remote call](#calling-other-contracts) or a contract call transaction) +that has a non-zero `value` will fail. Contract entrypoints that should be called +with a non-zero value should be declared `payable`. + +```javascript +payable stateful entrypoint buy(to : address) = + if(Call.value > 42) + transfer_item(to) + else + abort("Value too low") +``` + +Note: In the Aeternity VM (AEVM) contracts and entrypoints were by default +payable until the Lima release. + +### Namespaces + +Code can be split into libraries using the `namespace` construct. Namespaces +can appear at the top-level and can contain type and function definitions, but +not entrypoints. Outside the namespace you can refer to the (non-private) names +by qualifying them with the namespace (`Namespace.name`). +For example, + +``` +namespace Library = + type number = int + function inc(x : number) : number = x + 1 + +contract MyContract = + entrypoint plus2(x) : Library.number = + Library.inc(Library.inc(x)) +``` + +Functions in namespaces have access to the same environment (including the +`Chain`, `Call`, and `Contract`, builtin namespaces) as function in a contract, +with the exception of `state`, `put` and `Chain.event` since these are +dependent on the specific state and event types of the contract. + +### Splitting code over multiple files + + +Code from another file can be included in a contract using an `include` +statement. These must appear at the top-level (outside the main contract). The +included file can contain one or more namespaces and abstract contracts. For +example, if the file `library.aes` contains + +``` +namespace Library = + function inc(x) = x + 1 +``` + +you can use it from another file using an `include`: + +``` +include "library.aes" +contract MyContract = + entrypoint plus2(x) = Library.inc(Library.inc(x)) +``` + +This behaves as if the contents of `library.aes` was textually inserted into +the file, except that error messages will refer to the original source +locations. The language will try to include each file at most one time automatically, +so even cyclic includes should be working without any special tinkering. + +### Standard library + +Sophia offers [standard library](sophia_stdlib.md) which exposes some +primitive operations and some higher level utilities. The builtin +namespaces like `Chain`, `Contract`, `Map` +are included by default and are supported internally by the compiler. +Others like `List`, `Frac`, `Option` need to be manually included using the +`include` directive. For example +``` +include "List.aes" +include "Pair.aes" +-- Map is already there! + +namespace C = + entrypoint keys(m : map('a, 'b)) : list('a) = + List.map(Pair.fst, (Map.to_list(m))) +``` + +### Types +Sophia has the following types: + +| Type | Description | Example | +|----------------------|---------------------------------------------------------------------------------------------|--------------------------------------------------------------| +| int | A 2-complement integer | ```-1``` | +| address | Aeternity address, 32 bytes | ```Call.origin``` | +| bool | A Boolean | ```true``` | +| bits | A bit field | ```Bits.none``` | +| bytes(n) | A byte array with `n` bytes | ```#fedcba9876543210``` | +| string | An array of bytes | ```"Foo"``` | +| list | A homogeneous immutable singly linked list. | ```[1, 2, 3]``` | +| ('a, 'b) => 'c | A function. Parentheses can be skipped if there is only one argument | ```(x : int, y : int) => x + y``` | +| tuple | An ordered heterogeneous array | ```(42, "Foo", true)``` | +| record | An immutable key value store with fixed key names and typed values | ``` record balance = { owner: address, value: int } ``` | +| map | An immutable key value store with dynamic mapping of keys of one type to values of one type | ```type accounts = map(string, address)``` | +| option('a) | An optional value either None or Some('a) | ```Some(42)``` | +| state | A user defined type holding the contract state | ```record state = { owner: address, magic_key: bytes(4) }``` | +| event | An append only list of blockchain events (or log entries) | ```datatype event = EventX(indexed int, string)``` | +| hash | A 32-byte hash - equivalent to `bytes(32)` | | +| signature | A signature - equivalent to `bytes(64)` | | +| Chain.ttl | Time-to-live (fixed height or relative to current block) | ```FixedTTL(1050)``` ```RelativeTTL(50)``` | +| oracle('a, 'b) | And oracle answering questions of type 'a with answers of type 'b | ```Oracle.register(acct, qfee, ttl)``` | +| oracle_query('a, 'b) | A specific oracle query | ```Oracle.query(o, q, qfee, qttl, rttl)``` | +| contract | A user defined, typed, contract address | ```function call_remote(r : RemoteContract) = r.fun()``` | + +### Literals +| Type | Constant/Literal example(s) | +| ---------- | ------------------------------- | +| int | `-1`, `2425`, `4598275923475723498573485768` | +| address | `ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt` | +| bool | `true`, `false` | +| bits | `Bits.none`, `Bits.all` | +| bytes(8) | `#fedcba9876543210` | +| string | `"This is a string"` | +| list | `[1, 2, 3]`, `[(true, 24), (false, 19), (false, -42)]` | +| tuple | `(42, "Foo", true)` | +| record | `{ owner = Call.origin, value = 100000000 }` | +| map | `{["foo"] = 19, ["bar"] = 42}`, `{}` | +| option(int) | `Some(42)`, `None` | +| state | `state{ owner = Call.origin, magic_key = #a298105f }` | +| event | `EventX(0, "Hello")` | +| hash | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` | +| signature | `#000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f` | +| Chain.ttl | `FixedTTL(1050)`, `RelativeTTL(50)` | +| oracle('a, 'b) | `ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5` | +| oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` | +| contract | `ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ` | + +### Arithmetic + +Sophia integers (`int`) are represented by 256-bit (AEVM) or arbitrary-sized (FATE) signed words and supports the following +arithmetic operations: +- addition (`x + y`) +- subtraction (`x - y`) +- multiplication (`x * y`) +- division (`x / y`), truncated towards zero +- remainder (`x mod y`), satisfying `y * (x / y) + x mod y == x` for non-zero `y` +- exponentiation (`x ^ y`) + +All operations are *safe* with respect to overflow and underflow. On AEVM they behave as the corresponding +operations on arbitrary-size integers and fail with `arithmetic_error` if the +result cannot be represented by a 256-bit signed word. For example, `2 ^ 255` +fails rather than wrapping around to -2²⁵⁵. + +The division and modulo operations also throw an arithmetic error if the +second argument is zero. + +### Bit fields + +Sophia integers do not support bit arithmetic. Instead there is a separate +type `bits`. See the standard library [documentation](sophia_stdlib.md#Bits). + +On the AEVM a bit field is represented by a 256-bit word and reading or writing +a bit outside the 0..255 range fails with an `arithmetic_error`. On FATE a bit +field can be of arbitrary size (but it is still represented by the +corresponding integer, so setting very high bits can be expensive). + +### Type aliases + +Type aliases can be introduced with the `type` keyword and can be +parameterized. For instance + +``` +type number = int +type string_map('a) = map(string, 'a) +``` + +A type alias and its definition can be used interchangeably. Sophia does not support +higher-kinded types, meaning that following type alias is invalid: `type wrap('f, 'a) = 'f('a)` + +### Algebraic data types + +Sophia supports algebraic data types (variant types) and pattern matching. Data +types are declared by giving a list of constructors with +their respective arguments. For instance, + +``` +datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b) +``` + +Elements of data types can be pattern matched against, using the `switch` construct: + +``` +function get_left(x : one_or_both('a, 'b)) : option('a) = + switch(x) + Left(x) => Some(x) + Right(_) => None + Both(x, _) => Some(x) +``` + +or directly in the left-hand side: +``` +function + get_left : one_or_both('a, 'b) => option('a) + get_left(Left(x)) = Some(x) + get_left(Right(_)) = None + get_left(Both(x, _)) = Some(x) +``` + +*NOTE: Data types cannot currently be recursive.* + +### Lists + +A Sophia list is a dynamically sized, homogenous, immutable, singly +linked list. A list is constructed with the syntax `[1, 2, 3]`. The +elements of a list can be any of datatype but they must have the same +type. The type of lists with elements of type `'e` is written +`list('e)`. For example we can have the following lists: + +``` +[1, 33, 2, 666] : list(int) +[(1, "aaa"), (10, "jjj"), (666, "the beast")] : list(int * string) +[{[1] = "aaa", [10] = "jjj"}, {[5] = "eee", [666] = "the beast"}] : list(map(int, string)) +``` + +New elements can be prepended to the front of a list with the `::` +operator. So `42 :: [1, 2, 3]` returns the list `[42, 1, 2, 3]`. The +concatenation operator `++` appends its second argument to its first +and returns the resulting list. So concatenating two lists +`[1, 22, 33] ++ [10, 18, 55]` returns the list `[1, 22, 33, 10, 18, 55]`. + +Sophia supports list comprehensions known from languages like Python, Haskell or Erlang. +Example syntax: +``` +[x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]] +// yields [12,13,14,20,21,22,30,31,32] +``` + +Please refer to the [standard library](sophia_stdlib.md#List) for the predefined functionalities. + +### Maps and records + +A Sophia record type is given by a fixed set of fields with associated, +possibly different, types. For instance +``` + record account = { name : string, + balance : int, + history : list(transaction) } +``` + +Maps, on the other hand, can contain an arbitrary number of key-value bindings, +but of a fixed type. The type of maps with keys of type `'k` and values of type +`'v` is written `map('k, 'v)`. The key type can be any type that does not +contain a map or a function type. + +Please refer to the [standard library](sophia_stdlib.md#Map) for the predefined functionalities. + +#### Constructing maps and records + +A value of record type is constructed by giving a value for each of the fields. +For the example above, +``` + function new_account(name) = + {name = name, balance = 0, history = []} +``` +Maps are constructed similarly, with keys enclosed in square brackets +``` + function example_map() : map(string, int) = + {["key1"] = 1, ["key2"] = 2} +``` +The empty map is written `{}`. + +#### Accessing values + +Record fields access is written `r.f` and map lookup `m[k]`. For instance, +``` + function get_balance(a : address, accounts : map(address, account)) = + accounts[a].balance +``` +Looking up a non-existing key in a map results in contract execution failing. A +default value to return for non-existing keys can be provided using the syntax +`m[k = default]`. See also `Map.member` and `Map.lookup` below. + +#### Updating a value + +Record field updates are written `r{f = v}`. This creates a new record value +which is the same as `r`, but with the value of the field `f` replaced by `v`. +Similarly, `m{[k] = v}` constructs a map with the same values as `m` except +that `k` maps to `v`. It makes no difference if `m` has a mapping for `k` or +not. + +It is possible to give a name to the old value of a field or mapping in an +update: instead of `acc{ balance = acc.balance + 100 }` it is possible to write +`acc{ balance @ b = b + 100 }`, binding `b` to `acc.balance`. When giving a +name to a map value (`m{ [k] @ x = v }`), the corresponding key must be present +in the map or execution fails, but a default value can be provided: +`m{ [k = default] @ x = v }`. In this case `x` is bound to `default` if +`k` is not in the map. + +Updates can be nested: +``` +function clear_history(a : address, accounts : map(address, account)) : map(address, account) = + accounts{ [a].history = [] } +``` +This is equivalent to `accounts{ [a] @ acc = acc{ history = [] } }` and thus +requires `a` to be present in the accounts map. To have `clear_history` create +an account if `a` is not in the map you can write (given a function `empty_account`): +``` + accounts{ [a = empty_account()].history = [] } +``` + +#### Map implementation + +Internally in the VM maps are implemented as hash maps and support fast lookup +and update. Large maps can be stored in the contract state and the size of the +map does not contribute to the gas costs of a contract call reading or updating +it. + +### Strings + +There is a builtin type `string`, which can be seen as an array of bytes. +Strings can be compared for equality (`==`, `!=`), used as keys in maps and +records, and used in builtin functions `String.length`, `String.concat` and +the hash functions described below. + +Please refer to the `Map` [library documentation](sophia_stdlib.md#String). + +### Byte arrays + +Byte arrays are fixed size arrays of 8-bit integers. They are described in hexadecimal system, +for example the literal `#cafe` creates a two-element array of bytes `ca` (202) and `fe` (254) +and thus is a value of type `bytes(2)`. + +Please refer to the `Bytes` [library documentation](sophia_stdlib.md#Bytes). + + +### Cryptographic builins + +Libraries [Crypto](sophia_stdlib.md#Crypto) and [String](sophia_stdlib.md#String) provide functions to +hash objects, verify signatures etc. The `hash` is a type alias for `bytes(32)`. + +#### AEVM note +The hash functions in `String` hash strings interpreted as byte arrays, and +the `Crypto` hash functions accept an element of any (first-order) type. The +result is the hash of the binary encoding of the argument as [described +below](#encoding-sophia-values-as-binaries). Note that this means that for `s : +string`, `String.sha3(s)` and `Crypto.sha3(s)` will give different results on AEVM. + + +### Authorization interface + +When a Generalized account is authorized, the authorization function needs +access to the transaction 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`. + + +### Oracle interface +You can attach an oracle to the current contract and you can interact with oracles +through the Oracle interface. + +For a full description of how Oracle works see +[Oracles](https://github.com/aeternity/protocol/blob/master/oracles/oracles.md#oracles). +For a functionality documentation refer to the [standard library](sophia_stdlib.md#Oracle). + + +#### Example + +Example for an oracle answering questions of type `string` with answers of type `int`: +``` +contract Oracles = + + stateful entrypoint registerOracle(acct : address, + sign : signature, // Signed oracle address + contract address + qfee : int, + ttl : Chain.ttl) : oracle(string, int) = + Oracle.register(acct, signature = sign, qfee, ttl) + + entrypoint queryFee(o : oracle(string, int)) : int = + Oracle.query_fee(o) + + payable stateful entrypoint createQuery(o : oracle_query(string, int), + q : string, + qfee : int, + qttl : Chain.ttl, + rttl : int) : oracle_query(string, int) = + require(qfee =< Call.value, "insufficient value for qfee") + Oracle.query(o, q, qfee, qttl, RelativeTTL(rttl)) + + stateful entrypoint extendOracle(o : oracle(string, int), + ttl : Chain.ttl) : unit = + Oracle.extend(o, ttl) + + stateful entrypoint signExtendOracle(o : oracle(string, int), + sign : signature, // Signed oracle address + contract address + ttl : Chain.ttl) : unit = + Oracle.extend(o, signature = sign, ttl) + + stateful entrypoint respond(o : oracle(string, int), + q : oracle_query(string, int), + sign : signature, // Signed oracle query id + contract address + r : int) = + Oracle.respond(o, q, signature = sign, r) + + entrypoint getQuestion(o : oracle(string, int), + q : oracle_query(string, int)) : string = + Oracle.get_question(o, q) + + entrypoint hasAnswer(o : oracle(string, int), + q : oracle_query(string, int)) = + switch(Oracle.get_answer(o, q)) + None => false + Some(_) => true + + entrypoint getAnswer(o : oracle(string, int), + q : oracle_query(string, int)) : option(int) = + Oracle.get_answer(o, q) +``` + +#### Sanity checks + +When an Oracle literal is passed to a contract, no deep checks are performed. +For extra safety [Oracle.check](sophia_stdlib.md#check) and [Oracle.check_query](sophia_stdlib.md#check_query) +functions are provided. + +### AENS interface + +Contracts can interact with the +[Aeternity Naming System](https://github.com/aeternity/protocol/blob/master/AENS.md). +For this purpose the [AENS](sophia_stdlib.md#AENS) library was exposed. + + +### Events + +Sophia contracts log structured messages to an event log in the resulting +blockchain transaction. The event log is quite similar to [Events in +Solidity](https://solidity.readthedocs.io/en/v0.4.24/contracts.html#events). +Events are further discussed in the [protocol](https://github.com/aeternity/protocol/blob/master/contracts/events.md). + + +To use events a contract must declare a datatype `event`, and events are then +logged using the `Chain.event` function: + +``` + datatype event + = Event1(int, int, string) + | Event2(string, address) + + Chain.event(e : event) : unit +``` + +The event can have 0-3 *indexed* fields, and an optional *payload* field. A +field is indexed if it fits in a 32-byte word, i.e. +- `bool` +- `int` +- `bits` +- `address` +- `oracle(_, _)` +- `oracle_query(_, _)` +- contract types +- `bytes(n)` for `n` ≤ 32, in particular `hash` + +The payload field must be either a string or a byte array of more than 32 bytes. +The fields can appear in any order. + +*NOTE:* Indexing is not part of the core aeternity node. + +Events are emitted by using the `Chain.event` function. The following function +will emit one Event of each kind in the example. + +``` + entrypoint emit_events() : () = + Chain.event(TheFirstEvent(42)) + Chain.event(AnotherEvent(Contract.address, "This is not indexed")) +``` + +#### Argument order + +It is only possible to have one (1) `string` parameter in the event, but it can +be placed in any position (and its value will end up in the `data` field), i.e. +``` +AnotherEvent(string, indexed address) + +... + +Chain.event(AnotherEvent("This is not indexed", Contract.address)) +``` +would yield exactly the same result in the example above! + +### Compiler pragmas + +To enforce that a contract is only compiled with specific versions of the +Sophia compiler, you can give one or more `@compiler` pragmas at the +top-level (typically at the beginning) of a file. For instance, to enforce that +a contract is compiled with version 4.3 of the compiler you write + +``` +@compiler >= 4.3 +@compiler < 4.4 +``` + +Valid operators in compiler pragmas are `<`, `=<`, `==`, `>=`, and `>`. Version +numbers are given as a sequence of non-negative integers separated by dots. +Trailing zeros are ignored, so `4.0.0 == 4`. If a constraint is violated an +error is reported and compilation fails. + +### Exceptions + +Contracts can fail with an (uncatchable) exception using the built-in function + +``` +abort(reason : string) : 'a +``` + +Calling abort causes the top-level call transaction to return an error result +containing the `reason` string. Only the gas used up to and including the abort +call is charged. This is different from termination due to a crash which +consumes all available gas. + +For convenience the following function is also built-in: + +``` +function require(b : bool, err : string) = + if(!b) abort(err) +``` + +## Syntax + +### Lexical syntax + +#### Comments + +Single line comments start with `//` and block comments are enclosed in `/*` +and `*/` and can be nested. + +#### Keywords + +``` +contract elif else entrypoint false function if import include let mod namespace +private payable stateful switch true type record datatype +``` + +#### Tokens + +- `Id = [a-z_][A-Za-z0-9_']*` identifiers start with a lower case letter. +- `Con = [A-Z][A-Za-z0-9_']*` constructors start with an upper case letter. +- `QId = (Con\.)+Id` qualified identifiers (e.g. `Map.member`) +- `QCon = (Con\.)+Con` qualified constructor +- `TVar = 'Id` type variable (e.g `'a`, `'b`) +- `Int = [0-9]+(_[0-9]+)*|0x[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` integer literal with optional `_` separators +- `Bytes = #[0-9A-Fa-f]+(_[0-9A-Fa-f]+)*` byte array literal with optional `_` separators +- `String` string literal enclosed in `"` with escape character `\` +- `Char` character literal enclosed in `'` with escape character `\` +- `AccountAddress` base58-encoded 32 byte account pubkey with `ak_` prefix +- `ContractAddress` base58-encoded 32 byte contract address with `ct_` prefix +- `OracleAddress` base58-encoded 32 byte oracle address with `ok_` prefix +- `OracleQueryId` base58-encoded 32 byte oracle query id with `oq_` prefix + +Valid string escape codes are + +| Escape | ASCII | | +|---------------|-------------|---| +| `\b` | 8 | | +| `\t` | 9 | | +| `\n` | 10 | | +| `\v` | 11 | | +| `\f` | 12 | | +| `\r` | 13 | | +| `\e` | 27 | | +| `\xHexDigits` | *HexDigits* | | + + +See the [identifier encoding scheme](https://github.com/aeternity/protocol/blob/master/node/api/api_encoding.md) for the +details on the base58 literals. + +### Layout blocks + +Sophia uses Python-style layout rules to group declarations and statements. A +layout block with more than one element must start on a separate line and be +indented more than the currently enclosing layout block. Blocks with a single +element can be written on the same line as the previous token. + +Each element of the block must share the same indentation and no part of an +element may be indented less than the indentation of the block. For instance + +``` +contract Layout = + function foo() = 0 // no layout + function bar() = // layout block starts on next line + let x = foo() // indented more than 2 spaces + x + + 1 // the '+' is indented more than the 'x' +``` + +### Notation + +In describing the syntax below, we use the following conventions: +- Upper-case identifiers denote non-terminals (like `Expr`) or terminals with + some associated value (like `Id`). +- Keywords and symbols are enclosed in single quotes: `'let'` or `'='`. +- Choices are separated by vertical bars: `|`. +- Optional elements are enclosed in `[` square brackets `]`. +- `(` Parentheses `)` are used for grouping. +- Zero or more repetitions are denoted by a postfix `*`, and one or more + repetitions by a `+`. +- `Block(X)` denotes a layout block of `X`s. +- `Sep(X, S)` is short for `[X (S X)*]`, i.e. a possibly empty sequence of `X`s + separated by `S`s. +- `Sep1(X, S)` is short for `X (S X)*`, i.e. same as `Sep`, but must not be empty. + + +### Declarations + +A Sophia file consists of a sequence of *declarations* in a layout block. + +```c +File ::= Block(Decl) +Decl ::= ['payable'] 'contract' Con '=' Block(Decl) + | 'namespace' Con '=' Block(Decl) + | '@compiler' PragmaOp Version + | 'include' String + | 'type' Id ['(' TVar* ')'] ['=' TypeAlias] + | 'record' Id ['(' TVar* ')'] '=' RecordType + | 'datatype' Id ['(' TVar* ')'] '=' DataType + | EModifier* ('entrypoint' | 'function') Block(FunDecl) + +FunDecl ::= Id ':' Type // Type signature + | Id Args [':' Type] '=' Block(Stmt) // Definition + +PragmaOp ::= '<' | '=<' | '==' | '>=' | '>' +Version ::= Sep1(Int, '.') + +EModifier ::= 'payable' | 'stateful' +FModifier ::= 'stateful' | 'private' + +Args ::= '(' Sep(Pattern, ',') ')' +``` + +Contract declarations must appear at the top-level. + +For example, +``` +contract Test = + type t = int + entrypoint add (x : t, y : t) = x + y +``` + +There are three forms of type declarations: type aliases (declared with the +`type` keyword), record type definitions (`record`) and data type definitions +(`datatype`): + +```c +TypeAlias ::= Type +RecordType ::= '{' Sep(FieldType, ',') '}' +DataType ::= Sep1(ConDecl, '|') + +FieldType ::= Id ':' Type +ConDecl ::= Con ['(' Sep1(Type, ',') ')'] +``` + +For example, +``` +record point('a) = {x : 'a, y : 'a} +datatype shape('a) = Circle(point('a), 'a) | Rect(point('a), point('a)) +type int_shape = shape(int) +``` + +### Types + +```c +Type ::= Domain '=>' Type // Function type + | Type '(' Sep(Type, ',') ')' // Type application + | '(' Type ')' // Parens + | 'unit' | Sep(Type, '*') // Tuples + | Id | QId | TVar + +Domain ::= Type // Single argument + | '(' Sep(Type, ',') ')' // Multiple arguments +``` + +The function type arrow associates to the right. + +Example, +``` +'a => list('a) => (int * list('a)) +``` + +### Statements + +Function bodies are blocks of *statements*, where a statement is one of the following + +```c +Stmt ::= 'switch' '(' Expr ')' Block(Case) + | 'if' '(' Expr ')' Block(Stmt) + | 'elif' '(' Expr ')' Block(Stmt) + | 'else' Block(Stmt) + | 'let' LetDef + | Expr + +LetDef ::= Id Args [':' Type] '=' Block(Stmt) // Function definition + | Pattern '=' Block(Stmt) // Value definition + +Case ::= Pattern '=>' Block(Stmt) +Pattern ::= Expr +``` + +`if` statements can be followed by zero or more `elif` statements and an optional final `else` statement. For example, + +``` +let x : int = 4 +switch(f(x)) + None => 0 + Some(y) => + if(y > 10) + "too big" + elif(y < 3) + "too small" + else + "just right" +``` + +### Expressions + +```c +Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + 1 + | 'if' '(' Expr ')' Expr 'else' Expr // If expression if(x < y) y else x + | Expr ':' Type // Type annotation 5 : int + | Expr BinOp Expr // Binary operator x + y + | UnOp Expr // Unary operator ! b + | Expr '(' Sep(Expr, ',') ')' // Application f(x, y) + | Expr '.' Id // Projection state.x + | Expr '[' Expr ']' // Map lookup map[key] + | Expr '{' Sep(FieldUpdate, ',') '}' // Record or map update r{ fld[key].x = y } + | '[' Sep(Expr, ',') ']' // List [1, 2, 3] + | '[' Expr '|' Sep(Generator, ',') ']' + // List comprehension [k | x <- [1], if (f(x)), let k = x+1] + | '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val} + | '(' Expr ')' // Parens (1 + 2) * 3 + | Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token + | Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%' + | AccountAddress | ContractAddress // Chain identifiers + | OracleAddress | OracleQueryId // Chain identifiers + +Generator ::= Pattern '<-' Expr // Generator + | 'if' '(' Expr ')' // Guard + | LetDef // Definition + +LamArgs ::= '(' Sep(LamArg, ',') ')' +LamArg ::= Id [':' Type] + +FieldUpdate ::= Path '=' Expr +Path ::= Id // Record field + | '[' Expr ']' // Map key + | Path '.' Id // Nested record field + | Path '[' Expr ']' // Nested map key + +BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!=' + | '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^' +UnOp ::= '-' | '!' +``` + +### Operators types + +| Operators | Type +| --- | --- +| `-` `+` `*` `/` `mod` `^` | arithmetic operators +| `!` `&&` `\|\|` | logical operators +| `==` `!=` `<` `>` `=<` `>=` | comparison operators +| `::` `++` | list operators + +### Operator precendences + +In order of highest to lowest precedence. + +| Operators | Associativity +| --- | --- +| `!` | right +| `^` | left +| `*` `/` `mod` | left +| `-` (unary) | right +| `+` `-` | left +| `::` `++` | right +| `<` `>` `=<` `>=` `==` `!=` | none +| `&&` | right +| `\|\|` | right + +## Examples + +``` +/* + * A simple crowd-funding example + */ +contract FundMe = + + record spend_args = { recipient : address, + amount : int } + + record state = { contributions : map(address, int), + total : int, + beneficiary : address, + deadline : int, + goal : int } + + function spend(args : spend_args) = + raw_spend(args.recipient, args.amount) + + entrypoint init(beneficiary, deadline, goal) : state = + { contributions = {}, + beneficiary = beneficiary, + deadline = deadline, + total = 0, + goal = goal } + + function is_contributor(addr) = + Map.member(addr, state.contributions) + + stateful entrypoint contribute() = + if(Chain.block_height >= state.deadline) + spend({ recipient = Call.caller, amount = Call.value }) // Refund money + false + else + let amount = + switch(Map.lookup(Call.caller, state.contributions)) + None => Call.value + Some(n) => n + Call.value + put(state{ contributions[Call.caller] = amount, + total @ tot = tot + Call.value }) + true + + stateful entrypoint withdraw() = + if(Chain.block_height < state.deadline) + abort("Cannot withdraw before deadline") + if(Call.caller == state.beneficiary) + withdraw_beneficiary() + elif(is_contributor(Call.caller)) + withdraw_contributor() + else + abort("Not a contributor or beneficiary") + + stateful function withdraw_beneficiary() = + require(state.total >= state.goal, "Project was not funded") + spend({recipient = state.beneficiary, + amount = Contract.balance }) + put(state{ beneficiary = #0 }) + + stateful function withdraw_contributor() = + if(state.total >= state.goal) + abort("Project was funded") + let to = Call.caller + spend({recipient = to, + amount = state.contributions[to]}) + put(state{ contributions @ c = Map.delete(to, c) }) +``` diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md new file mode 100644 index 0000000..df13842 --- /dev/null +++ b/docs/sophia_stdlib.md @@ -0,0 +1,1845 @@ + + +# Standard library + +Sophia language offers standard library that consists of several namespaces. Some of them are already +in the scope and do not need any actions to be used, while the others require some files to be included. + +The out-of-the-box namespaces are: + +- [Bits](#Bits) +- [String](#String) +- [Bytes](#Bytes) +- [Int](#Int) +- [Map](#Map) +- [Address](#Address) +- [Crypto](#Crypto) +- [Auth](#Auth) +- [Oracle](#Oracle) +- [AENS](#AENS) +- [Contract](#Contract) +- [Call](#Call) +- [Chain](#Chain) + +The following ones need to be included as regular files with `.aes` suffix, for example +``` +include "List.aes" +``` + +- [List](#List) +- [Option](#Option) +- [Func](#Func) +- [Pair](#Pair) +- [Triple](#Triple) +- [BLS12_381](#BLS12_381) +- [Frac](#Frac) + +# Builtin namespaces + +They are available without any explicit includes. + +## Bits + +### none +``` +Bits.none : bits +``` + +A bit field with all bits cleared + + +### all +``` +Bits.all : bits +``` + +A bit field with all bits set + + +### set +``` +Bits.set(b : bits, i : int) : bits +``` + +Set bit i + + +### clear +``` +Bits.clear(b : bits, i : int) : bits +``` + +Clear bit i + + +### test +``` +Bits.test(b : bits, i : int) : bool +``` + +Check if bit i is set + + +### sum +``` +Bits.sum(b : bits) : int +``` + +Count the number of set bits + + +### union +``` +Bits.union(a : bits, b : bits) : bits +``` + +Bitwise disjunction + + +### intersection +``` +Bits.intersection(a : bits, b : bits) : bits +``` + +Bitwise conjunction + + +### difference +``` +Bits.difference(a : bits, b : bits) : bits +``` + +Each bit is true if and only if it was 1 in `a` and 0 in `b` + + +## 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 +``` +Bytes.to_int(b : bytes(n)) : int +``` + +Interprets the byte array as a big endian integer + + +### to_str +``` +Bytes.to_str(b : bytes(n)) : string +``` + +Returns the hexadecimal representation of the byte array + + +### concat +``` +Bytes.concat : (a : bytes(m), b : bytes(n)) => bytes(m + n) +``` + +Concatenates two byte arrays + + +### split +``` +Bytes.split(a : bytes(m + n)) : bytes(m) * bytes(n) +``` + +Splits a byte array at given index + + +## Int + +### to_str +``` +Int.to_str : int => string +``` + +Casts integer to string using decimal representation + + +## Map + +### lookup +`Map.lookup(k : 'k, m : map('k, 'v)) : option('v)` + +Returns the value under a key in given map as `Some` or `None` +if the key is not present + + +### lookup_default +`Map.lookup_default(k : 'k, m : map('k, 'v), v : 'v) : 'v` + +Returns the value under a key in given map or the +default value `v` if the key is not present + + +### member +`Map.member(k : 'k, m : map('k, 'v)) : bool` + +Checks if the key is present in the map + + +### delete +`Map.delete(k : 'k, m : map('k, 'v)) : map('k, 'v)` + +Removes the key from the map + + +### size +`Map.size(m : map('k, 'v)) : int` + +Returns the number of elements in the map + + +### to_list +`Map.to_list(m : map('k, 'v)) : list('k * 'v)` + +Returns a list containing pairs of keys and their respective elements. + + +### from_list +`Map.from_list(m : list('k * 'v)) : map('k, 'v)` + +Turns a list of pairs of form `(key, value)` into a map + + + +## Address + +### to_str +``` +Address.to_str(a : address) : string +``` + +Base58 encoded string + + +### is_contract +``` +Address.is_contract(a : address) : bool +``` + +Is the address a contract + + +### is_oracle +``` +Address.is_oracle(a : address) : bool +``` + +Is the address a registered oracle + + +### is_payable +``` +Address.is_payable(a : address) : bool +``` + +Can the address be spent to + + +### to_contract +``` +Address.to_contract(a : address) : C +``` + +Cast address to contract type C (where `C` is a contract) + + +## Crypto + +### sha3 +``` +Crypto.sha3(x : 'a) : hash +``` + +Hash any object to SHA3 + + +### sha256 +``` +Crypto.sha256(x : 'a) : hash +``` + +Hash any object to SHA256 + + +### blake2b +``` +Crypto.blake2b(x : 'a) : hash +``` + +Hash any object to blake2b + + +### verify_sig +``` +Crypto.verify_sig(msg : hash, pubkey : address, sig : signature) : bool +``` + +Checks if the signature of `msg` was made using private key corresponding to +the `pubkey` + +### ecverify_secp256k1 +``` +Crypto.ecverify_secp256k1(msg : hash, addr : bytes(20), sig : bytes(65)) : bool +``` + +Verifies a signature for a msg against an Ethereum style address + + +### ecrecover_secp256k1 +``` +Crypto.ecrecover_secp256k1(msg : hash, sig : bytes(65)) : option(bytes(20)) +``` + +Recovers the Ethereum style address from a msg hash and respective signature + + +### verify_sig_secp256k1 +``` +Crypto.verify_sig_secp256k1(msg : hash, pubkey : bytes(64), sig : bytes(64)) : bool +``` + + + +## Auth + +### tx_hash +``` +Auth.tx_hash : option(hash) +``` + +Gets the transaction hash during authentication. + +## Oracle + +### register +``` +Oracle.register(, acct : address, qfee : int, ttl : Chain.ttl) : oracle('a, 'b) +``` + +Registers new oracle answering questions of type `'a` with answers of type `'b`. + +* The `acct` is the address of the oracle to register (can be the same as the contract). +* `signature` is a signature proving that the contract is allowed to register the account - + the account address + the contract address (concatenated as byte arrays) is + signed with the + private key of the account, proving you have the private key of the oracle to be. If the + address is the same as the contract `sign` is ignored and can be left out entirely. +* The `qfee` is the minimum query fee to be paid by a user when asking a question of the oracle. +* The `ttl` is the Time To Live for the oracle, either relative to the current + height (`RelativeTTL(delta)`) or a fixed height (`FixedTTL(height)`). +* The type `'a` is the type of the question to ask. +* The type `'b` is the type of the oracle answers. + +Examples: +``` + Oracle.register(addr0, 25, RelativeTTL(400)) + Oracle.register(addr1, 25, RelativeTTL(500), signature = sign1) +``` + + +### get_question +``` +Oracle.get_question(o : oracle('a, 'b), q : oracle_query('a, 'b)) : 'a +``` + +Checks what was the question of query `q` on oracle `o` + + +### respond +``` +Oracle.respond(, o : oracle('a, 'b), q : oracle_query('a, 'b), 'b) : unit +``` + +Responds to the question `q` on `o`. +Unless the contract address is the same as the oracle address the `signature` +(which is an optional, named argument) +needs to be provided. Proving that we have the private key of the oracle by +signing the oracle query id + contract address + + +### extend +``` +Oracle.extend(, o : oracle('a, 'b), ttl : Chain.ttl) : unit +``` + +Extends TTL of an oracle. +* `singature` is a named argument and thus optional. Must be the same as for `Oracle.register` +* `o` is the oracle being extended +* `ttl` must be `RelativeTTL`. The time to live of `o` will be extended by this value. + +### query_fee +``` +Oracle.query_fee(o : oracle('a, 'b)) : int +``` + +Returns the query fee of the oracle + + +### query +``` +Oracle.query(o : oracle('a, 'b), q : 'a, qfee : int, qttl : Chain.ttl, rttl : Chain.ttl) : oracle_query('a, 'b) +``` + +Asks the oracle a question. +* The `qfee` is the query fee debited to the contract account (`Contract.address`). +* The `qttl` controls the last height at which the oracle can submit a response + and can be either fixed or relative. +* The `rttl` must be relative and controls how long an answer is kept on the chain. +The call fails if the oracle could expire before an answer. + + +### get_answer +``` +Oracle.get_answer(o : oracle('a, 'b), q : oracle_query('a, 'b)) : option('b) +``` + +Checks what is the optional query answer + + +### check +``` +Oracle.check(o : oracle('a, 'b)) : bool +``` + +Returns `true` iff the oracle `o` exists and has correct type + + +### check_query +``` +Oracle.check_query(o : oracle('a, 'b), q : oracle_query('a, 'b)) : bool +``` + +It returns `true` iff the oracle query exist and has the expected type. + + +## AENS + +The following functionality is available for interacting with the Aeternity +Naming System (AENS). +If `owner` is equal to `Contract.address` the signature `signature` is +ignored, and can be left out since it is a named argument. Otherwise we need +a signature to prove that we are allowed to do AENS operations on behalf of +`owner` + +### resolve +``` +AENS.resolve(name : string, key : string) : option('a) +``` + +Name resolution. Here `name` should be a registered name and `key` one of the attributes +associated with this name (for instance `"account_pubkey"`). The return type +(`'a`) must be resolved at compile time to an atomic type and the value is +type checked against this type at run time. + + +### preclaim +``` +AENS.preclaim(owner : address, commitment_hash : hash, ) : unit +``` + +The signature should be over `owner address` + `Contract.address` +(concatenated as byte arrays). + + +### claim +``` +AENS.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit +``` + +The signature should be over `owner address` + `name_hash` + `Contract.address` +using the private key of the `owner` account for signing. + + +### transfer +``` +AENS.transfer(owner : address, new_owner : address, name_hash : hash, ) : unit +``` + +Transfers name to the new owner. + +The signature should be over `owner address` + `name_hash` + `Contract.address` +using the private key of the `owner` account for signing. + + +### revoke +``` +AENS.revoke(owner : address, name_hash : hash, ) : unit +``` + +Revokes the name to extend the ownership time. + +The signature should be over `owner address` + `name_hash` + `Contract.address` +using the private key of the `owner` account for signing. + + +## Contract + +Values related to the current contract + +### creator +``` +Contract.creator : address +``` + +Address of the entity that signed the contract creation transaction + + +### address +``` +Contract.address : address +``` + +Address of the contract account + + +### balance +``` +Contract.balance : int +``` + +Amount of coins in the contract account + + +## Call + +Values related to the call to the current contract + +### origin +``` +Call.origin : address +``` + +The address of the account that signed the call transaction that led to this call. + + +### caller +``` +Call.caller : address +``` + +The address of the entity (possibly another contract) calling the contract. + +### value +``` +Call.value : int +``` + +The amount of coins transferred to the contract in the call. + + +### gas +``` +Call.gas_price : int +``` + +The gas price of the current call. + + +### gas +``` +Call.gas_left() : int +``` + +The amount of gas left for the current call. + + +## Chain + +Values and functions related to the chain itself and other entities that live on it. + +### balance +``` +Chain.balance(a : address) : int +``` + +The balance of account `a`. + + +### block_hash +``` +Chain.block_hash(h : int) : option(bytes(32)) +``` + +The hash of the block at height `h`. + + +### block_height +``` +Chain.block_height : int" +``` + +The height of the current block (i.e. the block in which the current call will be included). + + +### coinbase +``` +Chain.coinbase : address +``` + +The address of the account that mined the current block. + + +### timestamp +``` +Chain.timestamp : int +``` + +The timestamp of the current block. + + +### difficulty +``` +Chain.difficulty : int +``` + +The difficulty of the current block. + + +### gas +``` +Chain.gas_limit : int +``` + +The gas limit of the current block. + + +### event +``` +Chain.event(e : event) : unit +``` +Emits the event. To use this function one needs to define the `event` type as a `datatype` in the contract. + + +# Includable namespaces + +These need to be explicitly included (with `.aes` suffix) + +## List + +This module contains common operations on lists like constructing, querying, traversing etc. + +### is_empty +``` +List.is_empty(l : list('a)) : bool +``` + +Returns `true` iff the list is equal to `[]`. + + +### first +``` +List.first(l : list('a)) : option('a) +``` + +Returns `Some` of the first element of a list or `None` if the list is empty. + + +### tail +``` +List.tail(l : list('a)) : option(list('a)) +``` + +Returns `Some` of a list without its first element or `None` if the list is empty. + + +### last +``` +List.last(l : list('a)) : option('a) +``` + +Returns `Some` of the last element of a list or `None` if the list is empty. + + +### find +``` +List.find(p : 'a => bool, l : list('a)) : option('a) +``` + +Finds first element of `l` fulfilling predicate `p` as `Some` or `None` if no such element exists. + + +### find_indices +``` +List.find_indices(p : 'a => bool, l : list('a)) : list(int) +``` + +Returns list of all indices of elements from `l` that fulfill the predicate `p`. + + +### nth +``` +List.nth(n : int, l : list('a)) : option('a) +``` + +Gets `n`th element of `l` as `Some` or `None` if `l` is shorter than `n + 1` or `n` is negative. + + +### get +``` +List.get(n : int, l : list('a)) : 'a +``` + +Gets `n`th element of `l` forcefully, throwing and error if `l` is shorter than `n + 1` or `n` is negative. + + +### length +``` +List.length(l : list('a)) : int +``` + +Returns length of a list. + + +### from_to +``` +List.from_to(a : int, b : int) : list(int) +``` + +Creates an ascending sequence of all integer numbers between `a` and `b` (including `a` and `b`). + + +### from_to_step +``` +List.from_to_step(a : int, b : int, step : int) : list(int) +``` + +Creates an ascending sequence of integer numbers betweeen `a` and `b` jumping by given `step`. Includes `a` and takes `b` only if `(b - a) mod step == 0`. `step` should be bigger than 0. + + +### replace_at +``` +List.replace_at(n : int, e : 'a, l : list('a)) : list('a) +``` + +Replaces `n`th element of `l` with `e`. Throws an error if `n` is negative or would cause an overflow. + + +### insert_at +``` +List.insert_at(n : int, e : 'a, l : list('a)) : list('a) +``` + +Inserts `e` into `l` to be on position `n` by shifting following elements further. For instance, +``` +insert_at(2, 9, [1,2,3,4]) +``` +will yield `[1,2,9,3,4]`. + + +### insert_by +``` +List.insert_by(cmp : (('a, 'a) => bool), x : 'a, l : list('a)) : list('a) +``` + +Assuming that cmp represents `<` comparison, inserts `x` before the first element in the list `l` which is greater than it. For instance, +``` +insert_by((a, b) => a < b, 4, [1,2,3,5,6,7]) +``` +will yield `[1,2,3,4,5,6,7]` + + +### foldr +``` +List.foldr(cons : ('a, 'b) => 'b, nil : 'b, l : list('a)) : 'b +``` + +Right fold of a list. Assuming `l = [x, y, z]` will return `f(x, f(y, f(z, nil)))`. +Not tail recursive. + + +### foldl +``` +List.foldl(rcons : ('b, 'a) => 'b, acc : 'b, l : list('a)) : 'b +``` + +Left fold of a list. Assuming `l = [x, y, z]` will return `f(f(f(acc, x), y), z)`. +Tail recursive. + +### foreach +``` +List.foreach(l : list('a), f : 'a => unit) : unit +``` + +Evaluates `f` on each element of a list. + + +### reverse +``` +List.reverse(l : list('a)) : list('a) +``` + +Returns a copy of `l` with reversed order of elements. + + +### map +``` +List.map(f : 'a => 'b, l : list('a)) : list('b) +``` + +Maps function `f` over a list. For instance +``` +map((x) => x == 0, [1, 2, 0, 3, 0]) +``` +will yield `[false, false, true, false, true]` + + +### flat_map +``` +List.flat_map(f : 'a => list('b), l : list('a)) : list('b) +``` + +Maps `f` over a list and then flattens it. For instance +``` +flat_map((x) => [x, x * 10], [1, 2, 3]) +``` +will yield `[1, 10, 2, 20, 3, 30]` + + +### filter +``` +List.filter(p : 'a => bool, l : list('a)) : list('a) +``` + +Filters out elements of `l` that fulfill predicate `p`. For instance +``` +filter((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3]) +``` +will yield `[1, 1, 2]` + + +### take +``` +List.take(n : int, l : list('a)) : list('a) +``` + +Takes `n` first elements of `l`. Fails if `n` is negative. If `n` is greater than length of a list it will return whole list. + + +### drop +``` +List.drop(n : int, l : list('a)) : list('a) +``` + +Removes `n` first elements of `l`. Fails if `n` is negative. If `n` is greater than length of a list it will return `[]`. + + +### take_while +``` +List.take_while(p : 'a => bool, l : list('a)) : list('a) +``` + +Returns longest prefix of `l` in which all elements fulfill `p`. + + +### drop_while +``` +List.drop_while(p : 'a => bool, l : list('a)) : list('a) +``` + +Removes longest prefix from `l` in which all elements fulfill `p`. + + +### partition +``` +List.partition(p : 'a => bool, l : list('a)) : (list('a) * list('a)) +``` + +Separates elements of `l` that fulfill `p` and these that do not. Elements fulfilling predicate will be in the right list. For instance +``` +partition((x) => x > 0, [-1, 1, -2, 0, 1, 2, -3]) +``` +will yield `([1, 1, 2], [-1, -2, 0, -3])` + + +### flatten +``` +List.flatten(ll : list(list('a))) : list('a) +``` + +Flattens a list of lists into a one list. + + +### all +``` +List.all(p : 'a => bool, l : list('a)) : bool +``` + +Checks if all elements of a list fulfill predicate `p`. + + +### any +``` +List.any(p : 'a => bool, l : list('a)) : bool +``` + +Checks if any element of a list fulfills predicate `p`. + + +### sum +``` +List.sum(l : list(int)) : int +``` + +Sums elements of a list. Returns 0 if the list is empty. + + +### product +``` +List.product(l : list(int)) : int +``` + +Multiplies elements of a list. Returns 1 if the list is empty. + + +### zip_with +``` +List.zip_with(f : ('a, 'b) => 'c, l1 : list('a), l2 : list('b)) : list('c) +``` + +"zips" two lists with a function. n-th element of resulting list will be equal to `f(x1, x2)` where `x1` and `x2` are n-th elements of `l1` and `l2` respectively. Will cut off the tail of the longer list. For instance +``` +zip_with((a, b) => a + b, [1,2], [1,2,3]) +``` +will yield `[2,4]` + + +### zip +``` +List.zip(l1 : list('a), l2 : list('b)) : list('a * 'b) +``` + +Special case of [zip_with](#zip_with) where the zipping function is `(a, b) => (a, b)`. + +### unzip +``` +List.unzip(l : list('a * 'b)) : list('a) * list('b) +``` + +Opposite to the `zip` operation. Takes a list of pairs and returns pair of lists with respective elements on same indices. + + +### sort +``` +List.sort(lesser_cmp : ('a, 'a) => bool, l : list('a)) : list('a) +``` + +Sorts a list using given comparator. `lesser_cmp(x, y)` should return `true` iff `x < y`. If `lesser_cmp` is not transitive or there exists an element `x` such that `lesser_cmp(x, x)` or there exists a pair of elements `x` and `y` such that `lesser_cmp(x, y) && lesser_cmp(y, x)` then the result is undefined. Currently O(n^2). + + +### intersperse +``` +List.intersperse(delim : 'a, l : list('a)) : list('a) +``` + +Intersperses elements of `l` with `delim`. Does nothing on empty lists and singletons. For instance +``` +intersperse(0, [1, 2, 3, 4]) +``` +will yield `[1, 0, 2, 0, 3, 0, 4]` + + +### enumerate +``` +List.enumerate(l : list('a)) : list(int * 'a) +``` + +Equivalent to [zip](#zip) with `[0..length(l)]`, but slightly faster. + + +## Option + +Common operations on `option` types and lists of `option`s. + +### is_none +``` +Option.is_none(o : option('a)) : bool +``` + +Returns true iff `o == None` + + +### is_some +``` +Option.is_some(o : option('a)) : bool +``` + +Returns true iff `o` is not `None`. + + +### match +``` +Option.match(n : 'b, s : 'a => 'b, o : option('a)) : 'b +``` + +Behaves like pattern matching on `option` using two case functions. + + +### default +``` +Option.default(def : 'a, o : option('a)) : 'a +``` + +Escapes `option` wrapping by providing default value for `None`. + + +### force +``` +Option.force(o : option('a)) : 'a +``` + +Forcefully escapes `option` wrapping assuming it is `Some`. Throws error on `None`. + + +### on_elem +``` +Option.on_elem(o : option('a), f : 'a => unit) : unit +``` + +Evaluates `f` on element under `Some`. Does nothing on `None`. + + +### map +``` +Option.map(f : 'a => 'b, o : option('a)) : option('b) +``` + +Maps element under `Some`. Leaves `None` unchanged. + + +### map2 +``` +Option.map2(f : ('a, 'b) => 'c, o1 : option('a), o2 : option('b)) : option('c) +``` + +Applies arity 2 function over two `option`s' elements. Returns `Some` iff both of `o1` and `o2` were `Some`, or `None` otherwise. For instance +``` +map2((a, b) => a + b, Some(1), Some(2)) +``` +will yield `Some(3)` and +``` +map2((a, b) => a + b, Some(1), None) +``` +will yield `None`. + + +### map3 +``` +Option.map3(f : ('a, 'b, 'c) => 'd, o1 : option('a), o2 : option('b), o3 : option('c)) : option('d) +``` + +Same as [map2](#map2) but with arity 3 function. + + +### app_over +``` +Option.app_over(f : option ('a => 'b), o : option('a)) : option('b) +``` + +Applies function under `option` over argument under `option`. If either of them is `None` the result will be `None` as well. For instance +``` +app_over(Some((x) => x + 1), Some(1)) +``` +will yield `Some(2)` and +``` +app_over(Some((x) => x + 1), None) +``` +will yield `None`. + + +### flat_map +``` +Option.flat_map(f : 'a => option('b), o : option('a)) : option('b) +``` + +Performs monadic bind on an `option`. Extracts element from `o` (if present) and forms new `option` from it. For instance +``` +flat_map((x) => Some(x + 1), Some(1)) +``` +will yield `Some(2)` and +``` +flat_map((x) => Some(x + 1), None) +``` +will yield `None`. + + +### to_list +``` +Option.to_list(o : option('a)) : list('a) +``` + +Turns `o` into an empty (if `None`) or singleton (if `Some`) list. + + +### filter_options +``` +Option.filter_options(l : list(option('a))) : list('a) +``` + +Removes `None`s from list and unpacks all remaining `Some`s. For instance +``` +filter_options([Some(1), None, Some(2)]) +``` +will yield `[1, 2]`. + + +### seq_options +``` +Option.seq_options(l : list (option('a))) : option (list('a)) +``` + +Tries to unpack all elements of a list from `Some`s. Returns `None` if at least element of `l` is `None`. For instance +``` +seq_options([Some(1), Some(2)]) +``` +will yield `Some([1, 2])`, but +``` +seq_options([Some(1), Some(2), None]) +``` +will yield `None`. + + +### choose +``` +Option.choose(o1 : option('a), o2 : option('a)) : option('a) +``` + +Out of two `option`s choose the one that is `Some`, or `None` if both are `None`s. + + +### choose_first +``` +Option.choose_first(l : list(option('a))) : option('a) +``` + +Same as [choose](#choose), but chooses from a list insted of two arguments. + + +## Func + +Functional combinators. + +### id +``` +Func.id(x : 'a) : 'a +``` + +Identity function. Returns its argument. + + +### const +``` +Func.const(x : 'a) : 'b => 'a = (y) => x +``` + +Constant function constructor. Given `x` returns a function that returns `x` regardless of its argument. + + +### flip +``` +Func.flip(f : ('a, 'b) => 'c) : ('b, 'a) => 'c +``` + +Switches order of arguments of arity 2 function. + + +### comp +``` +Func.comp(f : 'b => 'c, g : 'a => 'b) : 'a => 'c +``` + +Function composition. `comp(f, g)(x) == f(g(x))`. + + +### pipe +``` +Func.pipe(f : 'a => 'b, g : 'b => 'c) : 'a => 'c +``` + +Flipped function composition. `pipe(f, g)(x) == g(f(x))`. + + +### rapply +``` +Func.rapply(x : 'a, f : 'a => 'b) : 'b +``` + +Reverse application. `rapply(x, f) == f(x)`. + + +### recur +``` +Func.recur(f : ('arg => 'res, 'arg) => 'res) : 'arg => 'res +``` + +The Z combinator. Allows performing local recursion and having anonymous recursive lambdas. To make function `A => B` recursive the user needs to transform it to take two arguments instead – one of type `A => B` which is going to work as a self-reference, and the other one of type `A` which is the original argument. Therefore, transformed function should have `(A => B, A) => B` signature. + +Example usage: +``` +let factorial = recur((fac, n) => if(n < 2) 1 else n * fac(n - 1)) +``` + +If the function is going to take more than one argument it will need to be either tuplified or have curried out latter arguments. + +Example (factorial with custom step): + +``` +// tuplified version +let factorial_t(n, step) = + let fac(rec, args) = + let (n, step) = args + if(n < 2) 1 else n * rec((n - step, step)) + recur(fac)((n, step)) + +// curried version +let factorial_c(n, step) = + let fac(rec, n) = (step) => + if(n < 2) 1 else n * rec(n - 1)(step) + recur(fac)(n)(step) +``` + + +### iter +``` +Func.iter(n : int, f : 'a => 'a) : 'a => 'a +``` + +`n`th composition of f with itself, for instance `iter(3, f)` is equivalent to `(x) => f(f(f(x)))`. + + +### curry +``` +Func.curry2(f : ('a, 'b) => 'c) : 'a => ('b => 'c) +Func.curry3(f : ('a, 'b, 'c) => 'd) : 'a => ('b => ('c => 'd)) +``` + +Turns a function that takes n arguments into a curried function that takes +one argument and returns a function that waits for the rest in the same +manner. For instance `curry2((a, b) => a + b)(1)(2) == 3`. + + +### uncurry +``` +Func.uncurry2(f : 'a => ('b => 'c)) : ('a, 'b) => 'c +Func.uncurry3(f : 'a => ('b => ('c => 'd))) : ('a, 'b, 'c) => 'd +``` + +Opposite to [curry](#curry). + + +### tuplify +``` +Func.tuplify2(f : ('a, 'b) => 'c) : (('a * 'b)) => 'c +Func.tuplify3(f : ('a, 'b, 'c) => 'd) : 'a * 'b * 'c => 'd +``` + +Turns a function that takes n arguments into a function that takes an n-tuple. + + +### untuplify +``` +Func.untuplify2(f : 'a * 'b => 'c) : ('a, 'b) => 'c +Func.untuplify3(f : 'a * 'b * 'c => 'd) : ('a, 'b, 'c) => 'd +``` + +Opposite to [tuplify](#tuplify). + + +## Pair + +Common operations on 2-tuples. + +### fst +``` +Pair.fst(t : ('a * 'b)) : 'a +``` + +First element projection. + + +### snd +``` +Pair.snd(t : ('a * 'b)) : 'b +``` + +Second element projection. + + +### map1 +``` +Pair.map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) +``` + +Applies function over first element. + + +### map2 +``` +Pair.map2(f : 'b => 'c, t : ('a * 'b)) : ('a * 'c) +``` + +Applies function over second element. + + +### bimap +``` +Pair.bimap(f : 'a => 'c, g : 'b => 'd, t : ('a * 'b)) : ('c * 'd) +``` + +Applies functions over respective elements. + + +### swap +``` +Pair.swap(t : ('a * 'b)) : ('b * 'a) +``` + +Swaps elements. + + +## Triple + +### fst +``` +Triple.fst(t : ('a * 'b * 'c)) : 'a +``` + +First element projection. + + +### snd +``` +Triple.snd(t : ('a * 'b * 'c)) : 'b +``` + +Second element projection. + + +### thd +``` +Triple.thd(t : ('a * 'b * 'c)) : 'c +``` + +Third element projection. + + +### map1 +``` +Triple.map1(f : 'a => 'm, t : ('a * 'b * 'c)) : ('m * 'b * 'c) +``` + +Applies function over first element. + + +### map2 +``` +Triple.map2(f : 'b => 'm, t : ('a * 'b * 'c)) : ('a * 'm * 'c) +``` + +Applies function over second element. + + +### map3 +``` +Triple.map3(f : 'c => 'm, t : ('a * 'b * 'c)) : ('a * 'b * 'm) +``` + +Applies function over third element. + + +### trimap +``` +Triple.trimap(f : 'a => 'x, g : 'b => 'y, h : 'c => 'z, t : ('a * 'b * 'c)) : ('x * 'y * 'z) +``` + +Applies functions over respective elements. + + +### swap +``` +Triple.swap(t : ('a * 'b * 'c)) : ('c * 'b * 'a) +``` + +Swaps first and third element. + + +### rotr +``` +Triple.rotr(t : ('a * 'b * 'c)) : ('c * 'a * 'b) +``` + +Cyclic rotation of the elements to the right. + + +### rotl +``` +Triple.rotl(t : ('a * 'b * 'c)) : ('b * 'c * 'a) +``` + +Cyclic rotation of the elements to the left. + +## BLS12\_381 + +### Types +- `fp // Built-in (Montgomery) integer representation 32 bytes` +- `fr // Built-in (Montgomery) integer representation 48 bytes` +- `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 +``` +BLS12_381.pairing_check(xs : list(g1), ys : list(g2)) : bool +``` + +Pairing check of a list of points, `xs` and `ys` should be of equal length. + +### int_to_fr +``` +BLS12_381.int_to_fr(x : int) : fr +``` + +Convert an integer to an `fr` - a 32 bytes internal (Montgomery) integer representation. + +### int_to_fp +``` +BLS12_381.int_to_fp(x : int) : fp +``` + +Convert an integer to an `fp` - a 48 bytes internal (Montgomery) integer representation. + +### fr_to_int +``` +BLS12_381.fr_to_int(x : fr) : int +``` + +Convert a `fr` value into an integer. + +### fp_to_int +``` +BLS12_381.fp_to_int(x : fp) : int +``` + +Convert a `fp` value into an integer. + +### mk_g1 +``` +BLS12_381.mk_g1(x : int, y : int, z : int) : g1 +``` + +Construct a `g1` point from three integers. + +### mk_g2 +``` +BLS12_381.mk_g2(x1 : int, x2 : int, y1 : int, y2 : int, z1 : int, z2 : int) : g2 +``` + +Construct a `g2` point from six integers. + +### g1_neg +``` +BLS12_381.g1_neg(p : g1) : g1 +``` + +Negate a `g1` value. + +### g1_norm +``` +BLS12_381.g1_norm(p : g1) : g1 +``` + +Normalize a `g1` value. + +### g1_valid +``` +BLS12_381.g1_valid(p : g1) : bool +``` + +Check that a `g1` value is a group member. + +### g1_is_zero +``` +BLS12_381.g1_is_zero(p : g1) : bool +``` + +Check if a `g1` value corresponds to the zero value of the group. + +### g1_add +``` +BLS12_381.g1_add(p : g1, q : g1) : g1 +``` + +Add two `g1` values. + +### g1_mul +``` +BLS12_381.g1_mul(k : fr, p : g1) : g1 +``` + +Scalar multiplication for `g1`. + +### g2_neg +``` +BLS12_381.g2_neg(p : g2) : g2 +``` + +Negate a `g2` value. + +### g2_norm +``` +BLS12_381.g2_norm(p : g2) : g2 +``` + +Normalize a `g2` value. + +### g2_valid +``` +BLS12_381.g2_valid(p : g2) : bool +``` + +Check that a `g2` value is a group member. + +### g2_is_zero +``` +BLS12_381.g2_is_zero(p : g2) : bool +``` + +Check if a `g2` value corresponds to the zero value of the group. + +### g2_add +``` +BLS12_381.g2_add(p : g2, q : g2) : g2 +``` + +Add two `g2` values. + +### g2_mul +``` +BLS12_381.g2_mul(k : fr, p : g2) : g2 +``` + +Scalar multiplication for `g2`. + +### gt_inv +``` +BLS12_381.gt_inv(p : gt) : gt +``` + +Invert a `gt` value. + +### gt_add +``` +BLS12_381.gt_add(p : gt, q : gt) : gt +``` + +Add two `gt` values. + +### gt_mul +``` +BLS12_381.gt_mul(p : gt, q : gt) : gt +``` + +Multiply two `gt` values. + +### gt_pow +``` +BLS12_381.gt_pow(p : gt, k : fr) : gt +``` + +Calculate exponentiation `p ^ k`. + +### gt_is_one +``` +BLS12_381.gt_is_one(p : gt) : bool +``` + +Compare a `gt` value to the unit value of the Gt group. + +### pairing +``` +BLS12_381.pairing(p : g1, q : g2) : gt +``` + +Compute the pairing of a `g1` value and a `g2` value. + +### miller_loop +``` +BLS12_381.miller_loop(p : g1, q : g2) : gt +``` + +Do the Miller loop stage of pairing for `g1` and `g2`. + +### final_exp +``` +BLS12_381.final_exp(p : gt) : gt +``` + +Perform the final exponentiation step of pairing for a `gt` value. + +## Frac + +This namespace provides operations on rational numbers. A rational number is represented +as a fraction of two integers which are stored internally in the `frac` datatype. + +The datatype consists of three constructors `Neg/2`, `Zero/0` and `Pos/2` which determine the +sign of the number. Both values stored in `Neg` and `Pos` need to be strictly positive +integers. However, when creating a `frac` you should never use the constructors explicitly. +Instead of that, always use provided functions like `make_frac` or `from_int`. This helps +keeping the internal representation well defined. + +The described below functions take care of the normalization of the fractions – +they won't grow if it is unnecessary. Please note that the size of `frac` can be still +very big while the value is actually very close to a natural number – the division of +two extremely big prime numbers *will* be as big as both of them. To face this issue +the [optimize](#optimize) function is provided. It will approximate the value of the +fraction to fit in the given error margin and to shrink its size as much as possible. + +**Important note:** `frac` must *not* be compared using standard `<`-like operators. +The operator comparison is not possible to overload at this moment, nor the +language provides checkers to prevent unintended usage of them. Therefore the typechecker +**will** allow that and the results of such comparison will be unspecified. +You should use [lt](#lt), [geq](#geq), [eq](#eq) etc instead. + +### make_frac +`Frac.make_frac(n : int, d : int) : frac` + +Creates a fraction out of numerator and denominator. Automatically normalizes, so +`make_frac(2, 4)` and `make_frac(1, 2)` will yield same results. + + +### num +`Frac.num(f : frac) : int` + +Returns the numerator of a fraction. + + +### den +`Frac.den(f : frac) : int` + +Returns the denominator of a fraction. + + +### to_pair +`Frac.to_pair(f : frac) : int * int` + +Turns a fraction into a pair of numerator and denominator. + + +### sign +`Frac.sign(f : frac) : int` + +Returns the signum of a fraction, -1, 0, 1 if negative, zero, positive respectively. + + +### to_str +`Frac.to_str(f : frac) : string` + +Conversion to string. Does not display division by 1 or denominator if equals zero. + + +### simplify +`Frac.simplify(f : frac) : frac` + +Reduces fraction to normal form if for some reason it is not in it. + + +### eq +`Frac.eq(a : frac, b : frac) : bool` + +Checks if `a` is equal to `b`. + + +### neq +`Frac.neq(a : frac, b : frac) : bool` + +Checks if `a` is not equal to `b`. + + +### geq +`Frac.geq(a : frac, b : frac) : bool` + +Checks if `a` is greater or equal to `b`. + + +### leq +`Frac.leq(a : frac, b : frac) : bool` + +Checks if `a` is lesser or equal to `b`. + + +### gt +`Frac.gt(a : frac, b : frac) : bool` + +Checks if `a` is greater than `b`. + + +### lt +`Frac.lt(a : frac, b : frac) : bool` + +Checks if `a` is lesser than `b`. + + +### min +`Frac.min(a : frac, b : frac) : frac` + +Chooses lesser of the two fractions. + + +### max +`Frac.max(a : frac, b : frac) : frac` + +Chooses greater of the two fractions. + + +### abs +`Frac.abs(f : frac) : frac` + +Absolute value. + + +### from_int +`Frac.from_int(n : int) : frac` + +From integer conversion. Effectively `make_frac(n, 1)`. + + +### floor +`Frac.floor(f : frac) : int` + +Rounds a fraction to the nearest lesser or equal integer. + + +### ceil +`Frac.ceil(f : frac) : int` + +Rounds a fraction to the nearest greater or equal integer. + + +### round_to_zero +`Frac.round_to_zero(f : frac) : int` + +Rounds a fraction towards zero. +Effectively `ceil` if lesser than zero and `floor` if greater. + + +### round_from_zero +`Frac.round_from_zero(f : frac) : int` + +Rounds a fraction from zero. +Effectively `ceil` if greater than zero and `floor` if lesser. + + +### round +`Frac.round(f : frac) : int` + +Rounds a fraction to a nearest integer. If two integers are in the same distance it +will choose the even one. + + +### add +`Frac.add(a : frac, b : frac) : frac` + +Sum of the fractions. + + +### neg +`Frac.neg(a : frac) : frac` + +Negation of the fraction. + + +### sub +`Frac.sub(a : frac, b : frac) : frac` + +Subtraction of two fractions. + + +### inv +`Frac.inv(a : frac) : frac` + +Inverts a fraction. Throws error if `a` is zero. + + +### mul +`Frac.mul(a : frac, b : frac) : frac` + +Multiplication of two fractions. + + +### div +`Frac.div(a : frac, b : frac) : frac` + +Division of two fractions. + + +### int_exp +`Frac.int_exp(b : frac, e : int) : frac` + +Takes `b` to the power of `e`. The exponent can be a negative value. + + +### optimize +`Frac.optimize(f : frac, loss : frac) : frac` + +Shrink the internal size of a fraction as much as possible by approximating it to the +point where the error would exceed the `loss` value. + + +### is_sane +`Frac.is_sane(f : frac) : bool` + +For debugging. If it ever returns false in a code that doesn't call `frac` constructors or +accept arbitrary `frac`s from the surface you should report it as a +[bug](https://github.com/aeternity/aesophia/issues/new) + +If you expect getting calls with malformed `frac`s in your contract, you should use +this function to verify the input. diff --git a/priv/stdlib/Frac.aes b/priv/stdlib/Frac.aes index 1fdebb5..37ef80f 100644 --- a/priv/stdlib/Frac.aes +++ b/priv/stdlib/Frac.aes @@ -62,17 +62,13 @@ namespace Frac = else simplify(Neg(abs_int(n), abs_int(d))) function eq(a : frac, b : frac) : bool = - let na = num(a) - let nb = num(b) - let da = den(a) - let db = den(b) + let (na, da) = to_pair(a) + let (nb, db) = to_pair(b) (na == nb && da == db) || na * db == nb * da // they are more likely to be normalized function neq(a : frac, b : frac) : bool = - let na = num(a) - let nb = num(b) - let da = den(a) - let db = den(b) + let (na, da) = to_pair(a) + let (nb, db) = to_pair(b) (na != nb || da != db) && na * db != nb * da function geq(a : frac, b : frac) : bool = num(a) * den(b) >= num(b) * den(a) @@ -131,10 +127,8 @@ namespace Frac = else cl function add(a : frac, b : frac) : frac = - let na = num(a) - let nb = num(b) - let da = den(a) - let db = den(b) + let (na, da) = to_pair(a) + let (nb, db) = to_pair(b) if (da == db) make_frac(na + nb, da) else make_frac(na * db + nb * da, da * db) From 515838e2f92e3c39e320e209af701da6e1f75987 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 23 Mar 2020 18:11:00 +0100 Subject: [PATCH 05/14] Handle negative numbers in aci (#247) * Handle negative numbers in aci Fixes aeternity/aesophia_http#59 * Updated CHANGELOG Updated CHANGELOG1 Co-authored-by: radrow --- CHANGELOG.md | 1 + src/aeso_aci.erl | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d6231f..c2a3daa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added ### Changed +- Bug fix: Fixed ACI encoder to handle `-` unary operator ### Removed ## [4.2.0] - 2020-01-15 diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 274be69..26372a8 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -194,6 +194,8 @@ encode_expr({bytes, _, B}) -> encode_expr({Lit, _, L}) when Lit == oracle_pubkey; Lit == oracle_query_id; Lit == contract_pubkey; Lit == account_pubkey -> aeser_api_encoder:encode(Lit, L); +encode_expr({app, _, {'-', _}, [{int, _, N}]}) -> + encode_expr({int, [], -N}); encode_expr({app, _, F, As}) -> Ef = encode_expr(F), Eas = encode_exprs(As), From 48b52cb501f128d702c4719d96b0455468ccc800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Sun, 29 Mar 2020 17:14:01 +0200 Subject: [PATCH 06/14] Enchanted Frac library a bit (#253) --- priv/stdlib/Frac.aes | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/priv/stdlib/Frac.aes b/priv/stdlib/Frac.aes index 37ef80f..64cec1e 100644 --- a/priv/stdlib/Frac.aes +++ b/priv/stdlib/Frac.aes @@ -56,11 +56,14 @@ namespace Frac = /** Integer to rational division */ function make_frac(n : int, d : int) : frac = - if (d == 0) abort("Division by zero") + if (d == 0) abort("Zero denominator") elif (n == 0) Zero elif ((n < 0) == (d < 0)) simplify(Pos(abs_int(n), abs_int(d))) else simplify(Neg(abs_int(n), abs_int(d))) + function one() : frac = Pos(1, 1) + function zero() : frac = Zero + function eq(a : frac, b : frac) : bool = let (na, da) = to_pair(a) let (nb, db) = to_pair(b) @@ -146,7 +149,10 @@ namespace Frac = function mul(a : frac, b : frac) : frac = make_frac(num(a) * num(b), den(a) * den(b)) - function div(a : frac, b : frac) : frac = mul(a, inv(b)) + function div(a : frac, b : frac) : frac = switch(b) + Neg(n, d) => mul(a, Neg(d, n)) + Zero => abort("Division by zero") + Pos(n, d) => mul(a, Pos(d, n)) /** `b` to the power of `e` */ @@ -168,9 +174,10 @@ namespace Frac = function optimize(f : frac, loss : frac) : frac = require(geq(loss, Zero), "negative loss optimize") let s = sign(f) - mul(from_int(s), run_optimize(abs(f), loss)) - private function run_optimize(f : frac, loss : frac) : frac = - let t = make_frac((num(f) + 1) / 2, (den(f) + 1)/2) - if(gt(abs(sub(t, f)), loss)) f + mul(from_int(s), run_optimize(abs(f), abs(f), loss)) + private function run_optimize(orig : frac, f : frac, loss : frac) : frac = + let (n, d) = to_pair(f) + let t = make_frac((n+1)/2, (d+1)/2) + if(gt(abs(sub(t, orig)), loss)) f elif (eq(t, f)) f - else run_optimize(t, loss) + else run_optimize(orig, t, loss) From 4f554acee6109a12b3d38e298f3396866ff522d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Mon, 30 Mar 2020 14:52:16 +0200 Subject: [PATCH 07/14] Fix error messages for some illegal constructions, fix absolute path includes (#251) * Updated tests, banned type decls and toplevel letvals * Properly ban nested contracts * Fix including by path * Fix error message test * Fix prettpr attr display. Make dialyzer happy * More tests * Fixed type printing * Updated docs --- docs/sophia.md | 18 +++- src/aeso_ast_infer_types.erl | 44 ++++++--- src/aeso_ast_to_fcode.erl | 1 - src/aeso_compiler.erl | 4 +- src/aeso_parser.erl | 10 +- src/aeso_pretty.erl | 21 +++-- src/aeso_syntax.erl | 12 ++- src/aeso_syntax_utils.erl | 1 - test/aeso_compiler_tests.erl | 52 ++++++++++- test/contracts/05_greeter.aes | 3 +- test/contracts/all_syntax.aes | 92 +++++++++++++------ test/contracts/bad_arity.aes | 4 + .../bad_unnamed_map_update_default.aes | 5 + test/contracts/empty_typedecl.aes | 3 + test/contracts/higher_kinded_type.aes | 3 + test/contracts/multi_sig.aes | 4 +- test/contracts/non_functional_entrypoint.aes | 5 + test/contracts/not_toplevel_contract.aes | 6 ++ test/contracts/not_toplevel_include.aes | 5 + ..._and_ns.aes => not_toplevel_namespace.aes} | 3 +- test/contracts/toplevel_let.aes | 3 + test/contracts/voting.aes | 13 --- 22 files changed, 224 insertions(+), 88 deletions(-) create mode 100644 test/contracts/bad_arity.aes create mode 100644 test/contracts/bad_unnamed_map_update_default.aes create mode 100644 test/contracts/empty_typedecl.aes create mode 100644 test/contracts/higher_kinded_type.aes create mode 100644 test/contracts/non_functional_entrypoint.aes create mode 100644 test/contracts/not_toplevel_contract.aes create mode 100644 test/contracts/not_toplevel_include.aes rename test/contracts/{bad_include_and_ns.aes => not_toplevel_namespace.aes} (62%) create mode 100644 test/contracts/toplevel_let.aes diff --git a/docs/sophia.md b/docs/sophia.md index 3195870..8640b4e 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -446,6 +446,13 @@ Example syntax: // yields [12,13,14,20,21,22,30,31,32] ``` +Lists can be constructed using the range syntax using special `..` operator: +``` +[1..4] == [1,2,3,4] +``` +The ranges are always ascending and have step equal to 1. + + Please refer to the [standard library](sophia_stdlib.md#List) for the predefined functionalities. ### Maps and records @@ -825,15 +832,17 @@ In describing the syntax below, we use the following conventions: A Sophia file consists of a sequence of *declarations* in a layout block. ```c -File ::= Block(Decl) -Decl ::= ['payable'] 'contract' Con '=' Block(Decl) +File ::= Block(TopDecl) + +TopDecl ::= ['payable'] 'contract' Con '=' Block(Decl) | 'namespace' Con '=' Block(Decl) | '@compiler' PragmaOp Version | 'include' String - | 'type' Id ['(' TVar* ')'] ['=' TypeAlias] + +Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias | 'record' Id ['(' TVar* ')'] '=' RecordType | 'datatype' Id ['(' TVar* ')'] '=' DataType - | EModifier* ('entrypoint' | 'function') Block(FunDecl) + | (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl) FunDecl ::= Id ':' Type // Type signature | Id Args [':' Type] '=' Block(Stmt) // Definition @@ -945,6 +954,7 @@ Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x + | '[' Sep(Expr, ',') ']' // List [1, 2, 3] | '[' Expr '|' Sep(Generator, ',') ']' // List comprehension [k | x <- [1], if (f(x)), let k = x+1] + | '[' Expr '..' Expr ']' // List range [1..n] | '{' Sep(FieldUpdate, ',') '}' // Record or map value {x = 0, y = 1}, {[key] = val} | '(' Expr ')' // Parens (1 + 2) * 3 | Id | Con | QId | QCon // Identifiers x, None, Map.member, AELib.Token diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 868329c..950131c 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -2262,6 +2262,24 @@ mk_t_err(Pos, Msg) -> mk_t_err(Pos, Msg, Ctxt) -> aeso_errors:new(type_error, Pos, lists:flatten(Msg), lists:flatten(Ctxt)). +mk_error({higher_kinded_typevar, T}) -> + Msg = io_lib:format("Type ~s is a higher kinded type variable\n" + "(takes another type as an argument)\n", [pp(instantiate(T))] + ), + mk_t_err(pos(T), Msg); +mk_error({wrong_type_arguments, X, ArityGiven, ArityReal}) -> + Msg = io_lib:format("Arity for ~s doesn't match. Expected ~p, got ~p\n" + , [pp(instantiate(X)), ArityReal, ArityGiven] + ), + mk_t_err(pos(X), Msg); +mk_error({unnamed_map_update_with_default, Upd}) -> + Msg = "Invalid map update with default\n", + mk_t_err(pos(Upd), Msg); +mk_error({fundecl_must_have_funtype, _Ann, Id, Type}) -> + Msg = io_lib:format("~s at ~s was declared with an invalid type ~s.\n" + "Entrypoints and functions must have functional types" + , [pp(Id), pp_loc(Id), pp(instantiate(Type))]), + mk_t_err(pos(Id), Msg); mk_error({cannot_unify, A, B, When}) -> Msg = io_lib:format("Cannot unify ~s\n and ~s\n", [pp(instantiate(A)), pp(instantiate(B))]), @@ -2344,14 +2362,6 @@ mk_error({indexed_type_must_be_word, Type, Type1}) -> Msg = io_lib:format("The indexed type ~s (at ~s) equals ~s which is not a word type\n", [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]), mk_t_err(pos(Type), Msg); -mk_error({payload_type_must_be_string, Type, Type}) -> - Msg = io_lib:format("The payload type ~s (at ~s) should be string\n", - [pp_type("", Type), pp_loc(Type)]), - mk_t_err(pos(Type), Msg); -mk_error({payload_type_must_be_string, Type, Type1}) -> - Msg = io_lib:format("The payload type ~s (at ~s) equals ~s but it should be string\n", - [pp_type("", Type), pp_loc(Type), pp_type("", Type1)]), - mk_t_err(pos(Type), Msg); mk_error({event_0_to_3_indexed_values, Constr}) -> Msg = io_lib:format("The event constructor ~s (at ~s) has too many indexed values (max 3)\n", [name(Constr), pp_loc(Constr)]), @@ -2395,13 +2405,21 @@ mk_error({include, _, {string, Pos, Name}}) -> [binary_to_list(Name), pp_loc(Pos)]), mk_t_err(pos(Pos), Msg); mk_error({namespace, _Pos, {con, Pos, Name}, _Def}) -> - Msg = io_lib:format("Nested namespace not allowed\nNamespace '~s' at ~s not defined at top level.\n", + Msg = io_lib:format("Nested namespaces are not allowed\nNamespace '~s' at ~s not defined at top level.\n", + [Name, pp_loc(Pos)]), + mk_t_err(pos(Pos), Msg); +mk_error({contract, _Pos, {con, Pos, Name}, _Def}) -> + Msg = io_lib:format("Nested contracts are not allowed\nContract '~s' at ~s not defined at top level.\n", + [Name, pp_loc(Pos)]), + mk_t_err(pos(Pos), Msg); +mk_error({type_decl, _, {id, Pos, Name}, _}) -> + Msg = io_lib:format("Empty type declarations are not supported\nType ~s at ~s lacks a definition\n", + [Name, pp_loc(Pos)]), + mk_t_err(pos(Pos), Msg); +mk_error({letval, _Pos, {id, Pos, Name}, _Def}) -> + Msg = io_lib:format("Toplevel \"let\" definitions are not supported\nValue ~s at ~s could be replaced by 0-argument function\n", [Name, pp_loc(Pos)]), mk_t_err(pos(Pos), Msg); -mk_error({repeated_arg, Fun, Arg}) -> - Msg = io_lib:format("Repeated argument ~s to function ~s (at ~s).\n", - [Arg, pp(Fun), pp_loc(Fun)]), - mk_t_err(pos(Fun), Msg); mk_error({stateful_not_allowed, Id, Fun}) -> Msg = io_lib:format("Cannot reference stateful function ~s (at ~s)\nin the definition of non-stateful function ~s.\n", [pp(Id), pp_loc(Id), pp(Fun)]), diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index eb27e16..50ea216 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -291,7 +291,6 @@ decls_to_fcode(Env, Decls) -> end, Env1, Decls). -spec decl_to_fcode(env(), aeso_syntax:decl()) -> env(). -decl_to_fcode(Env, {type_decl, _, _, _}) -> Env; decl_to_fcode(Env = #{context := {main_contract, _}}, {fun_decl, _, Id, _}) -> case is_no_code(Env) of false -> fcode_error({missing_definition, Id}); diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index c677119..ae25a65 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -336,12 +336,12 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) -> try {ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))} catch throw:cannot_translate_to_sophia -> - Type1 = prettypr:format(aeso_pretty:type(Type)), + Type1 = prettypr:format(aeso_pretty:type(Type0)), Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s\n", [aeb_fate_encoding:deserialize(Data), Type1]), {error, [aeso_errors:new(data_error, Msg)]}; _:_ -> - Type1 = prettypr:format(aeso_pretty:type(Type)), + Type1 = prettypr:format(aeso_pretty:type(Type0)), Msg = io_lib:format("Failed to decode binary as type ~s\n", [Type1]), {error, [aeso_errors:new(data_error, Msg)]} end diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 74a329c..16212ce 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -656,8 +656,14 @@ stdlib_options() -> get_include_code(File, Ann, Opts) -> case {read_file(File, Opts), read_file(File, stdlib_options())} of - {{ok, _}, {ok,_ }} -> - fail(ann_pos(Ann), "Illegal redefinition of standard library " ++ File); + {{ok, Bin}, {ok, _}} -> + case filename:basename(File) == File of + true -> { error + , fail( ann_pos(Ann) + , "Illegal redefinition of standard library " ++ binary_to_list(File))}; + %% If a path is provided then the stdlib takes lower priority + false -> {ok, binary_to_list(Bin)} + end; {_, {ok, Bin}} -> {ok, binary_to_list(Bin)}; {{ok, Bin}, _} -> diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index bf00107..ac80edb 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -145,8 +145,12 @@ decl(D, Options) -> with_options(Options, fun() -> decl(D) end). -spec decl(aeso_syntax:decl()) -> doc(). -decl({contract, _, C, Ds}) -> - block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds)); +decl({contract, Attrs, C, Ds}) -> + Mod = fun({Mod, true}) when Mod == payable -> + text(atom_to_list(Mod)); + (_) -> empty() end, + block(follow( hsep(lists:map(Mod, Attrs) ++ [text("contract")]) + , hsep(name(C), text("="))), decls(Ds)); decl({namespace, _, C, Ds}) -> block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds)); decl({pragma, _, Pragma}) -> pragma(Pragma); @@ -155,13 +159,16 @@ decl({type_def, _, T, Vars, Def}) -> Kind = element(1, Def), equals(typedecl(Kind, T, Vars), typedef(Def)); decl({fun_decl, Ann, F, T}) -> + Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable -> + text(atom_to_list(Mod)); + (_) -> empty() end, Fun = case aeso_syntax:get_ann(entrypoint, Ann, false) of true -> text("entrypoint"); false -> text("function") end, - hsep(Fun, typed(name(F), T)); + hsep(lists:map(Mod, Ann) ++ [Fun, typed(name(F), T)]); decl(D = {letfun, Attrs, _, _, _, _}) -> - Mod = fun({Mod, true}) when Mod == private; Mod == stateful -> + Mod = fun({Mod, true}) when Mod == private; Mod == stateful; Mod == payable -> text(atom_to_list(Mod)); (_) -> empty() end, Fun = case aeso_syntax:get_ann(entrypoint, Attrs, false) of @@ -364,7 +371,8 @@ expr_p(_, {Type, _, Bin}) Type == oracle_query_id -> text(binary_to_list(aeser_api_encoder:encode(Type, Bin))); expr_p(_, {string, _, <<>>}) -> text("\"\""); -expr_p(_, {string, _, S}) -> term(binary_to_list(S)); +expr_p(_, {string, _, S}) -> + text(io_lib:format("\"~s\"", [binary_to_list(S)])); expr_p(_, {char, _, C}) -> case C of $' -> text("'\\''"); @@ -486,6 +494,3 @@ get_elifs(If = {'if', Ann, Cond, Then, Else}, Elifs) -> end; get_elifs(Else, Elifs) -> {lists:reverse(Elifs), {else, Else}}. -fmt(Fmt, Args) -> text(lists:flatten(io_lib:format(Fmt, Args))). -term(X) -> fmt("~p", [X]). - diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 61011da..30d4d67 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -37,20 +37,24 @@ -type decl() :: {contract, ann(), con(), [decl()]} | {namespace, ann(), con(), [decl()]} | {pragma, ann(), pragma()} - | {type_decl, ann(), id(), [tvar()]} + | {type_decl, ann(), id(), [tvar()]} % Only for error msgs | {type_def, ann(), id(), [tvar()], typedef()} | {fun_decl, ann(), id(), type()} | {fun_clauses, ann(), id(), type(), [letbind()]} | {block, ann(), [decl()]} - | letbind(). + | letfun() + | letval(). % Only for error msgs -type compiler_version() :: [non_neg_integer()]. -type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}. + +-type letval() :: {letval, ann(), pat(), expr()}. +-type letfun() :: {letfun, ann(), id(), [pat()], type(), expr()}. -type letbind() - :: {letval, ann(), pat(), expr()} - | {letfun, ann(), id(), [pat()], type(), expr()}. + :: letfun() + | letval(). -type arg() :: {arg, ann(), id(), type()}. diff --git a/src/aeso_syntax_utils.erl b/src/aeso_syntax_utils.erl index 71c7c90..deef257 100644 --- a/src/aeso_syntax_utils.erl +++ b/src/aeso_syntax_utils.erl @@ -45,7 +45,6 @@ fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) -> %% decl() {contract, _, _, Ds} -> Decl(Ds); {namespace, _, _, Ds} -> Decl(Ds); - {type_decl, _, I, _} -> BindType(I); {type_def, _, I, _, D} -> Plus(BindType(I), Decl(D)); {fun_decl, _, _, T} -> Type(T); {letval, _, P, E} -> Scoped(BindExpr(P), Expr(E)); diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index ec75032..c6f5b9f 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -375,11 +375,15 @@ failing_contracts() -> " r.foo : (gas : int, value : int) => Remote.themap\n" "against the expected type\n" " (gas : int, value : int) => map(string, int)">>]) - , ?TYPE_ERROR(bad_include_and_ns, - [<>, - <>]) + , ?TYPE_ERROR(not_toplevel_include, + [<>]) + , ?TYPE_ERROR(not_toplevel_namespace, + [<>]) + , ?TYPE_ERROR(not_toplevel_contract, + [<>]) , ?TYPE_ERROR(bad_address_literals, [< [<>]) + , ?TYPE_ERROR(toplevel_let, + [<>]) + , ?TYPE_ERROR(empty_typedecl, + [<>]) + , ?TYPE_ERROR(higher_kinded_type, + [<>]) + , ?TYPE_ERROR(bad_arity, + [<>, + <>, + <>, + <>]) + , ?TYPE_ERROR(bad_unnamed_map_update_default, + [<>]) + , ?TYPE_ERROR(non_functional_entrypoint, + [<>]) , ?TYPE_ERROR(bad_records, [< + function setGreeting(greeting: string) = state{ greeting = greeting } diff --git a/test/contracts/all_syntax.aes b/test/contracts/all_syntax.aes index a88bae8..6a40f27 100644 --- a/test/contracts/all_syntax.aes +++ b/test/contracts/all_syntax.aes @@ -1,44 +1,78 @@ // Try to cover all syntactic constructs. +@compiler > 0 +@compiler =< 10.1.1.1.1.1.2.3.4 -contract AllSyntaxType = - type typeDecl /* bla */ - type paramTypeDecl('a, 'b) +namespace Ns = + datatype d('a) = D | S(int) | M('a, list('a), int) + private function fff() = 123 + + stateful entrypoint + f (1, x) = (_) => x + +payable contract AllSyntaxType = /** Multi- * line * comment */ - function foo : _ + stateful function foo : _ + entrypoint bar : int => (int * 'a) + contract AllSyntax = - type typeDecl = int - type paramTypeDecl('a, 'b) = (('a, 'b) => 'b) => list('a) => 'b => 'b + datatype mickiewicz = Adam | Mickiewicz + record goethe('a, 'b) = { + johann : int, + wolfgang : 'a, + von : 'a * 'b * int, + goethe : unit + } + type dante = Ns.d(int) + type shakespeare('a) = goethe('a, 'a) - record nestedRecord = { x : int } - record recordType = { z : nestedRecord, y : int } - datatype variantType('a) = None | Some('a) + type state = shakespeare(int) - let valWithType : map(int, int) => option(int) = (m) => Map.get(m, 42) - let valNoType = - if(valWithType(Map.empty) == None) - print(42 mod 10 * 5 / 3) + entrypoint init() = { + johann = 1000, + wolfgang = -10, + von = (2 + 2, 0, List.sum([x | k <- [1,2,3] + , let l = k + 1 + , if(l < 10) + , let f(x) = x + 100 + , Adam <- [Adam, Mickiewicz] + , let x = f(l) + ])), + goethe = () } - function funWithType(x : int, y) : int * list(int) = (x, 0 :: [y] ++ []) - function funNoType() = - let foo = (x, y : bool) => - if (! (y && x =< 0x0b || true)) [x] - else [11..20] - let setY(r : recordType) : unit = r{ y = 5 } - let setX(r : recordType, x : int) : recordType = r { z.x = x } // nested record update - let getY(r) = switch(r) {y = y} => y - switch (funWithType(1, -2)) - (x, [y, z]) => bar({x = z, y = -y + - -z * (-1)}) - (x, y :: _) => () + function f() = + let kp = "nietzsche" + let p = "Пушкин" + let k(x : bytes(8)) : bytes(8) = Bytes.to_int(#fedcba9876543210) - let hash : address = #01ab0fff11 - let b = false - let qcon = Mod.Con - let str = "blabla\nfoo" - let chr = '"' + let f : () => address = () => ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt + if(Bits.test(Bits.all, 10)) + abort("ohno") + if(true && false) + require(true, "ohyes") + elif(false || 2 == 2) + () + else + () + if(true) f(1,2)((1,2)) + else switch(1::[1,2,3]) + [] => 1 + a::b => 123 + 1::2::3 => 123123 + [2,3,4] => 1 + _ => 13 + 1::[2] => 2138 + put(state{johann = 1}) + + let m = {["foo"] = 19, /*hey wanna talk about inlined comments?*/ ["bar"] = 42} + let n = {} + m{ ["x" = 0] @ z = z + state.johann } + let sh : shakespeare(shakespeare(int)) = + {wolfgang = state} + sh{wolfgang.wolfgang = sh.wolfgang} // comment diff --git a/test/contracts/bad_arity.aes b/test/contracts/bad_arity.aes new file mode 100644 index 0000000..0f02b14 --- /dev/null +++ b/test/contracts/bad_arity.aes @@ -0,0 +1,4 @@ +contract C = + type id('a) = 'a + entrypoint f() : id = 123 + entrypoint g() : id(int, int) = 123 \ No newline at end of file diff --git a/test/contracts/bad_unnamed_map_update_default.aes b/test/contracts/bad_unnamed_map_update_default.aes new file mode 100644 index 0000000..7eb39c1 --- /dev/null +++ b/test/contracts/bad_unnamed_map_update_default.aes @@ -0,0 +1,5 @@ +contract C = + entrypoint f() = + let z = 123 + {}{ [1 = 0] = z + 1 } + 2 \ No newline at end of file diff --git a/test/contracts/empty_typedecl.aes b/test/contracts/empty_typedecl.aes new file mode 100644 index 0000000..51b0286 --- /dev/null +++ b/test/contracts/empty_typedecl.aes @@ -0,0 +1,3 @@ +contract C = + type t + entrypoint f() = 123 \ No newline at end of file diff --git a/test/contracts/higher_kinded_type.aes b/test/contracts/higher_kinded_type.aes new file mode 100644 index 0000000..2f71537 --- /dev/null +++ b/test/contracts/higher_kinded_type.aes @@ -0,0 +1,3 @@ +contract IWantToBelieve = + type stateT('s, 'm, 'a) = 's => 'm('a * 's) + entrypoint s() = 123 \ No newline at end of file diff --git a/test/contracts/multi_sig.aes b/test/contracts/multi_sig.aes index cb1363f..f6ae20b 100644 --- a/test/contracts/multi_sig.aes +++ b/test/contracts/multi_sig.aes @@ -15,7 +15,7 @@ contract MultiSig = | OwnerRemoved (address) // of { .removedOwner : Address } | ReqChanged (int) // of { .newReq : int } - let maxOwners : int = 250 + function maxOwners() : int = 250 record state = { nRequired : int , nOwners : int @@ -68,7 +68,7 @@ contract MultiSig = switch(check_pending(callhash())) CheckFail(state') => { state = state' } CheckOk(state') => - if(state.nOwners >= maxOwners) () /* TODO */ + if(state.nOwners >= maxOwners()) () /* TODO */ else let nOwners' = state'.nOwners + 1 { state = state' { owners = Map.insert(nOwners', newOwner, state'.owners) diff --git a/test/contracts/non_functional_entrypoint.aes b/test/contracts/non_functional_entrypoint.aes new file mode 100644 index 0000000..4162e38 --- /dev/null +++ b/test/contracts/non_functional_entrypoint.aes @@ -0,0 +1,5 @@ +contract C1 = + entrypoint f : int + +contract C = + entrypoint f() = 123 \ No newline at end of file diff --git a/test/contracts/not_toplevel_contract.aes b/test/contracts/not_toplevel_contract.aes new file mode 100644 index 0000000..4d2c579 --- /dev/null +++ b/test/contracts/not_toplevel_contract.aes @@ -0,0 +1,6 @@ +namespace BadNs = + contract Con = + entrypoint e : () => int + +contract Con = + entrypoint foo() = 43 diff --git a/test/contracts/not_toplevel_include.aes b/test/contracts/not_toplevel_include.aes new file mode 100644 index 0000000..0b08c88 --- /dev/null +++ b/test/contracts/not_toplevel_include.aes @@ -0,0 +1,5 @@ +namespace BadNs = + include "included.aes" + +contract Con = + entrypoint foo() = 43 diff --git a/test/contracts/bad_include_and_ns.aes b/test/contracts/not_toplevel_namespace.aes similarity index 62% rename from test/contracts/bad_include_and_ns.aes rename to test/contracts/not_toplevel_namespace.aes index 42ebe67..df27823 100644 --- a/test/contracts/bad_include_and_ns.aes +++ b/test/contracts/not_toplevel_namespace.aes @@ -1,5 +1,4 @@ -contract Bad = - include "included.aes" +contract BadCon = namespace Foo = function foo() = 42 diff --git a/test/contracts/toplevel_let.aes b/test/contracts/toplevel_let.aes new file mode 100644 index 0000000..adca04c --- /dev/null +++ b/test/contracts/toplevel_let.aes @@ -0,0 +1,3 @@ +contract C = + let this_is_illegal = 2/0 + entrypoint this_is_legal() = 2/0 \ No newline at end of file diff --git a/test/contracts/voting.aes b/test/contracts/voting.aes index d786f6d..2a5ec05 100644 --- a/test/contracts/voting.aes +++ b/test/contracts/voting.aes @@ -1,16 +1,3 @@ - -/* Contract type */ -contract VotingType = - type state - function init : list(string) => state - - function giveRightToVote : address => unit - function delegate : address => unit - function vote : int => unit - function winnerName : unit => string - function currentTally : unit => list(string * int) - -/* Contract implementation */ contract Voting = // Types From dc977f735436afa80765e78711841aacd15e60ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Tue, 31 Mar 2020 13:14:13 +0200 Subject: [PATCH 08/14] Fixed example (#249) --- docs/sophia.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/sophia.md b/docs/sophia.md index 8640b4e..a1e6dfa 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -1022,8 +1022,8 @@ contract FundMe = deadline : int, goal : int } - function spend(args : spend_args) = - raw_spend(args.recipient, args.amount) + stateful function spend(args : spend_args) = + Chain.spend(args.recipient, args.amount) entrypoint init(beneficiary, deadline, goal) : state = { contributions = {}, @@ -1062,7 +1062,6 @@ contract FundMe = require(state.total >= state.goal, "Project was not funded") spend({recipient = state.beneficiary, amount = Contract.balance }) - put(state{ beneficiary = #0 }) stateful function withdraw_contributor() = if(state.total >= state.goal) From 98036eff655de0ee38e60eabb6d1a01c2f0354e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Wed, 1 Apr 2020 13:11:02 +0200 Subject: [PATCH 09/14] Update TOC in sophia.md (#245) --- docs/sophia.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/sophia.md b/docs/sophia.md index a1e6dfa..5179235 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -2,7 +2,6 @@ **Table of Contents** -- [-](#-) - [Language Features](#language-features) - [Contracts](#contracts) - [Calling other contracts](#calling-other-contracts) @@ -53,8 +52,7 @@ - [Operators types](#operators-types) - [Operator precendences](#operator-precendences) - [Examples](#examples) - - [The lifetime of a contract](#the-lifetime-of-a-contract) - - [Killing a contract](#killing-a-contract) + ## The Sophia Language An Æternity BlockChain Language From 93341dc13b084f4056457f2057b14f18da7298e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Thu, 2 Apr 2020 12:29:14 +0200 Subject: [PATCH 10/14] Prepared for REPL usage (#218) * Prepared for REPL usage Exposed expr parsing ets init in constant Exposing and fixing exposed expr This will be squashed either Expose letdef Error handling exposed autoimport remove unnecessary changes Fix types Parser update Expose body parser remove map_get warning make dialyzer happy * Formatting Co-Authored-By: Hans Svensson Co-authored-by: Hans Svensson --- src/aeso_ast_infer_types.erl | 6 ++++- src/aeso_compiler.erl | 4 ++- src/aeso_parser.erl | 50 +++++++++++++++++++++++------------- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 950131c..84f8683 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -12,7 +12,11 @@ -module(aeso_ast_infer_types). --export([infer/1, infer/2, unfold_types_in_type/3]). +-export([ infer/1 + , infer/2 + , unfold_types_in_type/3 + , pp_type/2 + ]). -type utype() :: {fun_t, aeso_syntax:ann(), named_args_t(), [utype()], utype()} | {app_t, aeso_syntax:ann(), utype(), [utype()]} diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index ae25a65..ad5288b 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -38,6 +38,7 @@ | pp_assembler | pp_bytecode | no_code + | keep_included | {backend, aevm | fate} | {include, {file_system, [string()]} | {explicit_files, #{string() => binary()}}} @@ -650,8 +651,9 @@ pp_fate_type(T) -> io_lib:format("~w", [T]). %% ------------------------------------------------------------------- +-spec sophia_type_to_typerep(string()) -> {error, bad_type} | {ok, aeb_aevm_data:type()}. sophia_type_to_typerep(String) -> - {ok, Ast} = aeso_parser:type(String), + Ast = aeso_parser:run_parser(aeso_parser:type(), String), try aeso_ast_to_icode:ast_typerep(Ast) of Type -> {ok, Type} catch _:_ -> {error, bad_type} diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 16212ce..acb09c6 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -3,20 +3,33 @@ %%% Description : %%% Created : 1 Mar 2018 by Ulf Norell -module(aeso_parser). +-compile({no_auto_import,[map_get/2]}). -export([string/1, string/2, string/3, + auto_imports/1, hash_include/2, - type/1]). + decl/0, + type/0, + body/0, + maybe_block/1, + run_parser/2, + run_parser/3]). -include("aeso_parse_lib.hrl"). -import(aeso_parse_lib, [current_file/0, set_current_file/1]). --type parse_result() :: aeso_syntax:ast() | none(). +-type parse_result() :: aeso_syntax:ast() | {aeso_syntax:ast(), sets:set(include_hash())} | none(). -type include_hash() :: {string(), binary()}. + +escape_errors({ok, Ok}) -> + Ok; +escape_errors({error, Err}) -> + parse_error(Err). + -spec string(string()) -> parse_result(). string(String) -> string(String, sets:new(), []). @@ -30,21 +43,17 @@ string(String, Opts) -> -spec string(string(), sets:set(include_hash()), aeso_compiler:options()) -> parse_result(). string(String, Included, Opts) -> - case parse_and_scan(file(), String, Opts) of - {ok, AST} -> - case expand_includes(AST, Included, Opts) of - {ok, AST1} -> AST1; - {error, Err} -> parse_error(Err) - end; - {error, Err} -> - parse_error(Err) + AST = run_parser(file(), String, Opts), + case expand_includes(AST, Included, Opts) of + {ok, AST1} -> AST1; + {error, Err} -> parse_error(Err) end. -type(String) -> - case parse_and_scan(type(), String, []) of - {ok, AST} -> {ok, AST}; - {error, Err} -> {error, [mk_error(Err)]} - end. + +run_parser(P, Inp) -> + escape_errors(parse_and_scan(P, Inp, [])). +run_parser(P, Inp, Opts) -> + escape_errors(parse_and_scan(P, Inp, Opts)). parse_and_scan(P, S, Opts) -> set_current_file(proplists:get_value(src_file, Opts, no_file)), @@ -102,7 +111,7 @@ decl() -> %% Function declarations , ?RULE(modifiers(), fun_or_entry(), maybe_block(fundef_or_decl()), fun_block(_1, _2, _3)) - , ?RULE(keyword('let'), valdef(),set_pos(get_pos(_1), _2)) + , ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) ])). fun_block(Mods, Kind, [Decl]) -> @@ -596,8 +605,13 @@ expand_includes(AST, Included, Opts) -> || File <- lists:usort(auto_imports(AST)) ] ++ AST, expand_includes(AST1, Included, [], Opts). -expand_includes([], _Included, Acc, _Opts) -> - {ok, lists:reverse(Acc)}; +expand_includes([], Included, Acc, Opts) -> + case lists:member(keep_included, Opts) of + false -> + {ok, lists:reverse(Acc)}; + true -> + {ok, {lists:reverse(Acc), Included}} + end; expand_includes([{include, Ann, {string, _SAnn, File}} | AST], Included, Acc, Opts) -> case get_include_code(File, Ann, Opts) of {ok, Code} -> From 85b151aa65b138d979ba2d7f37c8bc67f6f8d654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Thu, 2 Apr 2020 13:59:22 +0200 Subject: [PATCH 11/14] Prepare 4.3.0 (#254) --- CHANGELOG.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2a3daa..98b3f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added ### Changed -- Bug fix: Fixed ACI encoder to handle `-` unary operator ### Removed +## [4.3.0] +### Added +- Added documentation (moved from `protocol`) +- `Frac.aes` – library for rational numbers +- Added some more meaningful error messages +- Exported several parsing functionalities + - With option `keep_included` it is possible to see which files were included during the parse + - There is a function `run_parser` that be used to evaluate any parsing rule + - Exported parsers: `body`, `type` and `decl` +### Changed +- Performance improvements in the standard library +- Fixed ACI encoder to handle `-` unary operator +- Fixed including by absolute path +- Fixed variant type printing in the ACI error messages +- Fixed pretty printing of combined function clauses +### Removed +- `let` definitions are no longer supported in the toplevel of the contract +- type declarations are no longer supported + ## [4.2.0] - 2020-01-15 ### Added - Allow separate entrypoint/function type signature and definition, and pattern From 962ddf5303bfe05192a2ee01fe848fe79a580a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Rowicki?= <35342116+radrow@users.noreply.github.com> Date: Thu, 2 Apr 2020 15:10:53 +0200 Subject: [PATCH 12/14] =?UTF-8?q?Version=20push=20=E2=80=93=204.3.0=20(#25?= =?UTF-8?q?5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Version push – 4.3.0 . * Fixed ordering of diffs in CHANGELOG --- CHANGELOG.md | 11 ++++++----- rebar.config | 2 +- src/aesophia.app.src | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 98b3f61..59355bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,7 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (de-)construct byte arrays. - `[a..b]` language construct, returning the list of numbers between `a` and `b` (inclusive). Returns the empty list if `a` > `b`. -- [Standard libraries] (https://github.com/aeternity/protocol/blob/master/contracts/sophia_stdlib.md) +- [Standard libraries](https://github.com/aeternity/aesophia/blob/master/docs/sophia_stdlib.md) - Checks that `init` is not called from other functions. - FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts @@ -211,10 +211,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplify calldata creation - instead of passing a compiled contract, simply pass a (stubbed) contract string. -[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.2.0...HEAD -[4.2.0]: https://github.com/aeternity/aesophia/compare/v4.2.0...v4.1.0 -[4.1.0]: https://github.com/aeternity/aesophia/compare/v4.1.0...v4.0.0 -[4.0.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v3.2.0 +[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.3.0...HEAD +[4.3.0]: https://github.com/aeternity/aesophia/compare/v4.2.0...v4.3.0 +[4.2.0]: https://github.com/aeternity/aesophia/compare/v4.1.0...v4.2.0 +[4.1.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v4.1.0 +[4.0.0]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.0 [3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0 [3.1.0]: https://github.com/aeternity/aesophia/compare/v3.0.0...v3.1.0 [3.0.0]: https://github.com/aeternity/aesophia/compare/v2.1.0...v3.0.0 diff --git a/rebar.config b/rebar.config index febec86..160f89e 100644 --- a/rebar.config +++ b/rebar.config @@ -15,7 +15,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "4.2.0"}, +{relx, [{release, {aesophia, "4.3.0"}, [aesophia, aebytecode, getopt]}, {dev_mode, true}, diff --git a/src/aesophia.app.src b/src/aesophia.app.src index d224976..e5744a0 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,6 +1,6 @@ {application, aesophia, [{description, "Contract Language for aeternity"}, - {vsn, "4.2.0"}, + {vsn, "4.3.0"}, {registered, []}, {applications, [kernel, From adb3cf5406cab1e794cf65ed54dcf8714bf46ce0 Mon Sep 17 00:00:00 2001 From: radrow Date: Wed, 29 Apr 2020 15:27:40 +0200 Subject: [PATCH 13/14] Update documentation to master --- docs/sophia.md | 53 ++- docs/sophia_stdlib.md | 732 +++++++++++++++++++++++++++--------------- 2 files changed, 526 insertions(+), 259 deletions(-) diff --git a/docs/sophia.md b/docs/sophia.md index 5179235..a75062a 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) @@ -136,6 +139,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 +571,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 +607,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 diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index df13842..ece1f73 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) ``` @@ -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 ``` @@ -406,7 +384,7 @@ needs to be provided. Proving that we have the private key of the oracle by signing the oracle query id + contract address -### extend +#### extend ``` Oracle.extend(, o : oracle('a, 'b), ttl : Chain.ttl) : unit ``` @@ -416,7 +394,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 +402,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 +415,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 +423,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 +440,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 ``` @@ -470,7 +457,25 @@ ignored, and can be left out since it is a named argument. Otherwise we need a signature to prove that we are allowed to do AENS operations on behalf of `owner` -### 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) ``` @@ -481,7 +486,21 @@ 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 ``` @@ -490,7 +509,7 @@ The signature should be over `owner address` + `Contract.address` (concatenated as byte arrays). -### claim +#### claim ``` AENS.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit ``` @@ -499,7 +518,7 @@ The signature should be over `owner address` + `name_hash` + `Contract.address` using the private key of the `owner` account for signing. -### transfer +#### transfer ``` AENS.transfer(owner : address, new_owner : address, name_hash : hash, ) : unit ``` @@ -510,7 +529,7 @@ The signature should be over `owner address` + `name_hash` + `Contract.address` using the private key of the `owner` account for signing. -### revoke +#### revoke ``` AENS.revoke(owner : address, name_hash : hash, ) : unit ``` @@ -521,11 +540,22 @@ The signature should be over `owner address` + `name_hash` + `Contract.address` using the private key of the `owner` account for signing. +#### update +``` +AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int), + new_ptrs : map(string, AENS.pointee), ) : unit +``` + +Updates the name. If the optional parameters are set to `None` that parameter +will not be updated, for example if `None` is passed as `expiry` the expiry +block of the name is not changed. + + ## Contract Values related to the current contract -### creator +#### creator ``` Contract.creator : address ``` @@ -533,7 +563,7 @@ Contract.creator : address Address of the entity that signed the contract creation transaction -### address +#### address ``` Contract.address : address ``` @@ -541,7 +571,7 @@ Contract.address : address Address of the contract account -### balance +#### balance ``` Contract.balance : int ``` @@ -553,7 +583,7 @@ Amount of coins in the contract account Values related to the call to the current contract -### origin +#### origin ``` Call.origin : address ``` @@ -561,14 +591,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 ``` @@ -576,7 +606,7 @@ Call.value : int The amount of coins transferred to the contract in the call. -### gas +#### gas ``` Call.gas_price : int ``` @@ -584,7 +614,7 @@ Call.gas_price : int The gas price of the current call. -### gas +#### gas ``` Call.gas_left() : int ``` @@ -596,7 +626,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 ``` @@ -604,7 +673,7 @@ Chain.balance(a : address) : int The balance of account `a`. -### block_hash +#### block_hash ``` Chain.block_hash(h : int) : option(bytes(32)) ``` @@ -612,7 +681,7 @@ Chain.block_hash(h : int) : option(bytes(32)) The hash of the block at height `h`. -### block_height +#### block_height ``` Chain.block_height : int" ``` @@ -620,7 +689,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 ``` @@ -628,7 +697,7 @@ Chain.coinbase : address The address of the account that mined the current block. -### timestamp +#### timestamp ``` Chain.timestamp : int ``` @@ -636,7 +705,7 @@ Chain.timestamp : int The timestamp of the current block. -### difficulty +#### difficulty ``` Chain.difficulty : int ``` @@ -644,7 +713,7 @@ Chain.difficulty : int The difficulty of the current block. -### gas +#### gas ``` Chain.gas_limit : int ``` @@ -652,7 +721,7 @@ Chain.gas_limit : int The gas limit of the current block. -### event +#### event ``` Chain.event(e : event) : unit ``` @@ -667,7 +736,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 ``` @@ -675,7 +744,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) ``` @@ -683,7 +752,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)) ``` @@ -691,7 +760,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) ``` @@ -699,7 +768,7 @@ List.last(l : list('a)) : option('a) Returns `Some` of the last element of a list or `None` if the list is empty. -### find +#### find ``` List.find(p : 'a => bool, l : list('a)) : option('a) ``` @@ -707,7 +776,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) ``` @@ -715,7 +784,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) ``` @@ -723,7 +792,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 ``` @@ -731,7 +800,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 ``` @@ -739,7 +808,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) ``` @@ -747,7 +816,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) ``` @@ -755,7 +824,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) ``` @@ -763,7 +832,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) ``` @@ -775,7 +844,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) ``` @@ -787,7 +856,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 ``` @@ -796,7 +865,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 ``` @@ -804,7 +873,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 ``` @@ -812,7 +881,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) ``` @@ -820,7 +889,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) ``` @@ -832,7 +901,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) ``` @@ -844,7 +913,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) ``` @@ -856,7 +925,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) ``` @@ -864,7 +933,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) ``` @@ -872,7 +941,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) ``` @@ -880,7 +949,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) ``` @@ -888,7 +957,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)) ``` @@ -900,7 +969,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) ``` @@ -908,7 +977,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 ``` @@ -916,7 +985,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 ``` @@ -924,7 +993,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 ``` @@ -932,7 +1001,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 ``` @@ -940,7 +1009,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) ``` @@ -952,14 +1021,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) ``` @@ -967,7 +1036,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) ``` @@ -975,7 +1044,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) ``` @@ -987,7 +1056,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) ``` @@ -999,7 +1068,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 ``` @@ -1007,7 +1076,7 @@ Option.is_none(o : option('a)) : bool Returns true iff `o == None` -### is_some +#### is_some ``` Option.is_some(o : option('a)) : bool ``` @@ -1015,7 +1084,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 ``` @@ -1023,7 +1092,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 ``` @@ -1031,7 +1100,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 ``` @@ -1039,7 +1108,7 @@ Option.force(o : option('a)) : 'a Forcefully escapes `option` wrapping assuming it is `Some`. Throws error on `None`. -### on_elem +#### on_elem ``` Option.on_elem(o : option('a), f : 'a => unit) : unit ``` @@ -1047,7 +1116,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) ``` @@ -1055,7 +1124,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) ``` @@ -1071,7 +1140,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) ``` @@ -1079,7 +1148,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) ``` @@ -1095,7 +1164,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) ``` @@ -1111,7 +1180,7 @@ flat_map((x) => Some(x + 1), None) will yield `None`. -### to_list +#### to_list ``` Option.to_list(o : option('a)) : list('a) ``` @@ -1119,7 +1188,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) ``` @@ -1131,7 +1200,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)) ``` @@ -1147,7 +1216,7 @@ seq_options([Some(1), Some(2), None]) will yield `None`. -### choose +#### choose ``` Option.choose(o1 : option('a), o2 : option('a)) : option('a) ``` @@ -1155,7 +1224,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) ``` @@ -1163,11 +1232,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 ``` @@ -1175,7 +1360,7 @@ Func.id(x : 'a) : 'a Identity function. Returns its argument. -### const +#### const ``` Func.const(x : 'a) : 'b => 'a = (y) => x ``` @@ -1183,7 +1368,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 ``` @@ -1191,7 +1376,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 ``` @@ -1199,7 +1384,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 ``` @@ -1207,7 +1392,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 ``` @@ -1215,7 +1400,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 ``` @@ -1247,7 +1432,7 @@ let factorial_c(n, step) = ``` -### iter +#### iter ``` Func.iter(n : int, f : 'a => 'a) : 'a => 'a ``` @@ -1255,7 +1440,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)) @@ -1266,7 +1451,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 @@ -1275,7 +1460,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 @@ -1284,7 +1469,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 @@ -1297,7 +1482,7 @@ Opposite to [tuplify](#tuplify). Common operations on 2-tuples. -### fst +#### fst ``` Pair.fst(t : ('a * 'b)) : 'a ``` @@ -1305,7 +1490,7 @@ Pair.fst(t : ('a * 'b)) : 'a First element projection. -### snd +#### snd ``` Pair.snd(t : ('a * 'b)) : 'b ``` @@ -1313,7 +1498,7 @@ Pair.snd(t : ('a * 'b)) : 'b Second element projection. -### map1 +#### map1 ``` Pair.map1(f : 'a => 'c, t : ('a * 'b)) : ('c * 'b) ``` @@ -1321,7 +1506,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) ``` @@ -1329,7 +1514,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) ``` @@ -1337,7 +1522,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) ``` @@ -1347,7 +1532,7 @@ Swaps elements. ## Triple -### fst +#### fst ``` Triple.fst(t : ('a * 'b * 'c)) : 'a ``` @@ -1355,7 +1540,7 @@ Triple.fst(t : ('a * 'b * 'c)) : 'a First element projection. -### snd +#### snd ``` Triple.snd(t : ('a * 'b * 'c)) : 'b ``` @@ -1363,7 +1548,7 @@ Triple.snd(t : ('a * 'b * 'c)) : 'b Second element projection. -### thd +#### thd ``` Triple.thd(t : ('a * 'b * 'c)) : 'c ``` @@ -1371,7 +1556,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) ``` @@ -1379,7 +1564,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) ``` @@ -1387,7 +1572,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) ``` @@ -1395,7 +1580,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) ``` @@ -1403,7 +1588,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) ``` @@ -1411,7 +1596,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) ``` @@ -1419,7 +1604,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) ``` @@ -1429,196 +1614,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 ``` @@ -1649,192 +1862,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 From d2dcb9e249ca8710ab3839f5045fa528608e2764 Mon Sep 17 00:00:00 2001 From: radrow Date: Thu, 30 Apr 2020 00:02:26 +0200 Subject: [PATCH 14/14] Add AENS example, readd delegation signature chapter, fix links --- docs/sophia.md | 47 +++++++++++++++++++++++++++++++++++++++++++ docs/sophia_stdlib.md | 12 +++++------ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/docs/sophia.md b/docs/sophia.md index a75062a..6478a1d 100644 --- a/docs/sophia.md +++ b/docs/sophia.md @@ -37,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) @@ -55,6 +56,7 @@ - [Operators types](#operators-types) - [Operator precendences](#operator-precendences) - [Examples](#examples) + - [Delegation signature](#delegation-signature) ## The Sophia Language @@ -686,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 @@ -1110,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 ece1f73..158b8df 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -348,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 account address + the 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. @@ -381,7 +381,7 @@ 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 oracle query id + contract address +[signing](./sophia.md#delegation-signature) the oracle query id + contract address #### extend @@ -505,7 +505,7 @@ let Some(Name(owner, FixedTTL(expiry), ptrs)) = AENS.lookup("example.chain") AENS.preclaim(owner : address, commitment_hash : hash, ) : unit ``` -The signature should be over `owner address` + `Contract.address` +The [signature](./sophia.md#delegation-signature) should be over `owner address` + `Contract.address` (concatenated as byte arrays). @@ -514,7 +514,7 @@ The signature should be over `owner address` + `Contract.address` AENS.claim(owner : address, name : string, salt : int, name_fee : int, ) : unit ``` -The signature should be over `owner address` + `name_hash` + `Contract.address` +The [signature](./sophia.md#delegation-signature) should be over `owner address` + `name_hash` + `Contract.address` using the private key of the `owner` account for signing. @@ -525,7 +525,7 @@ AENS.transfer(owner : address, new_owner : address, name_hash : hash, ) : unit Revokes the name to extend the ownership time. -The signature should be over `owner address` + `name_hash` + `Contract.address` +The [signature](./sophia.md#delegation-signature) should be over `owner address` + `name_hash` + `Contract.address` using the private key of the `owner` account for signing.