Merge pull request #34 from aeternity/fix_create_calldata_again_again

Fix calldata creation for contracts involving oracles
This commit is contained in:
Hans Svensson 2019-03-01 15:03:51 +01:00 committed by GitHub
commit 4d9d3077ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 47 deletions

View File

@ -494,19 +494,7 @@ infer(Contracts) ->
-type option() :: permissive_address_literals | return_env. -type option() :: permissive_address_literals | return_env.
-spec init_env(list(option())) -> env(). -spec init_env(list(option())) -> env().
init_env(Options) -> init_env(_Options) -> global_env().
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.
-spec infer(aeso_syntax:ast(), list(option())) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}. -spec infer(aeso_syntax:ast(), list(option())) -> aeso_syntax:ast() | {env(), aeso_syntax:ast()}.
infer(Contracts, Options) -> infer(Contracts, Options) ->
@ -1667,9 +1655,16 @@ unify1(_Env, A, B, When) ->
Kind = fun({qcon, _, _}) -> con; Kind = fun({qcon, _, _}) -> con;
({con, _, _}) -> con; ({con, _, _}) -> con;
({id, _, "address"}) -> addr; ({id, _, "address"}) -> addr;
({id, _, "hash"}) -> hash;
({app_t, _, {id, _, "oracle"}, _}) -> oracle;
({app_t, _, {id, _, "oracle_query"}, _}) -> query;
(_) -> other end, (_) -> other end,
%% If permissive_address_literals we allow unifying contract types and address %% If permissive_address_literals we allow unifying adresses
[addr, con] == lists:usort([Kind(A), Kind(B)]); %% with contract types or oracles/oracle queries
case lists:usort([Kind(A), Kind(B)]) of
[addr, K] -> K /= other;
_ -> false
end;
false -> false false -> false
end, end,
[ cannot_unify(A, B, When) || not Ok ], [ cannot_unify(A, B, When) || not Ok ],

View File

@ -68,20 +68,13 @@ from_string(ContractBin, Options) when is_binary(ContractBin) ->
from_string(binary_to_list(ContractBin), Options); from_string(binary_to_list(ContractBin), Options);
from_string(ContractString, Options) -> from_string(ContractString, Options) ->
try try
Ast = parse(ContractString, Options), #{icode := Icode} = string_to_icode(ContractString, Options),
ok = pp_sophia_code(Ast, Options), TypeInfo = extract_type_info(Icode),
ok = pp_ast(Ast, Options), Assembler = assemble(Icode, Options),
TypedAst = aeso_ast_infer_types:infer(Ast, Options), pp_assembler(Assembler, 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),
ByteCodeList = to_bytecode(Assembler, Options), ByteCodeList = to_bytecode(Assembler, Options),
ByteCode = << << B:8 >> || B <- ByteCodeList >>, ByteCode = << << B:8 >> || B <- ByteCodeList >>,
ok = pp_bytecode(ByteCode, Options), pp_bytecode(ByteCode, Options),
{ok, #{byte_code => ByteCode, {ok, #{byte_code => ByteCode,
compiler_version => version(), compiler_version => version(),
contract_source => ContractString, contract_source => ContractString,
@ -99,6 +92,20 @@ from_string(ContractString, Options) ->
%% General programming errors in the compiler just signal error. %% General programming errors in the compiler just signal error.
end. 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),
Ast = parse(ContractString, Options),
pp_sophia_code(Ast, Options),
pp_ast(Ast, 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),
#{ typed_ast => TypedAst,
type_env => TypeEnv,
icode => Icode }.
join_errors(Prefix, Errors, Pfun) -> join_errors(Prefix, Errors, Pfun) ->
Ess = [ Pfun(E) || E <- Errors ], Ess = [ Pfun(E) || E <- Errors ],
list_to_binary(string:join([Prefix|Ess], "\n")). list_to_binary(string:join([Prefix|Ess], "\n")).
@ -112,7 +119,7 @@ join_errors(Prefix, Errors, Pfun) ->
%% terms for the arguments. %% terms for the arguments.
%% NOTE: Special treatment for "init" since it might be implicit and has %% NOTE: Special treatment for "init" since it might be implicit and has
%% a special return type (typerep, T) %% 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(). when Type :: term().
check_call(Source, "init" = FunName, Args, Options) -> check_call(Source, "init" = FunName, Args, Options) ->
PatchFun = fun(T) -> {tuple, [typerep, T]} end, PatchFun = fun(T) -> {tuple, [typerep, T]} end,
@ -132,20 +139,17 @@ check_call(Source, FunName, Args, Options) ->
check_call(ContractString0, FunName, Args, Options, PatchFun) -> check_call(ContractString0, FunName, Args, Options, PatchFun) ->
try 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), ContractString = insert_call_function(ContractString0, FunName, Args, Options),
Ast = parse(ContractString, Options), #{typed_ast := TypedAst,
ok = pp_sophia_code(Ast, Options), icode := Icode} = string_to_icode(ContractString, [permissive_address_literals | Options]),
ok = pp_ast(Ast, Options),
TypedAst = aeso_ast_infer_types:infer(Ast, [permissive_address_literals]),
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst), {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 ], ArgVMTypes = [ aeso_ast_to_icode:ast_typerep(T, Icode) || T <- ArgTypes ],
RetVMType = case RetType of RetVMType = case RetType of
{id, _, "_"} -> any; {id, _, "_"} -> any;
_ -> aeso_ast_to_icode:ast_typerep(RetType, Icode) _ -> aeso_ast_to_icode:ast_typerep(RetType, Icode)
end, end,
ok = pp_icode(Icode, Options),
#{ functions := Funs } = Icode, #{ functions := Funs } = Icode,
ArgIcode = get_arg_icode(Funs), ArgIcode = get_arg_icode(Funs),
ArgTerms = [ icode_to_term(T, Arg) || ArgTerms = [ icode_to_term(T, Arg) ||
@ -208,16 +212,12 @@ to_sophia_value(_, _, revert, Data, _Options) ->
end; end;
to_sophia_value(ContractString, FunName, ok, Data, Options) -> to_sophia_value(ContractString, FunName, ok, Data, Options) ->
try try
Ast = parse(ContractString, Options), #{ typed_ast := TypedAst,
ok = pp_sophia_code(Ast, Options), type_env := TypeEnv,
ok = pp_ast(Ast, Options), icode := Icode } = string_to_icode(ContractString, Options),
{Env, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
ok = pp_typed_ast(TypedAst, Options),
{ok, Type0} = get_decode_type(FunName, TypedAst), {ok, Type0} = get_decode_type(FunName, TypedAst),
Type = aeso_ast_infer_types:unfold_types_in_type(Env, Type0, [unfold_record_types, unfold_variant_types]), Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
Icode = to_icode(TypedAst, Options),
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode), VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
ok = pp_icode(Icode, Options),
case aeso_heap:from_binary(VmType, Data) of case aeso_heap:from_binary(VmType, Data) of
{ok, VmValue} -> {ok, VmValue} ->
try try
@ -374,7 +374,7 @@ icode_to_term(T, V) ->
icodes_to_terms(Ts, Vs) -> icodes_to_terms(Ts, Vs) ->
[ icode_to_term(T, V) || {T, V} <- lists:zip(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). aeso_ast_to_icode:convert_typed(TypedAst, Options).
assemble(Icode, Options) -> assemble(Icode, Options) ->

View File

@ -68,9 +68,14 @@ decl() ->
modifiers() -> modifiers() ->
many(choice([token(stateful), token(public), token(private), token(internal)])). many(choice([token(stateful), token(public), token(private), token(internal)])).
add_modifiers(Mods, Node) -> 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, lists:foldl(fun({Mod, _}, X) -> set_ann(Mod, true, X) end,
Node, Mods). Node, Mods)).
%% -- Type declarations ------------------------------------------------------ %% -- Type declarations ------------------------------------------------------

View File

@ -320,6 +320,7 @@ expr_p(_, E = {int, _, N}) ->
text(S); text(S);
expr_p(_, {bool, _, B}) -> text(atom_to_list(B)); expr_p(_, {bool, _, B}) -> text(atom_to_list(B));
expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16)); expr_p(_, {hash, _, <<N:256>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {hash, _, <<N:512>>}) -> text("#" ++ integer_to_list(N, 16));
expr_p(_, {unit, _}) -> text("()"); expr_p(_, {unit, _}) -> text("()");
expr_p(_, {string, _, S}) -> term(binary_to_list(S)); expr_p(_, {string, _, S}) -> term(binary_to_list(S));
expr_p(_, {char, _, C}) -> expr_p(_, {char, _, C}) ->

View File

@ -97,6 +97,7 @@ calldata_test() ->
Map = #{ <<"a">> => 4 }, Map = #{ <<"a">> => 4 },
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] = [{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]), 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. ok.
calldata_init_test() -> calldata_init_test() ->
@ -105,14 +106,51 @@ calldata_init_test() ->
Code = parameterized_contract("foo", ["int"]), Code = parameterized_contract("foo", ["int"]),
encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}). 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([], FunName, Types).
parameterized_contract(ExtraCode, FunName, Types) ->
lists:flatten( lists:flatten(
["contract Dummy =\n", ["contract Dummy =\n",
ExtraCode, "\n",
" type an_alias('a) = (string, 'a)\n" " type an_alias('a) = (string, 'a)\n"
" record r = {x : an_alias(int), y : variant}\n" " record r = {x : an_alias(int), y : variant}\n"
" datatype variant = Red | Blue(map(string, int))\n" " datatype variant = Red | Blue(map(string, int))\n"
" function ", FunName, " : (", string:join(Types, ", "), ") => 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", _/binary>>} =
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
ok.
encode_decode_calldata(FunName, Types, Args) -> encode_decode_calldata(FunName, Types, Args) ->
encode_decode_calldata(FunName, Types, Args, word). encode_decode_calldata(FunName, Types, Args, word).