From 9984679a24ac87cdfd288636c48f4f38b9bd70c1 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 1 Mar 2019 09:02:55 +0100 Subject: [PATCH 1/7] better handling of permissive_literals ...that doesn't make the compiler crash --- src/aeso_ast_infer_types.erl | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index f3c44de..22af695 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -494,19 +494,7 @@ infer(Contracts) -> -type option() :: permissive_address_literals | return_env. -spec init_env(list(option())) -> env(). -init_env(Options) -> - case proplists:get_value(permissive_address_literals, Options, false) of - false -> global_env(); - true -> - %% Treat oracle and query ids as address to allow address literals for these - Ann = [{origin, system}], - Tag = fun(Tag, Val) -> {Tag, Ann, Val} end, - lists:foldl(fun({Name, Arity}, E) -> - bind_type(Name, [{origin, system}], - {lists:duplicate(Arity, Tag(tvar, "_")), - {alias_t, Tag(id, "address")}}, E) - end, global_env(), [{"oracle", 2}, {"oracle_query", 2}]) - end. +init_env(_Options) -> global_env(). -spec infer(aeso_syntax:ast(), list(option())) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}. infer(Contracts, Options) -> @@ -1667,9 +1655,15 @@ unify1(_Env, A, B, When) -> Kind = fun({qcon, _, _}) -> con; ({con, _, _}) -> con; ({id, _, "address"}) -> addr; + ({app_t, _, {id, _, "oracle"}, _}) -> oracle; + ({app_t, _, {id, _, "oracle_query"}, _}) -> query; (_) -> other end, - %% If permissive_address_literals we allow unifying contract types and address - [addr, con] == lists:usort([Kind(A), Kind(B)]); + %% 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 ], From 9e908369ec876c32862faa206e984051e89e4713 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 1 Mar 2019 09:21:56 +0100 Subject: [PATCH 2/7] check create_calldata contract without the __call function first --- src/aeso_compiler.erl | 54 +++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index d25ed8d..59e5638 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -68,20 +68,13 @@ from_string(ContractBin, Options) when is_binary(ContractBin) -> from_string(binary_to_list(ContractBin), Options); from_string(ContractString, Options) -> try - Ast = parse(ContractString, Options), - ok = pp_sophia_code(Ast, Options), - ok = pp_ast(Ast, Options), - TypedAst = aeso_ast_infer_types:infer(Ast, Options), - %% pp_types is handled inside aeso_ast_infer_types. - ok = pp_typed_ast(TypedAst, Options), - ICode = to_icode(TypedAst, Options), - TypeInfo = extract_type_info(ICode), - ok = pp_icode(ICode, Options), - Assembler = assemble(ICode, Options), - ok = pp_assembler(Assembler, Options), + #{icode := Icode} = string_to_icode(ContractString, Options), + TypeInfo = extract_type_info(Icode), + Assembler = assemble(Icode, Options), + pp_assembler(Assembler, Options), ByteCodeList = to_bytecode(Assembler, Options), ByteCode = << << B:8 >> || B <- ByteCodeList >>, - ok = pp_bytecode(ByteCode, Options), + pp_bytecode(ByteCode, Options), {ok, #{byte_code => ByteCode, compiler_version => version(), contract_source => ContractString, @@ -99,6 +92,18 @@ from_string(ContractString, Options) -> %% General programming errors in the compiler just signal error. end. +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 | Options]), + pp_typed_ast(TypedAst, Options), + Icode = ast_to_icode(TypedAst, Options), + pp_icode(Icode, Options), + #{ typed_ast => TypedAst, + type_env => TypeEnv, + icode => Icode }. + join_errors(Prefix, Errors, Pfun) -> Ess = [ Pfun(E) || E <- Errors ], list_to_binary(string:join([Prefix|Ess], "\n")). @@ -132,20 +137,17 @@ 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 + #{} = string_to_icode(ContractString0, Options), ContractString = insert_call_function(ContractString0, FunName, Args, Options), - Ast = parse(ContractString, Options), - ok = pp_sophia_code(Ast, Options), - ok = pp_ast(Ast, Options), - TypedAst = aeso_ast_infer_types:infer(Ast, [permissive_address_literals]), + #{typed_ast := TypedAst, + icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | Options]), {ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), - ok = pp_typed_ast(TypedAst, Options), - Icode = to_icode(TypedAst, Options), ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ], RetVMType = case RetType of {id, _, "_"} -> any; _ -> aeso_ast_to_icode:ast_typerep(RetType, Icode) end, - ok = pp_icode(Icode, Options), #{ functions := Funs } = Icode, ArgIcode = get_arg_icode(Funs), ArgTerms = [ icode_to_term(T, Arg) || @@ -208,16 +210,12 @@ to_sophia_value(_, _, revert, Data, _Options) -> end; to_sophia_value(ContractString, FunName, ok, Data, Options) -> try - Ast = parse(ContractString, Options), - ok = pp_sophia_code(Ast, Options), - ok = pp_ast(Ast, Options), - {Env, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]), - ok = pp_typed_ast(TypedAst, Options), + #{ typed_ast := TypedAst, + type_env := TypeEnv, + icode := Icode } = string_to_icode(ContractString, Options), {ok, Type0} = get_decode_type(FunName, TypedAst), - Type = aeso_ast_infer_types:unfold_types_in_type(Env, Type0, [unfold_record_types, unfold_variant_types]), - Icode = to_icode(TypedAst, Options), + Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]), VmType = aeso_ast_to_icode:ast_typerep(Type, Icode), - ok = pp_icode(Icode, Options), case aeso_heap:from_binary(VmType, Data) of {ok, VmValue} -> try @@ -374,7 +372,7 @@ icode_to_term(T, V) -> icodes_to_terms(Ts, Vs) -> [ icode_to_term(T, V) || {T, V} <- lists:zip(Ts, Vs) ]. -to_icode(TypedAst, Options) -> +ast_to_icode(TypedAst, Options) -> aeso_ast_to_icode:convert_typed(TypedAst, Options). assemble(Icode, Options) -> From 56f70fea6ca77e127d3ce70dd7c330cca6c7b41e Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 1 Mar 2019 09:36:43 +0100 Subject: [PATCH 3/7] test oracle calldata fixes --- src/aeso_compiler.erl | 2 +- test/aeso_abi_tests.erl | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index 59e5638..d0fe602 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -117,7 +117,7 @@ join_errors(Prefix, Errors, Pfun) -> %% terms for the arguments. %% NOTE: Special treatment for "init" since it might be implicit and has %% a special return type (typerep, T) --spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()} +-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type}, [term()]} | {error, term()} when Type :: term(). check_call(Source, "init" = FunName, Args, Options) -> PatchFun = fun(T) -> {tuple, [typerep, T]} end, diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index dee8317..706d7af 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -107,12 +107,30 @@ calldata_init_test() -> parameterized_contract(FunName, Types) -> lists:flatten( - ["contract Dummy =\n", + ["contract Dummy =\n" " type an_alias('a) = (string, 'a)\n" " record r = {x : an_alias(int), y : variant}\n" " datatype variant = Red | Blue(map(string, int))\n" " function ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]). +oracle_test() -> + Contract = + "contract OracleTest =\n" + " 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"], []), + ok. + +permissive_literals_fail_test() -> + Contract = + "contract OracleTest =\n" + " function haxx(o : oracle(list(string), option(int))) =\n" + " Chain.spend(o, 1000000)\n", + {error, <<"Type errors\nCannot unify address\n and oracle", _/binary>>} = + aeso_compiler:check_call(Contract, "haxx", ["#123"], []), + ok. + encode_decode_calldata(FunName, Types, Args) -> encode_decode_calldata(FunName, Types, Args, word). From c2a5ed28cf24a37af8ce0563aebde20209a9128f Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 1 Mar 2019 09:54:43 +0100 Subject: [PATCH 4/7] please dialyzer --- src/aeso_compiler.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index d0fe602..c632f3e 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -92,11 +92,13 @@ from_string(ContractString, Options) -> %% General programming errors in the compiler just signal error. end. -string_to_icode(ContractString, Options) -> +-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), Ast = parse(ContractString, Options), pp_sophia_code(Ast, Options), pp_ast(Ast, Options), - {TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | Options]), + {TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env | InferOptions]), pp_typed_ast(TypedAst, Options), Icode = ast_to_icode(TypedAst, Options), pp_icode(Icode, Options), From 20f2a05638d89f94f25d5ded5cf01c5f139843b7 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 1 Mar 2019 11:08:14 +0100 Subject: [PATCH 5/7] fix problem with indent detection when inserting the __call function --- src/aeso_parser.erl | 11 ++++++++--- test/aeso_abi_tests.erl | 23 +++++++++++++++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/aeso_parser.erl b/src/aeso_parser.erl index b934e08..78b329f 100644 --- a/src/aeso_parser.erl +++ b/src/aeso_parser.erl @@ -68,9 +68,14 @@ decl() -> modifiers() -> many(choice([token(stateful), token(public), token(private), token(internal)])). -add_modifiers(Mods, Node) -> - lists:foldl(fun({Mod, _}, X) -> set_ann(Mod, true, X) end, - Node, Mods). +add_modifiers([], Node) -> Node; +add_modifiers(Mods = [Tok | _], Node) -> + %% Set the position to the position of the first modifier. This is + %% important for code transformation tools (like what we do in + %% create_calldata) to be able to get the indentation of the declaration. + set_pos(get_pos(Tok), + lists:foldl(fun({Mod, _}, X) -> set_ann(Mod, true, X) end, + Node, Mods)). %% -- Type declarations ------------------------------------------------------ diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index 706d7af..3993e68 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -105,9 +105,28 @@ calldata_init_test() -> Code = parameterized_contract("foo", ["int"]), encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}). +calldata_indent_test() -> + Test = fun(Extra) -> + encode_decode_calldata_( + parameterized_contract(Extra, "foo", ["int"]), + "foo", ["42"], word) + end, + Test(" stateful function bla() = ()"), + Test(" type x = int"), + Test(" private function bla : int => int"), + Test(" public stateful function bla(x : int) =\n" + " x + 1"), + Test(" stateful private function bla(x : int) : int =\n" + " x + 1"), + ok. + parameterized_contract(FunName, Types) -> + parameterized_contract([], FunName, Types). + +parameterized_contract(ExtraCode, FunName, Types) -> lists:flatten( - ["contract Dummy =\n" + ["contract Dummy =\n", + ExtraCode, "\n", " type an_alias('a) = (string, 'a)\n" " record r = {x : an_alias(int), y : variant}\n" " datatype variant = Red | Blue(map(string, int))\n" @@ -127,7 +146,7 @@ permissive_literals_fail_test() -> "contract OracleTest =\n" " function haxx(o : oracle(list(string), option(int))) =\n" " Chain.spend(o, 1000000)\n", - {error, <<"Type errors\nCannot unify address\n and oracle", _/binary>>} = + {error, <<"Type errors\nCannot unify", _/binary>>} = aeso_compiler:check_call(Contract, "haxx", ["#123"], []), ok. From edc37bcf1b34992e244aaa950b20953ef5272113 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 1 Mar 2019 11:08:28 +0100 Subject: [PATCH 6/7] add case for signature literals to pretty printer --- src/aeso_pretty.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/aeso_pretty.erl b/src/aeso_pretty.erl index 0c047ec..240c499 100644 --- a/src/aeso_pretty.erl +++ b/src/aeso_pretty.erl @@ -320,6 +320,7 @@ expr_p(_, E = {int, _, N}) -> text(S); expr_p(_, {bool, _, B}) -> text(atom_to_list(B)); expr_p(_, {hash, _, <>}) -> text("#" ++ integer_to_list(N, 16)); +expr_p(_, {hash, _, <>}) -> text("#" ++ integer_to_list(N, 16)); expr_p(_, {unit, _}) -> text("()"); expr_p(_, {string, _, S}) -> term(binary_to_list(S)); expr_p(_, {char, _, C}) -> From 3efde2a2a194935fc1d832b358ecb2dbce8de2e7 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 1 Mar 2019 11:36:28 +0100 Subject: [PATCH 7/7] handle hash literals when `permissive_address_literals` --- src/aeso_ast_infer_types.erl | 1 + test/aeso_abi_tests.erl | 1 + 2 files changed, 2 insertions(+) diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 22af695..c247597 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1655,6 +1655,7 @@ unify1(_Env, A, B, When) -> 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, diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index 3993e68..fd0273f 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -97,6 +97,7 @@ 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"]), ok. calldata_init_test() ->