Merge pull request #30 from aeternity/to-sophia-value-revisited
To sophia value revisited
This commit is contained in:
commit
e94c1f9d84
@ -11,7 +11,7 @@
|
|||||||
-define(HASH_SIZE, 32).
|
-define(HASH_SIZE, 32).
|
||||||
|
|
||||||
-export([ old_create_calldata/3
|
-export([ old_create_calldata/3
|
||||||
, create_calldata/5
|
, create_calldata/4
|
||||||
, check_calldata/2
|
, check_calldata/2
|
||||||
, function_type_info/3
|
, function_type_info/3
|
||||||
, function_type_hash/3
|
, function_type_hash/3
|
||||||
@ -39,22 +39,11 @@
|
|||||||
%%%===================================================================
|
%%%===================================================================
|
||||||
%%% Handle calldata
|
%%% Handle calldata
|
||||||
|
|
||||||
create_calldata(Contract, FunName, Args, ArgTypes, RetType) ->
|
create_calldata(FunName, Args, ArgTypes, RetType) ->
|
||||||
case get_type_info_and_hash(Contract, FunName) of
|
{<<TypeHashInt:?HASH_SIZE/unit:8>>, _, _, _} =
|
||||||
{ok, TypeInfo, TypeHashInt} ->
|
function_type_info(list_to_binary(FunName), ArgTypes, RetType),
|
||||||
Data = aeso_heap:to_binary({TypeHashInt, list_to_tuple(Args)}),
|
Data = aeso_heap:to_binary({TypeHashInt, list_to_tuple(Args)}),
|
||||||
case check_calldata(Data, TypeInfo) of
|
{ok, Data, {tuple, [word, {tuple, ArgTypes}]}, RetType}.
|
||||||
{ok, CallDataType, OutType} ->
|
|
||||||
case check_given_type(FunName, ArgTypes, RetType, CallDataType, OutType) of
|
|
||||||
ok ->
|
|
||||||
{ok, Data, CallDataType, OutType};
|
|
||||||
{error, _} = Err ->
|
|
||||||
Err
|
|
||||||
end;
|
|
||||||
{error,_What} = Err -> Err
|
|
||||||
end;
|
|
||||||
{error, _} = Err -> Err
|
|
||||||
end.
|
|
||||||
|
|
||||||
get_type_info_and_hash(#{type_info := TypeInfo}, FunName) ->
|
get_type_info_and_hash(#{type_info := TypeInfo}, FunName) ->
|
||||||
FunBin = list_to_binary(FunName),
|
FunBin = list_to_binary(FunName),
|
||||||
@ -64,26 +53,6 @@ get_type_info_and_hash(#{type_info := TypeInfo}, FunName) ->
|
|||||||
{error, _} = Err -> Err
|
{error, _} = Err -> Err
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Check that the given type matches the type from the metadata.
|
|
||||||
check_given_type(FunName, GivenArgs, GivenRet, CalldataType, ExpectRet) ->
|
|
||||||
{tuple, [word, {tuple, ExpectArgs}]} = CalldataType,
|
|
||||||
ReturnOk = if FunName == "init" -> true;
|
|
||||||
GivenRet == any -> true;
|
|
||||||
true -> GivenRet == ExpectRet
|
|
||||||
end,
|
|
||||||
ArgsOk = ExpectArgs == GivenArgs,
|
|
||||||
case ReturnOk andalso ArgsOk of
|
|
||||||
true -> ok;
|
|
||||||
false when FunName == "init" ->
|
|
||||||
{error, {init_args_mismatch,
|
|
||||||
{given, GivenArgs},
|
|
||||||
{expected, ExpectArgs}}};
|
|
||||||
false ->
|
|
||||||
{error, {call_type_mismatch,
|
|
||||||
{given, GivenArgs, '=>', GivenRet},
|
|
||||||
{expected, ExpectArgs, '=>', ExpectRet}}}
|
|
||||||
end.
|
|
||||||
|
|
||||||
-spec check_calldata(binary(), type_info()) ->
|
-spec check_calldata(binary(), type_info()) ->
|
||||||
{'ok', typerep(), typerep()} | {'error', atom()}.
|
{'ok', typerep(), typerep()} | {'error', atom()}.
|
||||||
check_calldata(CallData, TypeInfo) ->
|
check_calldata(CallData, TypeInfo) ->
|
||||||
|
@ -11,12 +11,12 @@
|
|||||||
-export([ file/1
|
-export([ file/1
|
||||||
, file/2
|
, file/2
|
||||||
, from_string/2
|
, from_string/2
|
||||||
, check_call/2
|
, check_call/4
|
||||||
, create_calldata/3
|
, create_calldata/3
|
||||||
, version/0
|
, version/0
|
||||||
, sophia_type_to_typerep/1
|
, sophia_type_to_typerep/1
|
||||||
, to_sophia_value/2
|
, to_sophia_value/4
|
||||||
, to_sophia_value/3
|
, to_sophia_value/5
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||||
@ -110,10 +110,11 @@ join_errors(Prefix, Errors, Pfun) ->
|
|||||||
%% function (foo, say) and a function __call() = foo(args) calling this
|
%% function (foo, say) and a function __call() = foo(args) calling this
|
||||||
%% function. Returns the name of the called functions, typereps and Erlang
|
%% function. Returns the name of the called functions, typereps and Erlang
|
||||||
%% terms for the arguments.
|
%% terms for the arguments.
|
||||||
-spec check_call(string(), options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()}
|
-spec check_call(string(), string(), [string()], options()) -> {ok, string(), {[Type], Type | any}, [term()]} | {error, term()}
|
||||||
when Type :: term().
|
when Type :: term().
|
||||||
check_call(ContractString, Options) ->
|
check_call(ContractString0, FunName, Args, Options) ->
|
||||||
try
|
try
|
||||||
|
ContractString = insert_call_function(ContractString0, FunName, Args, Options),
|
||||||
Ast = parse(ContractString, Options),
|
Ast = parse(ContractString, Options),
|
||||||
ok = pp_sophia_code(Ast, Options),
|
ok = pp_sophia_code(Ast, Options),
|
||||||
ok = pp_ast(Ast, Options),
|
ok = pp_ast(Ast, Options),
|
||||||
@ -145,40 +146,71 @@ check_call(ContractString, Options) ->
|
|||||||
fun (E) -> io_lib:format("~p", [E]) end)}
|
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec to_sophia_value(string(), aeso_sophia:data()) ->
|
%% Add the __call function to a contract.
|
||||||
{ok, aeso_syntax:expr()} | {error, term()}.
|
-spec insert_call_function(string(), string(), [string()], options()) -> string().
|
||||||
to_sophia_value(ContractString, Data) ->
|
insert_call_function(Code, FunName, Args, Options) ->
|
||||||
to_sophia_value(ContractString, Data, []).
|
Ast = parse(Code, Options),
|
||||||
|
Ind = last_contract_indent(Ast),
|
||||||
|
lists:flatten(
|
||||||
|
[ Code,
|
||||||
|
"\n\n",
|
||||||
|
lists:duplicate(Ind, " "),
|
||||||
|
"function __call() = ", FunName, "(", string:join(Args, ","), ")\n"
|
||||||
|
]).
|
||||||
|
|
||||||
-spec to_sophia_value(string(), aeso_sophia:data(), options()) ->
|
last_contract_indent(Decls) ->
|
||||||
|
case lists:last(Decls) of
|
||||||
|
{_, _, _, [Decl | _]} -> aeso_syntax:get_ann(col, Decl, 1) - 1;
|
||||||
|
_ -> 0
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec to_sophia_value(string(), string(), ok | error | revert, aeso_sophia:data()) ->
|
||||||
{ok, aeso_syntax:expr()} | {error, term()}.
|
{ok, aeso_syntax:expr()} | {error, term()}.
|
||||||
to_sophia_value(ContractString, Data, Options) ->
|
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
||||||
|
to_sophia_value(ContractString, Fun, ResType, Data, []).
|
||||||
|
|
||||||
|
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
|
||||||
|
{ok, aeso_syntax:expr()} | {error, term()}.
|
||||||
|
to_sophia_value(_, _, error, Err, _Options) ->
|
||||||
|
{ok, {app, [], {id, [], "error"}, [{string, [], Err}]}};
|
||||||
|
to_sophia_value(_, _, revert, Data, _Options) ->
|
||||||
|
case aeso_heap:from_binary(string, Data) of
|
||||||
|
{ok, Err} -> {ok, {app, [], {id, [], "abort"}, [{string, [], Err}]}};
|
||||||
|
{error, _} = Err -> Err
|
||||||
|
end;
|
||||||
|
to_sophia_value(ContractString, FunName, ok, Data, Options) ->
|
||||||
try
|
try
|
||||||
Ast = parse(ContractString, Options),
|
Ast = parse(ContractString, Options),
|
||||||
ok = pp_sophia_code(Ast, Options),
|
ok = pp_sophia_code(Ast, Options),
|
||||||
ok = pp_ast(Ast, Options),
|
ok = pp_ast(Ast, Options),
|
||||||
{Env, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
|
{Env, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
|
||||||
{ok, Type0} = get_decode_type(TypedAst),
|
|
||||||
Type = aeso_ast_infer_types:unfold_types_in_type(Env, Type0, [unfold_record_types, unfold_variant_types]),
|
|
||||||
ok = pp_typed_ast(TypedAst, Options),
|
ok = pp_typed_ast(TypedAst, 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),
|
Icode = to_icode(TypedAst, Options),
|
||||||
{ok, VmType} = get_decode_vm_type(Icode),
|
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||||
ok = pp_icode(Icode, Options),
|
ok = pp_icode(Icode, Options),
|
||||||
try
|
case aeso_heap:from_binary(VmType, Data) of
|
||||||
{ok, translate_vm_value(VmType, Type, Data)}
|
{ok, VmValue} ->
|
||||||
catch throw:cannot_translate_to_sophia ->
|
try
|
||||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
{ok, translate_vm_value(VmType, Type, VmValue)}
|
||||||
{error, join_errors("Translation error", [io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
|
catch throw:cannot_translate_to_sophia ->
|
||||||
[Data, VmType, Type0Str])],
|
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||||
fun (E) -> E end)}
|
{error, join_errors("Translation error", [lists:flatten(io_lib:format("Cannot translate VM value ~p\n of type ~p\n to Sophia type ~s\n",
|
||||||
|
[Data, VmType, Type0Str]))],
|
||||||
|
fun (E) -> E end)}
|
||||||
|
end;
|
||||||
|
{error, _Err} ->
|
||||||
|
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
|
||||||
|
fun(E) -> E end)}
|
||||||
end
|
end
|
||||||
catch
|
catch
|
||||||
error:{parse_errors, Errors} ->
|
error:{parse_errors, Errors} ->
|
||||||
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
|
{error, join_errors("Parse errors", Errors, fun (E) -> E end)};
|
||||||
error:{type_errors, Errors} ->
|
error:{type_errors, Errors} ->
|
||||||
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
|
{error, join_errors("Type errors", Errors, fun (E) -> E end)};
|
||||||
error:{badmatch, {error, missing_decode_function}} ->
|
error:{badmatch, {error, missing_function}} ->
|
||||||
{error, join_errors("Type errors", ["missing __decode function"],
|
{error, join_errors("Type errors", ["no function: '" ++ FunName ++ "'"],
|
||||||
fun (E) -> E end)};
|
fun (E) -> E end)};
|
||||||
throw:Error -> %Don't ask
|
throw:Error -> %Don't ask
|
||||||
{error, join_errors("Code errors", [Error],
|
{error, join_errors("Code errors", [Error],
|
||||||
@ -239,34 +271,16 @@ translate_vm_value(VmTypes, {constr_t, _, Con, Types}, Args)
|
|||||||
translate_vm_value(_VmType, _Type, _Data) ->
|
translate_vm_value(_VmType, _Type, _Data) ->
|
||||||
throw(cannot_translate_to_sophia).
|
throw(cannot_translate_to_sophia).
|
||||||
|
|
||||||
-spec create_calldata(map(), string(), string()) ->
|
-spec create_calldata(string(), string(), [string()]) ->
|
||||||
{ok, binary(), aeso_sophia:type(), aeso_sophia:type()}
|
{ok, binary(), aeso_sophia:type(), aeso_sophia:type()}
|
||||||
| {error, argument_syntax_error}.
|
| {error, term()}.
|
||||||
create_calldata(Contract, "", CallCode) when is_map(Contract) ->
|
create_calldata(Code, Fun, Args) ->
|
||||||
case check_call(CallCode, []) of
|
case check_call(Code, Fun, Args, []) of
|
||||||
{ok, FunName, {ArgTypes, RetType}, Args} ->
|
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
|
||||||
aeso_abi:create_calldata(Contract, FunName, Args, ArgTypes, RetType);
|
aeso_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
|
||||||
{error, _} = Err -> Err
|
{error, _} = Err -> Err
|
||||||
end;
|
|
||||||
create_calldata(Contract, Function, Argument) when is_map(Contract) ->
|
|
||||||
%% Slightly hacky shortcut to let you get away without writing the full
|
|
||||||
%% call contract code.
|
|
||||||
%% Function should be "foo : type", and
|
|
||||||
%% Argument should be "Arg1, Arg2, .., ArgN" (no parens)
|
|
||||||
case string:lexemes(Function, ": ") of
|
|
||||||
%% If function is a single word fallback to old calldata generation
|
|
||||||
[FunName] -> aeso_abi:old_create_calldata(Contract, FunName, Argument);
|
|
||||||
[FunName | _] ->
|
|
||||||
Args = lists:map(fun($\n) -> 32; (X) -> X end, Argument), %% newline to space
|
|
||||||
CallContract = lists:flatten(
|
|
||||||
[ "contract MakeCall =\n"
|
|
||||||
, " function ", Function, "\n"
|
|
||||||
, " function __call() = ", FunName, "(", Args, ")"
|
|
||||||
]),
|
|
||||||
create_calldata(Contract, "", CallContract)
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
get_arg_icode(Funs) ->
|
get_arg_icode(Funs) ->
|
||||||
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
|
case [ Args || {[_, ?CALL_NAME], _, _, {funcall, _, Args}, _} <- Funs ] of
|
||||||
[Args] -> Args;
|
[Args] -> Args;
|
||||||
@ -286,21 +300,17 @@ get_call_type([_ | Contracts]) ->
|
|||||||
%% The __call should be in the final contract
|
%% The __call should be in the final contract
|
||||||
get_call_type(Contracts).
|
get_call_type(Contracts).
|
||||||
|
|
||||||
get_decode_type([{contract, _, _, Defs}]) ->
|
get_decode_type(FunName, [{contract, _, _, Defs}]) ->
|
||||||
case [ DecodeType
|
GetType = fun({letfun, _, {id, _, Name}, _, Ret, _}) when Name == FunName -> [Ret];
|
||||||
|| {letfun, _, {id, _, ?DECODE_NAME}, [{arg, _, _, DecodeType}], _Ret, _} <- Defs ] of
|
({fun_decl, _, {id, _, Name}, {fun_t, _, _, _, Ret}}) when Name == FunName -> [Ret];
|
||||||
|
(_) -> [] end,
|
||||||
|
case lists:flatmap(GetType, Defs) of
|
||||||
[Type] -> {ok, Type};
|
[Type] -> {ok, Type};
|
||||||
[] -> {error, missing_call_function}
|
[] -> {error, missing_function}
|
||||||
end;
|
end;
|
||||||
get_decode_type([_ | Contracts]) ->
|
get_decode_type(FunName, [_ | Contracts]) ->
|
||||||
%% The __decode should be in the final contract
|
%% The __decode should be in the final contract
|
||||||
get_decode_type(Contracts).
|
get_decode_type(FunName, Contracts).
|
||||||
|
|
||||||
get_decode_vm_type(#{ functions := Funs }) ->
|
|
||||||
case [ VMType || {[_, ?DECODE_NAME], _, [{_, VMType}], _, _} <- Funs ] of
|
|
||||||
[Type] -> {ok, Type};
|
|
||||||
[] -> {error, missing_decode_function}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% Translate an icode value (error if not value) to an Erlang term that can be
|
%% Translate an icode value (error if not value) to an Erlang term that can be
|
||||||
%% consumed by aeso_heap:to_binary().
|
%% consumed by aeso_heap:to_binary().
|
||||||
|
@ -70,23 +70,17 @@ encode_decode_sophia_test() ->
|
|||||||
|
|
||||||
encode_decode_sophia_string(SophiaType, String) ->
|
encode_decode_sophia_string(SophiaType, String) ->
|
||||||
io:format("String ~p~n", [String]),
|
io:format("String ~p~n", [String]),
|
||||||
TypeDefs = [" type an_alias('a) = (string, 'a)\n",
|
|
||||||
" record r = {x : an_alias(int), y : variant}\n"
|
|
||||||
" datatype variant = Red | Blue(map(string, int))\n"],
|
|
||||||
Code = [ "contract MakeCall =\n"
|
Code = [ "contract MakeCall =\n"
|
||||||
, " type arg_type = ", SophiaType, "\n"
|
, " type arg_type = ", SophiaType, "\n"
|
||||||
, TypeDefs
|
, " type an_alias('a) = (string, 'a)\n"
|
||||||
, " function foo : arg_type => _\n"
|
, " record r = {x : an_alias(int), y : variant}\n"
|
||||||
, " function __call() = foo(", String, ")\n" ],
|
, " datatype variant = Red | Blue(map(string, int))\n"
|
||||||
case aeso_compiler:check_call(lists:flatten(Code), []) of
|
, " function foo : arg_type => arg_type\n" ],
|
||||||
|
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], []) of
|
||||||
{ok, _, {[Type], _}, [Arg]} ->
|
{ok, _, {[Type], _}, [Arg]} ->
|
||||||
io:format("Type ~p~n", [Type]),
|
io:format("Type ~p~n", [Type]),
|
||||||
Data = encode(Arg),
|
Data = encode(Arg),
|
||||||
Decoded = decode(Type, Data),
|
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data) of
|
||||||
DecodeCode = [ "contract Decode =\n",
|
|
||||||
TypeDefs,
|
|
||||||
" function __decode(_ : ", SophiaType, ") = ()\n" ],
|
|
||||||
case aeso_compiler:to_sophia_value(DecodeCode, Decoded) of
|
|
||||||
{ok, Sophia} ->
|
{ok, Sophia} ->
|
||||||
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
|
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
|
||||||
{error, Err} ->
|
{error, Err} ->
|
||||||
@ -98,6 +92,24 @@ encode_decode_sophia_string(SophiaType, String) ->
|
|||||||
{error, Err}
|
{error, Err}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
calldata_test() ->
|
||||||
|
[42, <<"foobar">>] = encode_decode_calldata(["int", "string"], ["42", "\"foobar\""]),
|
||||||
|
Map = #{ <<"a">> => 4 },
|
||||||
|
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
|
||||||
|
encode_decode_calldata(["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
encode_decode_calldata(Types, Args) ->
|
||||||
|
Code = lists:flatten(
|
||||||
|
["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 foo : (", string:join(Types, ", "), ") => int\n" ]),
|
||||||
|
{ok, Calldata, CalldataType, word} = aeso_compiler:create_calldata(Code, "foo", Args),
|
||||||
|
{ok, {_Hash, ArgTuple}} = aeso_heap:from_binary(CalldataType, Calldata),
|
||||||
|
tuple_to_list(ArgTuple).
|
||||||
|
|
||||||
encode_decode(T, D) ->
|
encode_decode(T, D) ->
|
||||||
?assertEqual(D, decode(T, encode(D))),
|
?assertEqual(D, decode(T, encode(D))),
|
||||||
D.
|
D.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user