From f60f9122ba392ad54267001336d72fed6c50d451 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 29 Oct 2021 13:34:40 +0200 Subject: [PATCH] [Ceres]: Add bitwise ops, Address.to_bytes and Crypto.poseidon --- CHANGELOG.md | 10 +++ docs/sophia_features.md | 8 +++ docs/sophia_stdlib.md | 104 ++++++----------------------- docs/sophia_syntax.md | 11 ++- priv/stdlib/Bitwise.aes | 126 ----------------------------------- src/aeso_ast_infer_types.erl | 17 ++++- src/aeso_ast_to_fcode.erl | 19 +++--- src/aeso_fcode_to_fate.erl | 18 +++++ src/aeso_parser.erl | 11 ++- src/aeso_pretty.erl | 14 ++-- src/aeso_scan.erl | 2 +- src/aeso_syntax.erl | 4 +- test/aeso_compiler_tests.erl | 1 + test/aeso_parser_tests.erl | 2 +- test/aeso_scan_tests.erl | 3 +- test/contracts/ceres.aes | 14 ++++ 16 files changed, 128 insertions(+), 236 deletions(-) delete mode 100644 priv/stdlib/Bitwise.aes create mode 100644 test/contracts/ceres.aes diff --git a/CHANGELOG.md b/CHANGELOG.md index 683f96d..daff8e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [CERES 8.0.0] +### Added +- Bitwise operations for integers: `band`, `bor`, `bxor`, `bnot`, `<<` and `>>`. +- `Int.mulmod` - combined builtin operation for multiplication and modulus. +- `Crypto.poseidon` - a ZK/SNARK-friendly hash function (over the BLS12-381 scalar field). +- `Address.to_bytes` - convert an address to its binary representation (for hashing, etc.). +### Changed +### Removed +- `Bitwise.aes` standard library is removed - the builtin operations are superior. + ## [Unreleased] ### Added ### Changed diff --git a/docs/sophia_features.md b/docs/sophia_features.md index a696441..487bb42 100644 --- a/docs/sophia_features.md +++ b/docs/sophia_features.md @@ -632,6 +632,14 @@ All operations are *safe* with respect to overflow and underflow. The division and modulo operations throw an arithmetic error if the right-hand operand is zero. +Sophia arbitrary-sized integers (FATE) also supports the following bitwise operations: +- bitwise and (`x band y`) +- bitwise or (`x bor y`) +- bitwise xor (`x bxor y`) +- bitwise not (`bnot x`) +- arithmetic bitshift left (`x << n`) +- arithmetic bitshift right (`x >> n`) + ## Bit fields Sophia integers do not support bit arithmetic. Instead there is a separate diff --git a/docs/sophia_stdlib.md b/docs/sophia_stdlib.md index 9fcdd7e..7d5c91d 100644 --- a/docs/sophia_stdlib.md +++ b/docs/sophia_stdlib.md @@ -381,6 +381,12 @@ Call.gas_price : int The gas price of the current call. +#### mulmod +``` +Int.mulmod : (a : int, b : int, q : int) : int +``` + +Combined multiplication and modulus, returns `(a * b) mod q`. #### fee ``` @@ -469,6 +475,13 @@ Chain.block_height : int" The height of the current block (i.e. the block in which the current call will be included). +#### to_bytes +``` +Address.to_bytes(a : address) : bytes(32) +``` + +The binary representation of the address. + ##### bytecode_hash ``` @@ -506,6 +519,13 @@ charging the calling contract. Note that this won't be visible in `Call.value` in the `init` call of the new contract. It will be included in `Contract.balance`, however. +#### poseidon +``` +Crypto.poseidon(x1 : int, x2 : int) : int +``` + +Hash two integers (in the scalar field of BLS12-381) to another integer (in the scalar +field of BLS12-281). This is a ZK/SNARK-friendly hash function. The type `'c` must be instantiated with a contract. @@ -926,90 +946,6 @@ It returns `true` iff the oracle query exist and has the expected type. These need to be explicitly included (with `.aes` suffix) -### Bitwise - -Bitwise operations on arbitrary precision integers. - -#### bsr -``` -Bitwise.bsr(n : int, x : int) : int -``` - -Logical bit shift `x` right `n` positions. - - -#### bsl -``` -Bitwise.bsl(n : int, x : int) : int -``` - -Logical bit shift `x` left `n` positions. - - -#### bsli -``` -Bitwise.bsli(n : int, x : int, lim : int) : int -``` - -Logical bit shift `x` left `n` positions, limit to `lim` bits. - - -#### band -``` -Bitwise.band(x : int, y : int) : int -``` - -Bitwise `and` of `x` and `y`. - - -#### bor -``` -Bitwise.bor(x : int, y : int) : int -``` - -Bitwise `or` of `x` and `y`. - - -#### bxor -``` -Bitwise.bxor(x : int, y : int) : int -``` - -Bitwise `xor` of `x` and `y`. - - -#### bnot -``` -Bitwise.bnot(x : int) : int -``` - -Bitwise `not` of `x`. Defined and implemented as `bnot(x) = bxor(x, -1)`. - - -#### uband -``` -Bitwise.uband(x : int, y : int) : int -``` - -Bitwise `and` of _non-negative_ numbers `x` and `y`. - - -#### ubor -``` -Bitwise.ubor(x : int, y : int) : int -``` - -Bitwise `or` of _non-negative_ `x` and `y`. - - -#### ubxor -``` -Bitwise.ubxor(x : int, y : int) : int -``` - -Bitwise `xor` of _non-negative_ `x` and `y`. - - ### BLS12\_381 #### Types diff --git a/docs/sophia_syntax.md b/docs/sophia_syntax.md index f0df9e6..712c9ce 100644 --- a/docs/sophia_syntax.md +++ b/docs/sophia_syntax.md @@ -256,8 +256,8 @@ Path ::= Id // Record field BinOp ::= '||' | '&&' | '<' | '>' | '=<' | '>=' | '==' | '!=' | '::' | '++' | '+' | '-' | '*' | '/' | 'mod' | '^' - | '|>' -UnOp ::= '-' | '!' + | 'band' | 'bor' | 'bxor' | '<<' | '>>' | '|>' +UnOp ::= '-' | '!' | 'bnot' ``` ## Operators types @@ -266,6 +266,7 @@ UnOp ::= '-' | '!' | --- | --- | `-` `+` `*` `/` `mod` `^` | arithmetic operators | `!` `&&` `||` | logical operators +| `band` `bor` `bxor` `bnot` `<<` `>>` | bitwise operators | `==` `!=` `<` `>` `=<` `>=` | comparison operators | `::` `++` | list operators | `|>` | functional operators @@ -276,13 +277,17 @@ In order of highest to lowest precedence. | Operators | Associativity | --- | --- -| `!` | right +| `!` `bnot`| right | `^` | left | `*` `/` `mod` | left | `-` (unary) | right | `+` `-` | left +| `<<` `>>` | left | `::` `++` | right | `<` `>` `=<` `>=` `==` `!=` | none +| `band` | left +| `bxor` | left +| `bor` | left | `&&` | right | `||` | right | `|>` | left diff --git a/priv/stdlib/Bitwise.aes b/priv/stdlib/Bitwise.aes deleted file mode 100644 index cc273f0..0000000 --- a/priv/stdlib/Bitwise.aes +++ /dev/null @@ -1,126 +0,0 @@ -@compiler >= 4.3 - -namespace Bitwise = - - // bit shift 'x' right 'n' postions - function bsr(n : int, x : int) : int = - let step = 2^n - let res = x / step - if (x >= 0 || x mod step == 0) - res - else - res - 1 - - // bit shift 'x' left 'n' positions - function bsl(n : int, x : int) : int = - x * 2^n - - // bit shift 'x' left 'n' positions, limit at 'lim' bits - function bsli(n : int, x : int, lim : int) : int = - (x * 2^n) mod (2^lim) - - // bitwise 'and' for arbitrary precision integers - function band(a : int, b : int) : int = - if (a >= 0 && b >= 0) - uband_(a, b) - elif (b >= 0) - ubnand_(b, -1 - a) - elif (a >= 0) - ubnand_(a, -1 - b) - else - -1 - ubor_(-1 - a, -1 - b) - - // bitwise 'or' for arbitrary precision integers - function - bor : (int, int) => int - bor(0, b) = b - bor(a, 0) = a - bor(a : int, b : int) : int = - if (a >= 0 && b >= 0) - ubor_(a, b) - elif (b >= 0) - -1 - ubnand_(-1 - a, b) - elif (a >= 0) - -1 - ubnand_(-1 - b, a) - else - -1 - uband_(-1 - a, -1 - b) - - // bitwise 'xor' for arbitrary precision integers - function - bxor : (int, int) => int - bxor(0, b) = b - bxor(a, 0) = a - bxor(a, b) = - if (a >= 0 && b >= 0) - ubxor_(a, b) - elif (b >= 0) - -1 - ubxor_(-1 - a, b) - elif (a >= 0) - -1 - ubxor_(a, -1 - b) - else - ubxor_(-1 - a, -1 - b) - - // bitwise 'not' for arbitrary precision integers - function bnot(a : int) = bxor(a, -1) - - // Bitwise 'and' for non-negative integers - function uband(a : int, b : int) : int = - require(a >= 0 && b >= 0, "uband is only defined for non-negative integers") - switch((a, b)) - (0, _) => 0 - (_, 0) => 0 - _ => uband__(a, b, 1, 0) - - private function uband_(a, b) = uband__(a, b, 1, 0) - - private function - uband__(0, b, val, acc) = acc - uband__(a, 0, val, acc) = acc - uband__(a, b, val, acc) = - switch (a mod 2 + b mod 2) - 2 => uband__(a / 2, b / 2, val * 2, acc + val) - _ => uband__(a / 2, b / 2, val * 2, acc) - - // Bitwise 'or' for non-negative integers - function ubor(a, b) = - require(a >= 0 && b >= 0, "ubor is only defined for non-negative integers") - switch((a, b)) - (0, _) => b - (_, 0) => a - _ => ubor__(a, b, 1, 0) - - private function ubor_(a, b) = ubor__(a, b, 1, 0) - - private function - ubor__(0, 0, val, acc) = acc - ubor__(a, b, val, acc) = - switch (a mod 2 + b mod 2) - 0 => ubor__(a / 2, b / 2, val * 2, acc) - _ => ubor__(a / 2, b / 2, val * 2, acc + val) - - //Bitwise 'xor' for non-negative integers - function - ubxor : (int, int) => int - ubxor(0, b) = b - ubxor(a, 0) = a - ubxor(a, b) = - require(a >= 0 && b >= 0, "ubxor is only defined for non-negative integers") - ubxor__(a, b, 1, 0) - - private function ubxor_(a, b) = ubxor__(a, b, 1, 0) - - private function - ubxor__(0, 0, val, acc) = acc - ubxor__(a, b, val, acc) = - switch(a mod 2 + b mod 2) - 1 => ubxor__(a / 2, b / 2, val * 2, acc + val) - _ => ubxor__(a / 2, b / 2, val * 2, acc) - - private function ubnand_(a, b) = ubnand__(a, b, 1, 0) - - private function - ubnand__(0, b, val, acc) = acc - ubnand__(a, b, val, acc) = - switch((a mod 2, b mod 2)) - (1, 0) => ubnand__(a / 2, b / 2, val * 2, acc + val) - _ => ubnand__(a / 2, b / 2, val * 2, acc) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 2af3b62..5bf8e4f 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -764,7 +764,8 @@ global_env() -> {"ecrecover_secp256k1", Fun([Hash, Bytes(65)], Option(Bytes(20)))}, {"sha3", Fun1(A, Hash)}, {"sha256", Fun1(A, Hash)}, - {"blake2b", Fun1(A, Hash)}]) }, + {"blake2b", Fun1(A, Hash)}, + {"poseidon", Fun([Int, Int], Int)}]) }, %% Fancy BLS12-381 crypto operations MCL_BLS12_381_Scope = #scope @@ -849,14 +850,16 @@ global_env() -> ]) }, %% Conversion - IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) }, + IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}, + {"mulmod", Fun([Int, Int, Int], Int)}]) }, + AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}, + {"to_bytes", Fun1(Address, Bytes(32))}, {"to_contract", FunC(address_to_contract, [Address], A)}, {"is_oracle", Fun1(Address, Bool)}, {"is_contract", Fun1(Address, Bool)}, {"is_payable", Fun1(Address, Bool)}]) }, - #env{ scopes = #{ [] => TopScope , ["Chain"] => ChainScope @@ -2396,6 +2399,11 @@ infer_infix({IntOp, As}) IntOp == '^'; IntOp == 'mod' -> Int = {id, As, "int"}, {fun_t, As, [], [Int, Int], Int}; +infer_infix({BitOp, As}) + when BitOp == 'band'; BitOp == 'bor'; BitOp == 'bxor'; + BitOp == '<<'; BitOp == '>>' -> + Int = {id, As, "int"}, + {fun_t, As, [], [Int, Int], Int}; infer_infix({RelOp, As}) when RelOp == '=='; RelOp == '!='; RelOp == '<'; RelOp == '>'; @@ -2423,6 +2431,9 @@ infer_infix({'|>', As}) -> infer_prefix({'!',As}) -> Bool = {id, As, "bool"}, {fun_t, As, [], [Bool], Bool}; +infer_prefix({BitOp,As}) when BitOp =:= 'bnot' -> + Int = {id, As, "int"}, + {fun_t, As, [], [Int], Int}; infer_prefix({IntOp,As}) when IntOp =:= '-' -> Int = {id, As, "int"}, {fun_t, As, [], [Int], Int}. diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 38da87e..583ce11 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -32,12 +32,13 @@ -type op() :: '+' | '-' | '*' | '/' | mod | '^' | '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!=' | '!' | + 'band' | 'bor' | 'bxor' | 'bnot' | '<<' | '>>' | map_get | map_get_d | map_set | map_from_list | map_to_list | map_delete | map_member | map_size | string_length | string_concat | bits_set | bits_clear | bits_test | bits_sum | bits_intersection | bits_union | bits_difference | contract_to_address | address_to_contract | crypto_verify_sig | crypto_verify_sig_secp256k1 | - crypto_sha3 | crypto_sha256 | crypto_blake2b | + crypto_sha3 | crypto_sha256 | crypto_blake2b | crypto_poseidon | crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1 | mcl_bls12_381_g1_neg | mcl_bls12_381_g1_norm | mcl_bls12_381_g1_valid | mcl_bls12_381_g1_is_zero | mcl_bls12_381_g1_add | mcl_bls12_381_g1_mul | @@ -278,7 +279,7 @@ builtins() -> {"lookup_default", 3}, {"delete", 2}, {"member", 2}, {"size", 1}]}, {["Crypto"], [{"verify_sig", 3}, {"verify_sig_secp256k1", 3}, {"ecverify_secp256k1", 3}, {"ecrecover_secp256k1", 2}, - {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}]}, + {"sha3", 1}, {"sha256", 1}, {"blake2b", 1}, {"poseidon", 2}]}, {["MCL_BLS12_381"], [{"g1_neg", 1}, {"g1_norm", 1}, {"g1_valid", 1}, {"g1_is_zero", 1}, {"g1_add", 2}, {"g1_mul", 2}, {"g2_neg", 1}, {"g2_norm", 1}, {"g2_valid", 1}, {"g2_is_zero", 1}, {"g2_add", 2}, {"g2_mul", 2}, {"gt_inv", 1}, {"gt_add", 2}, {"gt_mul", 2}, {"gt_pow", 2}, {"gt_is_one", 1}, @@ -291,8 +292,9 @@ builtins() -> {["Bits"], [{"set", 2}, {"clear", 2}, {"test", 2}, {"sum", 1}, {"intersection", 2}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]}, - {["Int"], [{"to_str", 1}]}, - {["Address"], [{"to_str", 1}, {"to_contract", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} + {["Int"], [{"to_str", 1}, {"mulmod", 2}]}, + {["Address"], [{"to_str", 1}, {"to_bytes", 1}, {"to_contract", 1}, + {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]} ], maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}} || {NS, Funs} <- Scopes, @@ -748,8 +750,9 @@ expr_to_fcode(Env, Type, {app, Ann, {Op, _}, [A, B]}) when is_atom(Op) -> expr_to_fcode(Env, _Type, {app, Ann, {Op, _}, [A]}) when is_atom(Op) -> FAnn = to_fann(Ann), case Op of - '-' -> {op, FAnn, '-', [{lit, FAnn, {int, 0}}, expr_to_fcode(Env, A)]}; - '!' -> {op, FAnn, '!', [expr_to_fcode(Env, A)]} + '-' -> {op, FAnn, '-', [{lit, FAnn, {int, 0}}, expr_to_fcode(Env, A)]}; + 'bnot' -> {op, FAnn, 'bnot', [expr_to_fcode(Env, A)]}; + '!' -> {op, FAnn, '!', [expr_to_fcode(Env, A)]} end; %% Function calls @@ -1170,9 +1173,9 @@ op_builtins() -> stringinternal_sha3, stringinternal_sha256, stringinternal_blake2b, char_to_int, char_from_int, stringinternal_to_lower, stringinternal_to_upper, bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, - bits_difference, int_to_str, address_to_str, crypto_verify_sig, + bits_difference, int_to_str, int_mulmod, address_to_str, address_to_bytes, crypto_verify_sig, address_to_contract, - crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, + crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b, crypto_poseidon, crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1, mcl_bls12_381_g1_neg, mcl_bls12_381_g1_norm, mcl_bls12_381_g1_valid, mcl_bls12_381_g1_is_zero, mcl_bls12_381_g1_add, mcl_bls12_381_g1_mul, diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 96ffe69..95f80fe 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -667,6 +667,12 @@ op_to_scode('>=') -> aeb_fate_ops:egt(?a, ?a, ?a); op_to_scode('==') -> aeb_fate_ops:eq(?a, ?a, ?a); op_to_scode('!=') -> aeb_fate_ops:neq(?a, ?a, ?a); op_to_scode('!') -> aeb_fate_ops:not_op(?a, ?a); +op_to_scode('bnot') -> aeb_fate_ops:bin_not(?a, ?a); +op_to_scode('band') -> aeb_fate_ops:bin_and(?a, ?a, ?a); +op_to_scode('bor') -> aeb_fate_ops:bin_or(?a, ?a, ?a); +op_to_scode('bxor') -> aeb_fate_ops:bin_xor(?a, ?a, ?a); +op_to_scode('<<') -> aeb_fate_ops:bin_sl(?a, ?a, ?a); +op_to_scode('>>') -> aeb_fate_ops:bin_sr(?a, ?a, ?a); op_to_scode(map_get) -> aeb_fate_ops:map_lookup(?a, ?a, ?a); op_to_scode(map_get_d) -> aeb_fate_ops:map_lookup(?a, ?a, ?a, ?a); op_to_scode(map_set) -> aeb_fate_ops:map_update(?a, ?a, ?a, ?a); @@ -691,7 +697,9 @@ op_to_scode(bits_intersection) -> aeb_fate_ops:bits_and(?a, ?a, ?a); op_to_scode(bits_union) -> aeb_fate_ops:bits_or(?a, ?a, ?a); op_to_scode(bits_difference) -> aeb_fate_ops:bits_diff(?a, ?a, ?a); op_to_scode(address_to_str) -> aeb_fate_ops:addr_to_str(?a, ?a); +op_to_scode(address_to_bytes) -> aeb_fate_ops:addr_to_bytes(?a, ?a); op_to_scode(int_to_str) -> aeb_fate_ops:int_to_str(?a, ?a); +op_to_scode(int_mulmod) -> aeb_fate_ops:mulmod(?a, ?a, ?a, ?a); op_to_scode(contract_to_address) -> aeb_fate_ops:contract_to_address(?a, ?a); op_to_scode(address_to_contract) -> aeb_fate_ops:address_to_contract(?a, ?a); op_to_scode(crypto_verify_sig) -> aeb_fate_ops:verify_sig(?a, ?a, ?a, ?a); @@ -701,6 +709,7 @@ op_to_scode(crypto_ecrecover_secp256k1) -> aeb_fate_ops:ecrecover_secp256k1(?a, op_to_scode(crypto_sha3) -> aeb_fate_ops:sha3(?a, ?a); op_to_scode(crypto_sha256) -> aeb_fate_ops:sha256(?a, ?a); op_to_scode(crypto_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); +op_to_scode(crypto_poseidon) -> aeb_fate_ops:poseidon(?a, ?a, ?a); op_to_scode(stringinternal_sha3) -> aeb_fate_ops:sha3(?a, ?a); op_to_scode(stringinternal_sha256) -> aeb_fate_ops:sha256(?a, ?a); op_to_scode(stringinternal_blake2b) -> aeb_fate_ops:blake2b(?a, ?a); @@ -973,6 +982,13 @@ attributes(I) -> {'DIV', A, B, C} -> Pure(A, [B, C]); {'MOD', A, B, C} -> Pure(A, [B, C]); {'POW', A, B, C} -> Pure(A, [B, C]); + {'MULMOD', A, B, C, D} -> Pure(A, [B, C, D]); + {'BAND', A, B, C} -> Pure(A, [B, C]); + {'BOR', A, B, C} -> Pure(A, [B, C]); + {'BXOR', A, B, C} -> Pure(A, [B, C]); + {'BNOT', A, B} -> Pure(A, [B]); + {'BSL', A, B, C} -> Pure(A, [B, C]); + {'BSR', A, B, C} -> Pure(A, [B, C]); {'LT', A, B, C} -> Pure(A, [B, C]); {'GT', A, B, C} -> Pure(A, [B, C]); {'EQ', A, B, C} -> Pure(A, [B, C]); @@ -1025,12 +1041,14 @@ attributes(I) -> {'SHA3', A, B} -> Pure(A, [B]); {'SHA256', A, B} -> Pure(A, [B]); {'BLAKE2B', A, B} -> Pure(A, [B]); + {'POSEIDON', A, B, C} -> Pure(A, [B, C]); {'VERIFY_SIG', A, B, C, D} -> Pure(A, [B, C, D]); {'VERIFY_SIG_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'ECRECOVER_SECP256K1', A, B, C} -> Pure(A, [B, C]); {'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]); {'ADDRESS_TO_CONTRACT', A, B} -> Pure(A, [B]); + {'ADDRESS_TO_BYTES', A, B} -> Pure(A, [B]); {'AUTH_TX_HASH', A} -> Pure(A, []); {'AUTH_TX', A} -> Pure(A, []); {'BYTES_TO_INT', A, B} -> Pure(A, [B]); diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 3c03bf0..12826ae 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -335,14 +335,19 @@ expr100() -> expr150() -> infixl(expr200(), binop('|>')). expr200() -> infixr(expr300(), binop('||')). -expr300() -> infixr(expr400(), binop('&&')). +expr300() -> infixr(expr325(), binop('&&')). +expr325() -> infixl(expr350(), binop('bor')). +expr350() -> infixl(expr375(), binop('bxor')). +expr375() -> infixl(expr400(), binop('band')). expr400() -> infix(expr500(), binop(['<', '>', '=<', '>=', '==', '!='])). -expr500() -> infixr(expr600(), binop(['::', '++'])). +expr500() -> infixr(expr550(), binop(['::', '++'])). +expr550() -> infixl(expr600(), binop(['<<', '>>'])). expr600() -> infixl(expr650(), binop(['+', '-'])). expr650() -> ?RULE(many(token('-')), expr700(), prefixes(_1, _2)). expr700() -> infixl(expr750(), binop(['*', '/', mod])). expr750() -> infixl(expr800(), binop(['^'])). -expr800() -> ?RULE(many(token('!')), expr900(), prefixes(_1, _2)). +expr800() -> ?RULE(many(token('!')), expr850(), prefixes(_1, _2)). +expr850() -> ?RULE(many(token('bnot')), expr900(), prefixes(_1, _2)). expr900() -> ?RULE(exprAtom(), many(elim()), elim(_1, _2)). exprAtom() -> diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index afce62c..3ef3300 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -436,15 +436,20 @@ bin_prec('=') -> { 0, 0, 0}; %% Always printed inside '[ ]' bin_prec('@') -> { 0, 0, 0}; %% Only in error messages bin_prec('|>') -> {150, 150, 200}; bin_prec('||') -> {200, 300, 200}; -bin_prec('&&') -> {300, 400, 300}; +bin_prec('&&') -> {300, 325, 300}; +bin_prec('bor') -> {325, 350, 325}; +bin_prec('bxor') -> {350, 375, 350}; +bin_prec('band') -> {375, 400, 375}; bin_prec('<') -> {400, 500, 500}; bin_prec('>') -> {400, 500, 500}; bin_prec('=<') -> {400, 500, 500}; bin_prec('>=') -> {400, 500, 500}; bin_prec('==') -> {400, 500, 500}; bin_prec('!=') -> {400, 500, 500}; -bin_prec('++') -> {500, 600, 500}; -bin_prec('::') -> {500, 600, 500}; +bin_prec('++') -> {500, 550, 500}; +bin_prec('::') -> {500, 550, 500}; +bin_prec('<<') -> {550, 600, 550}; +bin_prec('>>') -> {550, 600, 550}; bin_prec('+') -> {600, 600, 650}; bin_prec('-') -> {600, 600, 650}; bin_prec('*') -> {700, 700, 750}; @@ -454,7 +459,8 @@ bin_prec('^') -> {750, 750, 800}. -spec un_prec(aeso_syntax:un_op()) -> {integer(), integer()}. un_prec('-') -> {650, 650}; -un_prec('!') -> {800, 800}. +un_prec('!') -> {800, 800}; +un_prec('bnot') -> {850, 850}. equals(Ann, A, B) -> {app, [{format, infix} | Ann], {'=', Ann}, [A, B]}. diff --git a/src/aeso_scan.erl b/src/aeso_scan.erl index 4587efa..a6746bc 100644 --- a/src/aeso_scan.erl +++ b/src/aeso_scan.erl @@ -45,7 +45,7 @@ lexer() -> Keywords = ["contract", "include", "let", "switch", "type", "record", "datatype", "if", "elif", "else", "function", "stateful", "payable", "true", "false", "mod", "public", "entrypoint", "private", "indexed", "namespace", - "interface", "main", "using", "as", "for", "hiding" + "interface", "main", "using", "as", "for", "hiding", "band", "bor", "bxor", "bnot" ], KW = string:join(Keywords, "|"), diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 0ff9f5e..1e8ee2d 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -107,8 +107,8 @@ -type bin_op() :: '+' | '-' | '*' | '/' | mod | '^' | '++' | '::' | '<' | '>' | '=<' | '>=' | '==' | '!=' - | '||' | '&&' | '..' | '|>'. --type un_op() :: '-' | '!'. + | '||' | '&&' | '..' | 'band' | 'bor' | 'bxor' | '>>' | '<<' | '|>'. +-type un_op() :: '-' | '!' | 'bnot'. -type expr() :: {lam, ann(), [arg()], expr()} diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 483531f..0d87a92 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -225,6 +225,7 @@ compilable_contracts() -> "unapplied_named_arg_builtin", "resolve_field_constraint_by_arity", "toplevel_constants", + "ceres", "test" % Custom general-purpose test file. Keep it last on the list. ]. diff --git a/test/aeso_parser_tests.erl b/test/aeso_parser_tests.erl index e3b7598..2324fc4 100644 --- a/test/aeso_parser_tests.erl +++ b/test/aeso_parser_tests.erl @@ -53,7 +53,7 @@ simple_contracts_test_() -> %% associativity [ RightAssoc(Op) || Op <- ["||", "&&", "::", "++"] ], [ NonAssoc(Op) || Op <- ["==", "!=", "<", ">", "=<", ">="] ], - [ LeftAssoc(Op) || Op <- ["+", "-", "*", "/", "mod"] ], + [ LeftAssoc(Op) || Op <- ["+", "-", "*", "/", "mod", "band", "bor", "bxor", "<<", ">>"] ], %% precedence [ Stronger(Op2, Op1) || [T1 , T2 | _] <- tails(Tiers), Op1 <- T1, Op2 <- T2 ], diff --git a/test/aeso_scan_tests.erl b/test/aeso_scan_tests.erl index 685d3ac..92912e0 100644 --- a/test/aeso_scan_tests.erl +++ b/test/aeso_scan_tests.erl @@ -39,7 +39,8 @@ all_tokens() -> %% Symbols lists:map(Lit, [',', '.', ';', '|', ':', '(', ')', '[', ']', '{', '}']) ++ %% Operators - lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, ':', '::', '->', '=>', '||', '&&', '!']) ++ + lists:map(Lit, ['=', '==', '!=', '>', '<', '>=', '=<', '-', '+', '++', '*', '/', mod, + ':', '::', '->', '=>', '||', '&&', '!', 'band', 'bor', 'bxor', 'bnot' ,'<<', '>>']) ++ %% Keywords lists:map(Lit, [contract, type, 'let', switch]) ++ %% Comment token (not an actual token), just for tests diff --git a/test/contracts/ceres.aes b/test/contracts/ceres.aes new file mode 100644 index 0000000..832334f --- /dev/null +++ b/test/contracts/ceres.aes @@ -0,0 +1,14 @@ +contract C = + entrypoint test() = + let a : int = 23 + let b : int = 52 + let c = a bor b + let d = c bxor b + let e = d band b + let f = bnot a + let g = f << 2 + let h = g >> 2 + let i = Int.mulmod(a, b, h) + let j = Crypto.poseidon(i, a) + let k : bytes(32) = Address.to_bytes(Call.origin) + (a bor b band c bxor a << bnot b >> a, k)