diff --git a/rebar.config b/rebar.config index 0a53b0d..1ae767f 100644 --- a/rebar.config +++ b/rebar.config @@ -3,7 +3,7 @@ {erl_opts, [debug_info]}. {deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", - {ref, "56cf62b"}}} + {ref, "e8253b0"}}} , {getopt, "1.0.1"} , {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}} diff --git a/rebar.lock b/rebar.lock index accdf9c..e4ce54e 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,7 +1,7 @@ {"1.1.0", [{<<"aebytecode">>, {git,"https://github.com/aeternity/aebytecode.git", - {ref,"56cf62b48753a61e30b81d805eee5ec36959b688"}}, + {ref,"e8253b09709f1595d8bd6a1756a0ce93185c6518"}}, 0}, {<<"aeserialization">>, {git,"https://github.com/aeternity/aeserialization.git", diff --git a/src/aeso_aci.erl b/src/aeso_aci.erl index 422fb77..1db0a01 100644 --- a/src/aeso_aci.erl +++ b/src/aeso_aci.erl @@ -24,6 +24,7 @@ %% Types -record(app_t, {ann,id,fields}). -record(tuple_t, {ann,args}). +-record(bytes_t, {ann,len}). -record(record_t, {fields}). -record(field_t, {ann,id,type}). -record(alias_t, {type}). @@ -43,7 +44,7 @@ -record(bool, {ann,bool}). -record(int, {ann,value}). -record(string, {ann,bin}). --record(hash, {ann,hash}). +-record(bytes, {ann,bin}). -record(tuple, {ann,args}). -record(list, {ann,args}). -record(app, {ann,func,args}). @@ -133,6 +134,8 @@ encode_type(#qcon{names=Ns}) -> encode_type(#tuple_t{args=As}) -> Eas = encode_types(As), [{<<"tuple">>,Eas}]; +encode_type(#bytes_t{len=Len}) -> + {<<"bytes">>, Len}; encode_type(#record_t{fields=Fs}) -> Efs = encode_fields(Fs), [{<<"record">>,Efs}]; @@ -208,7 +211,7 @@ encode_expr(#typed{expr=E}) -> encode_expr(#bool{bool=B}) -> B; encode_expr(#int{value=V}) -> V; encode_expr(#string{bin=B}) -> B; -encode_expr(#hash{hash=H}) -> H; +encode_expr(#bytes{bin=B}) -> B; encode_expr(#tuple{args=As}) -> Eas = encode_exprs(As), [{<<"tuple">>,Eas}]; diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index f8fe59f..ed9083b 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -56,7 +56,12 @@ , fields :: [aeso_syntax:id()] , context :: why_record() }). --type field_constraint() :: #field_constraint{} | #record_create_constraint{}. +-record(is_contract_constraint, + { contract_t :: utype(), + context :: aeso_syntax:expr() %% The address literal + }). + +-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}. -record(field_info, { ann :: aeso_syntax:ann() @@ -341,6 +346,7 @@ global_env() -> Address = {id, Ann, "address"}, Hash = {id, Ann, "hash"}, Bits = {id, Ann, "bits"}, + Bytes = fun(Len) -> {bytes_t, Ann, Len} end, Oracle = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle"}, [Q, R]} end, Query = fun(Q, R) -> {app_t, Ann, {id, Ann, "oracle_query"}, [Q, R]} end, Unit = {tuple_t, Ann, []}, @@ -373,7 +379,9 @@ global_env() -> {"abort", Fun1(String, A)}]) , types = MkDefs( [{"int", 0}, {"bool", 0}, {"string", 0}, {"address", 0}, - {"hash", 0}, {"signature", 0}, {"bits", 0}, + {"hash", {[], {alias_t, Bytes(32)}}}, + {"signature", {[], {alias_t, Bytes(64)}}}, + {"bits", 0}, {"option", 1}, {"list", 1}, {"map", 2}, {"oracle", 2}, {"oracle_query", 2} ]) }, @@ -439,6 +447,7 @@ global_env() -> CryptoScope = #scope { funs = MkDefs( [{"ecverify", Fun([Hash, Address, SignId], Bool)}, + {"ecverify_secp256k1", Fun([Hash, Bytes(64), Bytes(64)], Bool)}, {"sha3", Fun1(A, Hash)}, {"sha256", Fun1(A, Hash)}, {"blake2b", Fun1(A, Hash)}]) }, @@ -497,7 +506,7 @@ map_t(As, K, V) -> {app_t, As, {id, As, "map"}, [K, V]}. infer(Contracts) -> infer(Contracts, []). --type option() :: permissive_address_literals | return_env. +-type option() :: return_env. -spec init_env(list(option())) -> env(). init_env(_Options) -> global_env(). @@ -675,6 +684,9 @@ check_type(Env, X = {Tag, _, _}, Arity) when Tag == con; Tag == qcon; Tag == id; check_type(Env, Type = {tuple_t, Ann, Types}, Arity) -> ensure_base_type(Type, Arity), {tuple_t, Ann, [ check_type(Env, T, 0) || T <- Types ]}; +check_type(_Env, Type = {bytes_t, _Ann, _Len}, Arity) -> + ensure_base_type(Type, Arity), + Type; check_type(Env, {app_t, Ann, Type, Types}, Arity) -> Types1 = [ check_type(Env, T, 0) || T <- Types ], Type1 = check_type(Env, Type, Arity + length(Types)), @@ -898,20 +910,29 @@ infer_expr(_Env, Body={int, As, _}) -> {typed, As, Body, {id, As, "int"}}; infer_expr(_Env, Body={string, As, _}) -> {typed, As, Body, {id, As, "string"}}; -infer_expr(_Env, Body={hash, As, Hash}) -> - case byte_size(Hash) of - 32 -> {typed, As, Body, {id, As, "address"}}; - 64 -> {typed, As, Body, {id, As, "signature"}} - end; +infer_expr(_Env, Body={bytes, As, Bin}) -> + {typed, As, Body, {bytes_t, As, byte_size(Bin)}}; +infer_expr(_Env, Body={account_pubkey, As, _}) -> + {typed, As, Body, {id, As, "address"}}; +infer_expr(_Env, Body={oracle_pubkey, As, _}) -> + Q = fresh_uvar(As), + R = fresh_uvar(As), + {typed, As, Body, {app_t, As, {id, As, "oracle"}, [Q, R]}}; +infer_expr(_Env, Body={oracle_query_id, As, _}) -> + Q = fresh_uvar(As), + R = fresh_uvar(As), + {typed, As, Body, {app_t, As, {id, As, "oracle_query"}, [Q, R]}}; +infer_expr(_Env, Body={contract_pubkey, As, _}) -> + Con = fresh_uvar(As), + constrain([#is_contract_constraint{ contract_t = Con, + context = Body }]), + {typed, As, Body, Con}; infer_expr(_Env, Body={id, As, "_"}) -> {typed, As, Body, fresh_uvar(As)}; -infer_expr(Env, Id = {id, As, _}) -> +infer_expr(Env, Id = {Tag, As, _}) when Tag == id; Tag == qid -> {QName, Type} = lookup_name(Env, As, Id), {typed, As, QName, Type}; -infer_expr(Env, Id = {qid, As, _}) -> - {QName, Type} = lookup_name(Env, As, Id), - {typed, As, QName, Type}; -infer_expr(Env, Id = {con, As, _}) -> +infer_expr(Env, Id = {Tag, As, _}) when Tag == con; Tag == qcon -> {QName, Type} = lookup_name(Env, As, Id, [freshen]), {typed, As, QName, Type}; infer_expr(Env, {unit, As}) -> @@ -1348,6 +1369,16 @@ check_record_create_constraints(Env, [C | Cs]) -> end, check_record_create_constraints(Env, Cs). +check_is_contract_constraints(_Env, []) -> ok; +check_is_contract_constraints(Env, [C | Cs]) -> + #is_contract_constraint{ contract_t = Type, context = Lit } = C, + Type1 = unfold_types_in_type(Env, instantiate(Type)), + case lookup_type(Env, record_type_name(Type1)) of + {_, {_Ann, {[], {contract_t, _}}}} -> ok; + _ -> type_error({not_a_contract_type, Type1, Lit}) + end, + check_is_contract_constraints(Env, Cs). + -spec solve_field_constraints(env(), [field_constraint()]) -> ok. solve_field_constraints(Env, Constraints) -> %% First look for record fields that appear in only one type definition @@ -1446,9 +1477,12 @@ solve_known_record_types(Env, Constraints) -> DerefConstraints--SolvedConstraints. destroy_and_report_unsolved_field_constraints(Env) -> - {FieldCs, CreateCs} = + {FieldCs, OtherCs} = lists:partition(fun(#field_constraint{}) -> true; (_) -> false end, get_field_constraints()), + {CreateCs, ContractCs} = + lists:partition(fun(#record_create_constraint{}) -> true; (_) -> false end, + OtherCs), Unknown = solve_known_record_types(Env, FieldCs), if Unknown == [] -> ok; true -> @@ -1458,6 +1492,7 @@ destroy_and_report_unsolved_field_constraints(Env) -> end end, check_record_create_constraints(Env, CreateCs), + check_is_contract_constraints(Env, ContractCs), destroy_field_constraints(), ok. @@ -1583,6 +1618,8 @@ unfold_types_in_type(Env, {field_t, Attr, Name, Type}, Options) -> {field_t, Attr, Name, unfold_types_in_type(Env, Type, Options)}; unfold_types_in_type(Env, {constr_t, Ann, Con, Types}, Options) -> {constr_t, Ann, Con, unfold_types_in_type(Env, Types, Options)}; +unfold_types_in_type(Env, {named_arg_t, Ann, Con, Types, Default}, Options) -> + {named_arg_t, Ann, Con, unfold_types_in_type(Env, Types, Options), Default}; unfold_types_in_type(Env, T, Options) when is_tuple(T) -> list_to_tuple(unfold_types_in_type(Env, tuple_to_list(T), Options)); unfold_types_in_type(Env, [H|T], Options) -> @@ -1638,6 +1675,8 @@ unify1(_Env, {qid, _, Name}, {qid, _, Name}, _When) -> true; 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) -> unify(Env, Named1, Named2, When) andalso unify(Env, Args1, Args2, When) andalso unify(Env, Result1, Result2, When); @@ -1655,26 +1694,8 @@ unify1(Env, {app_t, _, T, []}, B, When) -> unify1(Env, A, {app_t, _, T, []}, When) -> unify(Env, A, T, When); unify1(_Env, A, B, When) -> - Ok = - case get_option(permissive_address_literals, false) of - true -> - Kind = fun({qcon, _, _}) -> con; - ({con, _, _}) -> con; - ({id, _, "address"}) -> addr; - ({id, _, "hash"}) -> hash; - ({app_t, _, {id, _, "oracle"}, _}) -> oracle; - ({app_t, _, {id, _, "oracle_query"}, _}) -> query; - (_) -> other end, - %% If permissive_address_literals we allow unifying adresses - %% with contract types or oracles/oracle queries - case lists:usort([Kind(A), Kind(B)]) of - [addr, K] -> K /= other; - _ -> false - end; - false -> false - end, - [ cannot_unify(A, B, When) || not Ok ], - Ok. + cannot_unify(A, B, When), + false. dereference(T = {uvar, _, R}) -> case ets_lookup(type_vars, R) of @@ -1703,6 +1724,7 @@ occurs_check1(_, {con, _, _}) -> false; occurs_check1(_, {qid, _, _}) -> false; occurs_check1(_, {qcon, _, _}) -> false; occurs_check1(_, {tvar, _, _}) -> false; +occurs_check1(_, {bytes_t, _, _}) -> false; occurs_check1(R, {fun_t, _, Named, Args, Res}) -> occurs_check(R, [Res, Named | Args]); occurs_check1(R, {app_t, _, T, Ts}) -> @@ -1819,6 +1841,11 @@ pp_error({undefined_field, Id}) -> io_lib:format("Unbound field ~s at ~s\n", [pp(Id), pp_loc(Id)]); pp_error({not_a_record_type, Type, Why}) -> io_lib:format("~s\n~s\n", [pp_type("Not a record type: ", Type), pp_why_record(Why)]); +pp_error({not_a_contract_type, Type, Lit}) -> + io_lib:format("The type ~s is not a contract type\n" + "when checking that the contract literal at ~s\n~s\n" + "has the type\n~s\n", + [pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]); pp_error({non_linear_pattern, Pattern, Nonlinear}) -> Plural = [ $s || length(Nonlinear) > 1 ], io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n", @@ -2043,6 +2070,8 @@ pp({qid, _, Name}) -> string:join(Name, "."); pp({con, _, Name}) -> Name; +pp({qcon, _, Name}) -> + string:join(Name, "."); pp({uvar, _, Ref}) -> %% Show some unique representation ["?u" | integer_to_list(erlang:phash2(Ref, 16384)) ]; @@ -2050,6 +2079,8 @@ pp({tvar, _, Name}) -> Name; pp({tuple_t, _, Cpts}) -> ["(", pp(Cpts), ")"]; +pp({bytes_t, _, Len}) -> + ["bytes(", integer_to_list(Len), ")"]; pp({app_t, _, T, []}) -> pp(T); pp({app_t, _, Type, Args}) -> diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index a152eb8..138f972 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -346,6 +346,11 @@ ast_body(?qid_app(["Crypto", "ecverify"], [Msg, PK, Sig], _, _), Icode) -> [ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)], [word, word, sign_t()], word); +ast_body(?qid_app(["Crypto", "ecverify_secp256k1"], [Msg, PK, Sig], _, _), Icode) -> + prim_call(?PRIM_CALL_CRYPTO_ECVERIFY_SECP256K1, #integer{value = 0}, + [ast_body(Msg, Icode), ast_body(PK, Icode), ast_body(Sig, Icode)], + [bytes_t(32), bytes_t(64), bytes_t(64)], word); + ast_body(?qid_app(["Crypto", "sha3"], [Term], [Type], _), Icode) -> generic_hash_primop(?PRIM_CALL_CRYPTO_SHA3, Term, Type, Icode); ast_body(?qid_app(["Crypto", "sha256"], [Term], [Type], _), Icode) -> @@ -416,14 +421,17 @@ ast_body({bool, _, Bool}, _Icode) -> %BOOL as ints #integer{value = Value}; ast_body({int, _, Value}, _Icode) -> #integer{value = Value}; -ast_body({hash, _, Hash}, _Icode) -> - case Hash of - <> -> %% address - #integer{value = Value}; - <> -> %% signature - #tuple{cpts = [#integer{value = Hi}, - #integer{value = Lo}]} +ast_body({bytes, _, Bin}, _Icode) -> + case aeb_memory:binary_to_words(Bin) of + [Word] -> #integer{value = Word}; + Words -> #tuple{cpts = [#integer{value = W} || W <- Words]} end; +ast_body({Key, _, Bin}, _Icode) when Key == account_pubkey; + Key == contract_pubkey; + Key == oracle_pubkey; + Key == oracle_query_id -> + <> = Bin, + #integer{value = Value}; ast_body({string,_,Bin}, _Icode) -> Cpts = [size(Bin) | aeb_memory:binary_to_words(Bin)], #tuple{cpts = [#integer{value=X} || X <- Cpts]}; @@ -688,6 +696,8 @@ ast_typerep({qid, _, Name}, Icode) -> lookup_type_id(Name, [], Icode); ast_typerep({con, _, _}, _) -> word; %% Contract type +ast_typerep({bytes_t, _, Len}, _) -> + {bytes, Len}; ast_typerep({app_t, _, {id, _, Name}, Args}, Icode) -> ArgReps = [ ast_typerep(Arg, Icode) || Arg <- Args ], lookup_type_id(Name, ArgReps, Icode); @@ -715,8 +725,8 @@ ast_typerep({variant_t, Cons}, Icode) -> ttl_t(Icode) -> ast_typerep({qid, [], ["Chain", "ttl"]}, Icode). -sign_t() -> - {tuple, [word, word]}. +sign_t() -> bytes_t(64). +bytes_t(Len) -> {bytes, Len}. get_signature_arg(Args0) -> NamedArgs = [Arg || Arg = {named_arg, _, _, _} <- Args0], @@ -750,6 +760,8 @@ type_value({list, A}) -> type_value({tuple, As}) -> #tuple{ cpts = [#integer{ value = ?TYPEREP_TUPLE_TAG }, #list{ elems = [ type_value(A) || A <- As ] }] }; +type_value({bytes, Len}) -> + #tuple{ cpts = [#integer{ value = ?TYPEREP_BYTES_TAG }, #integer{ value = Len }] }; type_value({variant, Cs}) -> #tuple{ cpts = [#integer{ value = ?TYPEREP_VARIANT_TAG }, #list{ elems = [ #list{ elems = [ type_value(A) || A <- As ] } || As <- Cs ] }] }; diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index f8b2d1d..0f38ecf 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -104,13 +104,12 @@ from_string(ContractString, Options) -> %% General programming errors in the compiler just signal error. end. --spec string_to_icode(string(), [option() | permissive_address_literals]) -> map(). -string_to_icode(ContractString, Options0) -> - {InferOptions, Options} = lists:partition(fun(Opt) -> Opt == permissive_address_literals end, Options0), +-spec string_to_icode(string(), [option()]) -> map(). +string_to_icode(ContractString, Options) -> Ast = parse(ContractString, Options), pp_sophia_code(Ast, Options), pp_ast(Ast, Options), - {TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]), + {TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]), pp_typed_ast(TypedAst, Options), Icode = ast_to_icode(TypedAst, Options), pp_icode(Icode, Options), @@ -151,11 +150,11 @@ check_call(Source, FunName, Args, Options) -> check_call(ContractString0, FunName, Args, Options, PatchFun) -> try - %% First check the contract without the __call function and no permissive literals + %% First check the contract without the __call function #{} = string_to_icode(ContractString0, Options), ContractString = insert_call_function(ContractString0, FunName, Args, Options), #{typed_ast := TypedAst, - icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | Options]), + icode := Icode} = string_to_icode(ContractString, Options), {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], RetVMType = case RetType of @@ -257,19 +256,21 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) -> fun (E) -> io_lib:format("~p", [E]) end)} end. -address_literal(N) -> {hash, [], <>}. % TODO +address_literal(Type, N) -> {Type, [], <>}. %% TODO: somewhere else -spec translate_vm_value(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr(). -translate_vm_value(word, {id, _, "address"}, N) -> address_literal(N); -translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(N); -translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(N); -translate_vm_value(word, {id, _, "hash"}, N) -> {hash, [], <>}; -translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N}; -translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N}); -translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0}; -translate_vm_value({tuple, [word, word]}, {id, _, "signature"}, {tuple, [Hi, Lo]}) -> - {hash, [], <>}; +translate_vm_value(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N); +translate_vm_value(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N); +translate_vm_value(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N); +translate_vm_value(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N); +translate_vm_value(word, {id, _, "int"}, N) -> {int, [], N}; +translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N}); +translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0}; +translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) when Len =< 32 -> + {bytes, [], <>}; +translate_vm_value({bytes, Len}, {bytes_t, _, Len}, Val) -> + {bytes, [], binary:part(<< <> || W <- tuple_to_list(Val) >>, 0, Len)}; translate_vm_value(string, {id, _, "string"}, S) -> {string, [], S}; translate_vm_value({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) -> {list, [], [translate_vm_value(VmType, Type, X) || X <- List]}; @@ -402,6 +403,10 @@ icode_to_term(word, {integer, N}) -> N; icode_to_term(string, {tuple, [{integer, Len} | Words]}) -> <> = << <> || {integer, W} <- Words >>, Str; +icode_to_term({bytes, Len}, {integer, Value}) when Len =< 32 -> + Value; +icode_to_term({bytes, Len}, {tuple, Words}) when Len > 32-> + list_to_tuple([W || {integer, W} <- Words]); icode_to_term({list, T}, {list, Vs}) -> [ icode_to_term(T, V) || V <- Vs ]; icode_to_term({tuple, Ts}, {tuple, Vs}) -> diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index 78b329f..c174a6b 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -136,11 +136,15 @@ type200() -> type300() -> type400(). type400() -> - ?RULE(typeAtom(), optional(type_args()), + choice( + [?RULE(typeAtom(), optional(type_args()), case _2 of none -> _1; {ok, Args} -> {app_t, get_ann(_1), _1, Args} - end). + end), + ?RULE(id("bytes"), parens(token(int)), + {bytes_t, get_ann(_1), element(3, _2)}) + ]). typeAtom() -> ?LAZY_P(choice( @@ -203,8 +207,8 @@ exprAtom() -> ?LAZY_P(begin Expr = ?LAZY_P(expr()), choice( - [ id(), con(), token(qid), token(qcon) - , token(hash), token(string), token(char) + [ id_or_addr(), con(), token(qid), token(qcon) + , token(bytes), token(string), token(char) , token(int) , ?RULE(token(hex), set_ann(format, hex, setelement(1, _1, int))) , {bool, keyword(true), true} @@ -326,6 +330,26 @@ token(Tag) -> {Tok, {Line, Col}, Val} -> {Tok, pos_ann(Line, Col), Val} end). +id(Id) -> + ?LET_P({id, A, X} = Y, id(), + if X == Id -> Y; + true -> fail({A, "expected 'bytes'"}) + end). + +id_or_addr() -> + ?RULE(id(), parse_addr_literal(_1)). + +parse_addr_literal(Id = {id, Ann, Name}) -> + case lists:member(lists:sublist(Name, 3), ["ak_", "ok_", "oq_", "ct_"]) of + false -> Id; + true -> + try aeser_api_encoder:decode(list_to_binary(Name)) of + {Type, Bin} -> {Type, Ann, Bin} + catch _:_ -> + Id + end + end. + %% -- Helpers ---------------------------------------------------------------- keyword(K) -> ann(tok(K)). @@ -457,7 +481,7 @@ parse_pattern(E = {id, _, _}) -> E; parse_pattern(E = {unit, _}) -> E; parse_pattern(E = {int, _, _}) -> E; parse_pattern(E = {bool, _, _}) -> E; -parse_pattern(E = {hash, _, _}) -> E; +parse_pattern(E = {bytes, _, _}) -> E; parse_pattern(E = {string, _, _}) -> E; parse_pattern(E = {char, _, _}) -> E; parse_pattern(E) -> bad_expr_err("Not a valid pattern", E). diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 240c499..8a09fbc 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -236,6 +236,8 @@ type({app_t, _, Type, Args}) -> beside(type(Type), tuple_type(Args)); type({tuple_t, _, Args}) -> tuple_type(Args); +type({bytes_t, _, Len}) -> + text(lists:concat(["bytes(", Len, ")"])); type({named_arg_t, _, Name, Type, _Default}) -> %% Drop the default value %% follow(hsep(typed(name(Name), Type), text("=")), expr(Default)); @@ -319,8 +321,17 @@ expr_p(_, E = {int, _, N}) -> end, text(S); expr_p(_, {bool, _, B}) -> text(atom_to_list(B)); -expr_p(_, {hash, _, <>}) -> text("#" ++ integer_to_list(N, 16)); +expr_p(_, {bytes, _, Bin}) -> + Digits = byte_size(Bin), + <> = Bin, + text(lists:flatten(io_lib:format("#~*.16.0b", [Digits*2, N]))); expr_p(_, {hash, _, <>}) -> text("#" ++ integer_to_list(N, 16)); +expr_p(_, {Type, _, Bin}) + when Type == account_pubkey; + Type == contract_pubkey; + Type == oracle_pubkey; + Type == oracle_query_id -> + text(binary_to_list(aeser_api_encoder:encode(Type, Bin))); expr_p(_, {unit, _}) -> text("()"); expr_p(_, {string, _, S}) -> term(binary_to_list(S)); expr_p(_, {char, _, C}) -> diff --git a/src/aeso_scan.erl b/src/aeso_scan.erl index 17b26bc..e377438 100644 --- a/src/aeso_scan.erl +++ b/src/aeso_scan.erl @@ -20,7 +20,7 @@ lexer() -> CON = [UPPER, "[a-zA-Z0-9_]*"], INT = [DIGIT, "+"], HEX = ["0x", HEXDIGIT, "+"], - HASH = ["#", HEXDIGIT, "+"], + BYTES = ["#", HEXDIGIT, "+"], WS = "[\\000-\\ ]+", ID = [LOWER, "[a-zA-Z0-9_']*"], TVAR = ["'", ID], @@ -54,7 +54,7 @@ lexer() -> , {STRING, token(string, fun parse_string/1)} , {HEX, token(hex, fun parse_hex/1)} , {INT, token(int, fun list_to_integer/1)} - , {HASH, token(hash, fun parse_hash/1)} + , {BYTES, token(bytes, fun parse_bytes/1)} %% Identifiers (qualified first!) , {QID, token(qid, fun(S) -> string:tokens(S, ".") end)} @@ -117,10 +117,8 @@ unescape([C | Chars], Acc) -> parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16). -parse_hash("#" ++ Chars) -> - N = list_to_integer(Chars, 16), - case length(Chars) > 64 of %% 64 hex digits = 32 bytes - true -> <>; %% signature - false -> <> %% address - end. +parse_bytes("#" ++ Chars) -> + N = list_to_integer(Chars, 16), + Digits = (length(Chars) + 1) div 2, + <>. diff --git a/src/aeso_syntax.erl b/src/aeso_syntax.erl index 3e610bc..25f0963 100644 --- a/src/aeso_syntax.erl +++ b/src/aeso_syntax.erl @@ -60,6 +60,7 @@ -type type() :: {fun_t, ann(), [named_arg_t()], [type()], type()} | {app_t, ann(), type(), [type()]} | {tuple_t, ann(), [type()]} + | {bytes_t, ann(), integer()} | id() | qid() | con() | qcon() %% contracts | tvar(). @@ -70,6 +71,10 @@ :: {int, ann(), integer()} | {bool, ann(), true | false} | {hash, ann(), binary()} + | {account_pubkey, binary()} + | {contract_pubkey, binary()} + | {oracle_pubkey, binary()} + | {oracle_query_id, binary()} | {unit, ann()} | {string, ann(), binary()} | {char, ann(), integer()}. diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index 4f2f256..5357c1d 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -4,6 +4,9 @@ -compile(export_all). -define(SANDBOX(Code), sandbox(fun() -> Code end)). +-define(DUMMY_HASH_WORD, 16#123). +-define(DUMMY_HASH, <<0:30/unit:8, 127, 119>>). %% 16#123 +-define(DUMMY_HASH_LIT, "#0000000000000000000000000000000000000000000000000000000000000123"). sandbox(Code) -> Parent = self(), @@ -97,7 +100,15 @@ calldata_test() -> Map = #{ <<"a">> => 4 }, [{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] = encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]), - [16#123, 16#456] = encode_decode_calldata("foo", ["hash", "address"], ["#123", "#456"]), + [?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"], + [?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]), + [?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] = + encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]), + + [119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]), + + [16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]), + ok. calldata_init_test() -> @@ -126,7 +137,9 @@ parameterized_contract(FunName, Types) -> parameterized_contract(ExtraCode, FunName, Types) -> lists:flatten( - ["contract Dummy =\n", + ["contract Remote =\n" + " function bla : () => ()\n\n" + "contract Dummy =\n", ExtraCode, "\n", " type an_alias('a) = (string, 'a)\n" " record r = {x : an_alias(int), y : variant}\n" @@ -139,7 +152,9 @@ oracle_test() -> " function question(o, q : oracle_query(list(string), option(int))) =\n" " Oracle.get_question(o, q)\n", {ok, _, {[word, word], {list, string}}, [16#123, 16#456]} = - aeso_compiler:check_call(Contract, "question", ["#123", "#456"], []), + aeso_compiler:check_call(Contract, "question", ["ok_111111111111111111111111111111ZrdqRz9", + "oq_1111111111111111111111111111113AFEFpt5"], []), + ok. permissive_literals_fail_test() -> diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index c98db5a..320a5fb 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -8,6 +8,8 @@ -module(aeso_compiler_tests). +-compile([export_all, nowarn_export_all]). + -include_lib("eunit/include/eunit.hrl"). %% Very simply test compile the given contracts. Only basic checks @@ -16,10 +18,14 @@ simple_compile_test_() -> [ {"Testing the " ++ ContractName ++ " contract", fun() -> - #{byte_code := ByteCode, - contract_source := _, - type_info := _} = compile(ContractName), - ?assertMatch(Code when is_binary(Code), ByteCode) + case compile(ContractName) of + #{byte_code := ByteCode, + contract_source := _, + type_info := _} -> ?assertMatch(Code when is_binary(Code), ByteCode); + ErrBin -> + io:format("\n~s", [ErrBin]), + error(ErrBin) + end end} || ContractName <- compilable_contracts() ] ++ [ {"Testing error messages of " ++ ContractName, fun() -> @@ -98,7 +104,9 @@ compilable_contracts() -> "state_handling", "events", "include", - "basic_auth" + "basic_auth", + "bitcoin_auth", + "address_literals" ]. %% Contracts that should produce type errors @@ -218,4 +226,80 @@ failing_contracts() -> , {"bad_include_and_ns", [<<"Include of 'included.aes' at line 2, column 11\nnot allowed, include only allowed at top level.">>, <<"Nested namespace not allowed\nNamespace 'Foo' at line 3, column 13 not defined at top level.">>]} + , {"bad_address_literals", + [<<"The type bytes(32) is not a contract type\n" + "when checking that the contract literal at line 32, column 5\n" + " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" + "has the type\n" + " bytes(32)">>, + <<"The type oracle(int, bool) is not a contract type\n" + "when checking that the contract literal at line 30, column 5\n" + " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" + "has the type\n" + " oracle(int, bool)">>, + <<"The type address is not a contract type\n" + "when checking that the contract literal at line 28, column 5\n" + " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n" + "has the type\n" + " address">>, + <<"Cannot unify oracle_query('1, '2)\n" + " and Remote\n" + "when checking the type of the expression at line 25, column 5\n" + " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" + " oracle_query('1, '2)\n" + "against the expected type\n" + " Remote">>, + <<"Cannot unify oracle_query('3, '4)\n" + " and bytes(32)\n" + "when checking the type of the expression at line 23, column 5\n" + " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" + " oracle_query('3, '4)\n" + "against the expected type\n" + " bytes(32)">>, + <<"Cannot unify oracle_query('5, '6)\n" + " and oracle(int, bool)\n" + "when checking the type of the expression at line 21, column 5\n" + " oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY :\n" + " oracle_query('5, '6)\n" + "against the expected type\n" + " oracle(int, bool)">>, + <<"Cannot unify oracle('7, '8)\n" + " and Remote\n" + "when checking the type of the expression at line 18, column 5\n" + " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" + " oracle('7, '8)\n" + "against the expected type\n" + " Remote">>, + <<"Cannot unify oracle('9, '10)\n" + " and bytes(32)\n" + "when checking the type of the expression at line 16, column 5\n" + " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" + " oracle('9, '10)\n" + "against the expected type\n" + " bytes(32)">>, + <<"Cannot unify oracle('11, '12)\n" + " and oracle_query(int, bool)\n" + "when checking the type of the expression at line 14, column 5\n" + " ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 :\n" + " oracle('11, '12)\n" + "against the expected type\n" + " oracle_query(int, bool)">>, + <<"Cannot unify address\n" + " and oracle(int, bool)\n" + "when checking the type of the expression at line 11, column 5\n" + " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" + "against the expected type\n" + " oracle(int, bool)">>, + <<"Cannot unify address\n" + " and Remote\n" + "when checking the type of the expression at line 9, column 5\n" + " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" + "against the expected type\n" + " Remote">>, + <<"Cannot unify address\n" + " and bytes(32)\n" + "when checking the type of the expression at line 7, column 5\n" + " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" + "against the expected type\n" + " bytes(32)">>]} ]. diff --git a/test/aeso_scan_tests.erl b/test/aeso_scan_tests.erl index 6e61074..32b8456 100644 --- a/test/aeso_scan_tests.erl +++ b/test/aeso_scan_tests.erl @@ -48,7 +48,7 @@ all_tokens() -> %% Literals [ Lit(true), Lit(false) , Tok(id, "foo"), Tok(id, "_"), Tok(con, "Foo") - , Tok(hash, Hash) + , Tok(bytes, Hash) , Tok(int, 1234567890), Tok(hex, 9876543210) , Tok(string, <<"bla\"\\\b\e\f\n\r\t\vbla">>) ]. @@ -78,7 +78,7 @@ show_token({param, _, P}) -> "@" ++ P; show_token({string, _, S}) -> fmt(binary_to_list(S)); show_token({int, _, N}) -> fmt(N); show_token({hex, _, N}) -> fmt("0x~.16b", N); -show_token({hash, _, <>}) -> fmt("#~.16b", N); +show_token({bytes, _, <>}) -> fmt("#~64.16.0b", N); show_token({comment, _, S}) -> S; show_token({_, _, _}) -> "TODO". diff --git a/test/contracts/address_literals.aes b/test/contracts/address_literals.aes new file mode 100644 index 0000000..54bf8ad --- /dev/null +++ b/test/contracts/address_literals.aes @@ -0,0 +1,14 @@ + +contract Remote = + function foo : () => () + +contract AddressLiterals = + function addr() : address = + ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt + function oracle() : oracle(int, bool) = + ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 + function query() : oracle_query(int, bool) = + oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY + function contr() : Remote = + ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ + diff --git a/test/contracts/bad_address_literals.aes b/test/contracts/bad_address_literals.aes new file mode 100644 index 0000000..1e2a376 --- /dev/null +++ b/test/contracts/bad_address_literals.aes @@ -0,0 +1,33 @@ + +contract Remote = + function foo : () => () + +contract AddressLiterals = + function addr1() : bytes(32) = + ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt + function addr2() : Remote = + ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt + function addr3() : oracle(int, bool) = + ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt + + function oracle1() : oracle_query(int, bool) = + ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 + function oracle2() : bytes(32) = + ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 + function oracle3() : Remote = + ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5 + + function query1() : oracle(int, bool) = + oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY + function query2() : bytes(32) = + oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY + function query3() : Remote = + oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY + + function contr1() : address = + ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ + function contr2() : oracle(int, bool) = + ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ + function contr3() : bytes(32) = + ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ + diff --git a/test/contracts/bitcoin_auth.aes b/test/contracts/bitcoin_auth.aes new file mode 100644 index 0000000..79762ce --- /dev/null +++ b/test/contracts/bitcoin_auth.aes @@ -0,0 +1,18 @@ +contract BitcoinAuth = + record state = { nonce : int, owner : bytes(64) } + + function init(owner' : bytes(64)) = { nonce = 1, owner = owner' } + + function authorize(n : int, s : signature) : bool = + require(n >= state.nonce, "Nonce too low") + require(n =< state.nonce, "Nonce too high") + put(state{ nonce = n + 1 }) + switch(Auth.tx_hash) + None => abort("Not in Auth context") + Some(tx_hash) => Crypto.ecverify_secp256k1(to_sign(tx_hash, n), state.owner, s) + + function to_sign(h : hash, n : int) : hash = + Crypto.blake2b((h, n)) + + private function require(b : bool, err : string) = + if(!b) abort(err) diff --git a/test/contracts/fundme.aes b/test/contracts/fundme.aes index 0a73113..eed32c9 100644 --- a/test/contracts/fundme.aes +++ b/test/contracts/fundme.aes @@ -53,7 +53,7 @@ contract FundMe = require(state.total >= state.goal, "Project was not funded") spend({recipient = state.beneficiary, amount = Contract.balance }) - put(state{ beneficiary = #0 }) + put(state{ beneficiary = ak_11111111111111111111111111111111273Yts }) private stateful function withdraw_contributor() = if(state.total >= state.goal)