Merge pull request #34 from aeternity/fix_create_calldata_again_again
Fix calldata creation for contracts involving oracles
This commit is contained in:
commit
4d9d3077ad
@ -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 ],
|
||||||
|
@ -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) ->
|
||||||
|
@ -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 ------------------------------------------------------
|
||||||
|
|
||||||
|
@ -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}) ->
|
||||||
|
@ -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).
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user