Merge pull request #91 from aeternity/PT-166696064-add-calldata-decode
Pt 166696064 add calldata decode
This commit is contained in:
commit
20aeade545
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{erl_opts, [debug_info]}.
|
{erl_opts, [debug_info]}.
|
||||||
|
|
||||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"9dfc5f4"}}}
|
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"f91c8fa"}}}
|
||||||
, {getopt, "1.0.1"}
|
, {getopt, "1.0.1"}
|
||||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||||
{tag, "2.8.0"}}}
|
{tag, "2.8.0"}}}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{"1.1.0",
|
{"1.1.0",
|
||||||
[{<<"aebytecode">>,
|
[{<<"aebytecode">>,
|
||||||
{git,"https://github.com/aeternity/aebytecode.git",
|
{git,"https://github.com/aeternity/aebytecode.git",
|
||||||
{ref,"9dfc5f4f1d1a9676cb6b9577af74a4566cc3f3f4"}},
|
{ref,"f91c8fabdd01cf911fb194862a50f9635c96c0e5"}},
|
||||||
0},
|
0},
|
||||||
{<<"aeserialization">>,
|
{<<"aeserialization">>,
|
||||||
{git,"https://github.com/aeternity/aeserialization.git",
|
{git,"https://github.com/aeternity/aeserialization.git",
|
||||||
|
@ -16,9 +16,10 @@
|
|||||||
, create_calldata/4
|
, create_calldata/4
|
||||||
, version/0
|
, version/0
|
||||||
, sophia_type_to_typerep/1
|
, sophia_type_to_typerep/1
|
||||||
, to_sophia_value/4
|
, to_sophia_value/4 %% deprecated, need a backend
|
||||||
, to_sophia_value/5
|
, to_sophia_value/5
|
||||||
, decode_calldata/3
|
, decode_calldata/3 %% deprecated
|
||||||
|
, decode_calldata/4
|
||||||
, parse/2
|
, parse/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
@ -99,7 +100,7 @@ from_string(Backend, ContractString, Options) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
from_string1(aevm, ContractString, Options) ->
|
from_string1(aevm, ContractString, Options) ->
|
||||||
#{icode := Icode} = string_to_icode(ContractString, Options),
|
#{icode := Icode} = string_to_code(ContractString, Options),
|
||||||
TypeInfo = extract_type_info(Icode),
|
TypeInfo = extract_type_info(Icode),
|
||||||
Assembler = assemble(Icode, Options),
|
Assembler = assemble(Icode, Options),
|
||||||
pp_assembler(Assembler, Options),
|
pp_assembler(Assembler, Options),
|
||||||
@ -113,9 +114,7 @@ from_string1(aevm, ContractString, Options) ->
|
|||||||
type_info => TypeInfo
|
type_info => TypeInfo
|
||||||
}};
|
}};
|
||||||
from_string1(fate, ContractString, Options) ->
|
from_string1(fate, ContractString, Options) ->
|
||||||
Ast = parse(ContractString, Options),
|
#{fcode := FCode} = string_to_code(ContractString, Options),
|
||||||
TypedAst = aeso_ast_infer_types:infer(Ast, Options),
|
|
||||||
FCode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options),
|
|
||||||
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
|
FateCode = aeso_fcode_to_fate:compile(FCode, Options),
|
||||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||||
{ok, Version} = version(),
|
{ok, Version} = version(),
|
||||||
@ -126,30 +125,26 @@ from_string1(fate, ContractString, Options) ->
|
|||||||
fate_code => FateCode
|
fate_code => FateCode
|
||||||
}}.
|
}}.
|
||||||
|
|
||||||
-spec string_to_icode(string(), [option()]) -> map().
|
-spec string_to_code(string(), [option()]) -> map().
|
||||||
string_to_icode(ContractString, Options) ->
|
string_to_code(ContractString, Options) ->
|
||||||
Ast = parse(ContractString, Options),
|
Ast = parse(ContractString, Options),
|
||||||
pp_sophia_code(Ast, Options),
|
pp_sophia_code(Ast, Options),
|
||||||
pp_ast(Ast, Options),
|
pp_ast(Ast, Options),
|
||||||
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
|
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
|
||||||
pp_typed_ast(TypedAst, Options),
|
pp_typed_ast(TypedAst, Options),
|
||||||
Icode = ast_to_icode(TypedAst, Options),
|
case proplists:get_value(backend, Options, aevm) of
|
||||||
pp_icode(Icode, Options),
|
aevm ->
|
||||||
#{ typed_ast => TypedAst,
|
Icode = ast_to_icode(TypedAst, Options),
|
||||||
type_env => TypeEnv,
|
pp_icode(Icode, Options),
|
||||||
icode => Icode }.
|
#{ icode => Icode,
|
||||||
|
typed_ast => TypedAst,
|
||||||
-spec string_to_fcode(string(), [option()]) -> map().
|
type_env => TypeEnv};
|
||||||
string_to_fcode(ContractString, Options) ->
|
fate ->
|
||||||
Ast = parse(ContractString, Options),
|
Fcode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options),
|
||||||
pp_sophia_code(Ast, Options),
|
#{ fcode => Fcode,
|
||||||
pp_ast(Ast, Options),
|
typed_ast => TypedAst,
|
||||||
{TypeEnv, TypedAst} = aeso_ast_infer_types:infer(Ast, [return_env]),
|
type_env => TypeEnv}
|
||||||
pp_typed_ast(TypedAst, Options),
|
end.
|
||||||
Fcode = aeso_ast_to_fcode:ast_to_fcode(TypedAst, Options),
|
|
||||||
#{ typed_ast => TypedAst,
|
|
||||||
type_env => TypeEnv,
|
|
||||||
fcode => Fcode }.
|
|
||||||
|
|
||||||
join_errors(Prefix, Errors, Pfun) ->
|
join_errors(Prefix, Errors, Pfun) ->
|
||||||
Ess = [ Pfun(E) || E <- Errors ],
|
Ess = [ Pfun(E) || E <- Errors ],
|
||||||
@ -187,10 +182,10 @@ check_call1(ContractString0, FunName, Args, Options) ->
|
|||||||
case proplists:get_value(backend, Options, aevm) of
|
case proplists:get_value(backend, Options, aevm) of
|
||||||
aevm ->
|
aevm ->
|
||||||
%% First check the contract without the __call function
|
%% First check the contract without the __call function
|
||||||
#{} = string_to_icode(ContractString0, Options),
|
#{} = string_to_code(ContractString0, Options),
|
||||||
ContractString = insert_call_function(ContractString0, ?CALL_NAME, FunName, Args, Options),
|
ContractString = insert_call_function(ContractString0, ?CALL_NAME, FunName, Args, Options),
|
||||||
#{typed_ast := TypedAst,
|
#{typed_ast := TypedAst,
|
||||||
icode := Icode} = string_to_icode(ContractString, Options),
|
icode := Icode} = string_to_code(ContractString, Options),
|
||||||
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
|
{ok, {FunName, {fun_t, _, _, ArgTypes, RetType}}} = get_call_type(TypedAst),
|
||||||
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
|
||||||
@ -209,14 +204,14 @@ check_call1(ContractString0, FunName, Args, Options) ->
|
|||||||
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
|
{ok, FunName, {ArgVMTypes, RetVMType1}, ArgTerms};
|
||||||
fate ->
|
fate ->
|
||||||
%% First check the contract without the __call function
|
%% First check the contract without the __call function
|
||||||
#{fcode := OrgFcode} = string_to_fcode(ContractString0, Options),
|
#{fcode := OrgFcode} = string_to_code(ContractString0, Options),
|
||||||
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
|
FateCode = aeso_fcode_to_fate:compile(OrgFcode, []),
|
||||||
%% collect all hashes and compute the first name without hash collision to
|
%% collect all hashes and compute the first name without hash collision to
|
||||||
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
|
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
|
||||||
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
||||||
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
|
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
|
||||||
ContractString = insert_call_function(ContractString0, CallName, FunName, Args, Options),
|
ContractString = insert_call_function(ContractString0, CallName, FunName, Args, Options),
|
||||||
#{fcode := Fcode} = string_to_fcode(ContractString, Options),
|
#{fcode := Fcode} = string_to_code(ContractString, Options),
|
||||||
CallArgs = arguments_of_body(CallName, FunName, Fcode),
|
CallArgs = arguments_of_body(CallName, FunName, Fcode),
|
||||||
{ok, FunName, CallArgs}
|
{ok, FunName, CallArgs}
|
||||||
end
|
end
|
||||||
@ -280,7 +275,7 @@ last_contract_indent(Decls) ->
|
|||||||
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
|
-spec to_sophia_value(string(), string(), ok | error | revert, aeb_aevm_data:data()) ->
|
||||||
{ok, aeso_syntax:expr()} | {error, term()}.
|
{ok, aeso_syntax:expr()} | {error, term()}.
|
||||||
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
to_sophia_value(ContractString, Fun, ResType, Data) ->
|
||||||
to_sophia_value(ContractString, Fun, ResType, Data, []).
|
to_sophia_value(ContractString, Fun, ResType, Data, [{backend, aevm}]).
|
||||||
|
|
||||||
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
|
-spec to_sophia_value(string(), string(), ok | error | revert, binary(), options()) ->
|
||||||
{ok, aeso_syntax:expr()} | {error, term()}.
|
{ok, aeso_syntax:expr()} | {error, term()}.
|
||||||
@ -295,14 +290,14 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) ->
|
|||||||
try
|
try
|
||||||
#{ typed_ast := TypedAst,
|
#{ typed_ast := TypedAst,
|
||||||
type_env := TypeEnv,
|
type_env := TypeEnv,
|
||||||
icode := Icode } = string_to_icode(ContractString, Options),
|
icode := Icode } = string_to_code(ContractString, Options),
|
||||||
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
|
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
|
||||||
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
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),
|
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||||
case aeb_heap:from_binary(VmType, Data) of
|
case aeb_heap:from_binary(VmType, Data) of
|
||||||
{ok, VmValue} ->
|
{ok, VmValue} ->
|
||||||
try
|
try
|
||||||
{ok, translate_vm_value(VmType, Type, VmValue)}
|
{ok, aeso_vm_decode:from_aevm(VmType, Type, VmValue)}
|
||||||
catch throw:cannot_translate_to_sophia ->
|
catch throw:cannot_translate_to_sophia ->
|
||||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||||
{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",
|
{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",
|
||||||
@ -326,62 +321,6 @@ to_sophia_value(ContractString, FunName, ok, Data, Options) ->
|
|||||||
fun (E) -> io_lib:format("~p", [E]) end)}
|
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
address_literal(Type, N) -> {Type, [], <<N:256>>}.
|
|
||||||
|
|
||||||
%% 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(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) -> <<N1:256/signed>> = <<N:256>>, {int, [], N1};
|
|
||||||
translate_vm_value(word, {id, _, "bits"}, N) -> error({todo, bits, N});
|
|
||||||
translate_vm_value(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
|
|
||||||
translate_vm_value(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
|
|
||||||
{bytes, [], <<Val:Len/unit:8>>};
|
|
||||||
translate_vm_value({tuple, _}, {bytes_t, _, Len}, Val) ->
|
|
||||||
{bytes, [], binary:part(<< <<W:32/unit:8>> || 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]};
|
|
||||||
translate_vm_value({option, VmType}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
|
|
||||||
case Val of
|
|
||||||
none -> {con, [], "None"};
|
|
||||||
{some, X} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
|
|
||||||
end;
|
|
||||||
translate_vm_value({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
|
|
||||||
case Val of
|
|
||||||
{variant, 0, []} -> {con, [], "None"};
|
|
||||||
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [translate_vm_value(VmType, Type, X)]}
|
|
||||||
end;
|
|
||||||
translate_vm_value({tuple, VmTypes}, {tuple_t, _, Types}, Val)
|
|
||||||
when length(VmTypes) == length(Types),
|
|
||||||
length(VmTypes) == tuple_size(Val) ->
|
|
||||||
{tuple, [], [translate_vm_value(VmType, Type, X)
|
|
||||||
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
|
|
||||||
translate_vm_value({tuple, VmTypes}, {record_t, Fields}, Val)
|
|
||||||
when length(VmTypes) == length(Fields),
|
|
||||||
length(VmTypes) == tuple_size(Val) ->
|
|
||||||
{record, [], [ {field, [], [{proj, [], FName}], translate_vm_value(VmType, FType, X)}
|
|
||||||
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
|
|
||||||
translate_vm_value({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
|
|
||||||
when is_map(Map) ->
|
|
||||||
{map, [], [ {translate_vm_value(VmKeyType, KeyType, Key),
|
|
||||||
translate_vm_value(VmValType, ValType, Val)}
|
|
||||||
|| {Key, Val} <- maps:to_list(Map) ]};
|
|
||||||
translate_vm_value({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
|
|
||||||
when length(VmCons) == length(Cons),
|
|
||||||
length(VmCons) > Tag ->
|
|
||||||
VmTypes = lists:nth(Tag + 1, VmCons),
|
|
||||||
ConType = lists:nth(Tag + 1, Cons),
|
|
||||||
translate_vm_value(VmTypes, ConType, Args);
|
|
||||||
translate_vm_value(VmTypes, {constr_t, _, Con, Types}, Args)
|
|
||||||
when length(VmTypes) == length(Types),
|
|
||||||
length(VmTypes) == length(Args) ->
|
|
||||||
{app, [], Con, [ translate_vm_value(VmType, Type, Arg)
|
|
||||||
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
|
|
||||||
translate_vm_value(_VmType, _Type, _Data) ->
|
|
||||||
throw(cannot_translate_to_sophia).
|
|
||||||
|
|
||||||
-spec create_calldata(string(), string(), [string()]) ->
|
-spec create_calldata(string(), string(), [string()]) ->
|
||||||
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
|
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
|
||||||
@ -390,16 +329,15 @@ create_calldata(Code, Fun, Args) ->
|
|||||||
create_calldata(Code, Fun, Args, [{backend, aevm}]).
|
create_calldata(Code, Fun, Args, [{backend, aevm}]).
|
||||||
|
|
||||||
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
|
-spec create_calldata(string(), string(), [string()], [{atom(), any()}]) ->
|
||||||
{ok, binary(), aeb_aevm_data:type(), aeb_aevm_data:type()}
|
{ok, binary()}
|
||||||
| {ok, binary()}
|
|
||||||
| {error, term()}.
|
| {error, term()}.
|
||||||
create_calldata(Code, Fun, Args, Options) ->
|
create_calldata(Code, Fun, Args, Options) ->
|
||||||
case proplists:get_value(backend, Options, aevm) of
|
case proplists:get_value(backend, Options, aevm) of
|
||||||
aevm ->
|
aevm ->
|
||||||
case check_call(Code, Fun, Args, Options) of
|
case check_call(Code, Fun, Args, Options) of
|
||||||
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
|
{ok, FunName, {ArgTypes, RetType}, VMArgs} ->
|
||||||
aeb_aevm_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
|
aeb_aevm_abi:create_calldata(FunName, VMArgs, ArgTypes, RetType);
|
||||||
{error, _} = Err -> Err
|
{error, _} = Err -> Err
|
||||||
end;
|
end;
|
||||||
fate ->
|
fate ->
|
||||||
case check_call(Code, Fun, Args, Options) of
|
case check_call(Code, Fun, Args, Options) of
|
||||||
@ -413,30 +351,59 @@ create_calldata(Code, Fun, Args, Options) ->
|
|||||||
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
|
{ok, [aeso_syntax:type()], [aeso_syntax:expr()]}
|
||||||
| {error, term()}.
|
| {error, term()}.
|
||||||
decode_calldata(ContractString, FunName, Calldata) ->
|
decode_calldata(ContractString, FunName, Calldata) ->
|
||||||
|
decode_calldata(ContractString, FunName, Calldata, [{backend, aevm}]).
|
||||||
|
|
||||||
|
decode_calldata(ContractString, FunName, Calldata, Options) ->
|
||||||
try
|
try
|
||||||
#{ typed_ast := TypedAst,
|
Code = string_to_code(ContractString, Options),
|
||||||
type_env := TypeEnv,
|
#{ typed_ast := TypedAst, type_env := TypeEnv} = Code,
|
||||||
icode := Icode } = string_to_icode(ContractString, []),
|
|
||||||
{ok, Args, _} = get_decode_type(FunName, TypedAst),
|
{ok, Args, _} = get_decode_type(FunName, TypedAst),
|
||||||
DropArg = fun({arg, _, _, T}) -> T; (T) -> T end,
|
DropArg = fun({arg, _, _, T}) -> T; (T) -> T end,
|
||||||
ArgTypes = lists:map(DropArg, Args),
|
ArgTypes = lists:map(DropArg, Args),
|
||||||
Type0 = {tuple_t, [], ArgTypes},
|
Type0 = {tuple_t, [], ArgTypes},
|
||||||
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
%% user defined data types such as variants needed to match against
|
||||||
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||||
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
|
case proplists:get_value(backend, Options, aevm) of
|
||||||
{ok, {_, VmValue}} ->
|
aevm ->
|
||||||
try
|
Icode = maps:get(icode, Code),
|
||||||
{tuple, [], Values} = translate_vm_value(VmType, Type, VmValue),
|
VmType = aeso_ast_to_icode:ast_typerep(Type, Icode),
|
||||||
{ok, ArgTypes, Values}
|
case aeb_heap:from_binary({tuple, [word, VmType]}, Calldata) of
|
||||||
catch throw:cannot_translate_to_sophia ->
|
{ok, {_, VmValue}} ->
|
||||||
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
try
|
||||||
{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",
|
{tuple, [], Values} = aeso_vm_decode:from_aevm(VmType, Type, VmValue),
|
||||||
[VmValue, VmType, Type0Str]))],
|
%% Values are Sophia expressions in AST format
|
||||||
fun (E) -> E end)}
|
{ok, ArgTypes, Values}
|
||||||
|
catch throw:cannot_translate_to_sophia ->
|
||||||
|
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||||
|
{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",
|
||||||
|
[VmValue, 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;
|
||||||
{error, _Err} ->
|
fate ->
|
||||||
{error, join_errors("Decode errors", [lists:flatten(io_lib:format("Failed to decode binary at type ~p", [VmType]))],
|
case aeb_fate_abi:decode_calldata(FunName, Calldata) of
|
||||||
fun(E) -> E end)}
|
{ok, FateArgs} ->
|
||||||
|
try
|
||||||
|
{tuple_t, [], ArgTypes1} = Type,
|
||||||
|
AstArgs = [ aeso_vm_decode:from_fate(ArgType, FateArg)
|
||||||
|
|| {ArgType, FateArg} <- lists:zip(ArgTypes1, FateArgs)],
|
||||||
|
{ok, ArgTypes, AstArgs}
|
||||||
|
catch throw:cannot_translate_to_sophia ->
|
||||||
|
Type0Str = prettypr:format(aeso_pretty:type(Type0)),
|
||||||
|
{error, join_errors("Translation error",
|
||||||
|
[lists:flatten(io_lib:format("Cannot translate fate value ~p\n of Sophia type ~s\n",
|
||||||
|
[FateArgs, Type0Str]))],
|
||||||
|
fun (E) -> E end)}
|
||||||
|
end;
|
||||||
|
{error, _} ->
|
||||||
|
{error, join_errors("Decode errors", ["Failed to decode binary"],
|
||||||
|
fun(E) -> E end)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
catch
|
catch
|
||||||
error:{parse_errors, Errors} ->
|
error:{parse_errors, Errors} ->
|
||||||
@ -451,7 +418,6 @@ decode_calldata(ContractString, FunName, Calldata) ->
|
|||||||
fun (E) -> io_lib:format("~p", [E]) end)}
|
fun (E) -> io_lib:format("~p", [E]) end)}
|
||||||
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;
|
||||||
@ -477,7 +443,11 @@ get_decode_type(FunName, [{contract, _, _, Defs}]) ->
|
|||||||
(_) -> [] end,
|
(_) -> [] end,
|
||||||
case lists:flatmap(GetType, Defs) of
|
case lists:flatmap(GetType, Defs) of
|
||||||
[{Args, Ret}] -> {ok, Args, Ret};
|
[{Args, Ret}] -> {ok, Args, Ret};
|
||||||
[] -> {error, missing_function}
|
[] ->
|
||||||
|
case FunName of
|
||||||
|
"init" -> {ok, [], {tuple_t, [], []}};
|
||||||
|
_ -> {error, missing_function}
|
||||||
|
end
|
||||||
end;
|
end;
|
||||||
get_decode_type(FunName, [_ | Contracts]) ->
|
get_decode_type(FunName, [_ | Contracts]) ->
|
||||||
%% The __decode should be in the final contract
|
%% The __decode should be in the final contract
|
||||||
|
@ -69,11 +69,11 @@
|
|||||||
-type constant()
|
-type constant()
|
||||||
:: {int, ann(), integer()}
|
:: {int, ann(), integer()}
|
||||||
| {bool, ann(), true | false}
|
| {bool, ann(), true | false}
|
||||||
| {hash, ann(), binary()}
|
| {bytes, ann(), binary()}
|
||||||
| {account_pubkey, binary()}
|
| {account_pubkey, ann(), binary()}
|
||||||
| {contract_pubkey, binary()}
|
| {contract_pubkey, ann(), binary()}
|
||||||
| {oracle_pubkey, binary()}
|
| {oracle_pubkey, ann(), binary()}
|
||||||
| {oracle_query_id, binary()}
|
| {oracle_query_id, ann(), binary()}
|
||||||
| {string, ann(), binary()}
|
| {string, ann(), binary()}
|
||||||
| {char, ann(), integer()}.
|
| {char, ann(), integer()}.
|
||||||
|
|
||||||
@ -148,4 +148,3 @@ get_ann(Key, Node, Default) ->
|
|||||||
qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X);
|
qualify({con, Ann, N}, X) -> qualify({qcon, Ann, [N]}, X);
|
||||||
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
|
qualify({qcon, _, NS}, {con, Ann, C}) -> {qcon, Ann, NS ++ [C]};
|
||||||
qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}.
|
qualify({qcon, _, NS}, {id, Ann, X}) -> {qid, Ann, NS ++ [X]}.
|
||||||
|
|
||||||
|
115
src/aeso_vm_decode.erl
Normal file
115
src/aeso_vm_decode.erl
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
%%% @copyright (C) 2017, Aeternity Anstalt
|
||||||
|
%%% @doc Decoding aevm and fate data to AST
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%%-------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(aeso_vm_decode).
|
||||||
|
|
||||||
|
-export([ from_aevm/3, from_fate/2 ]).
|
||||||
|
|
||||||
|
-include_lib("aebytecode/include/aeb_fate_data.hrl").
|
||||||
|
|
||||||
|
address_literal(Type, N) -> {Type, [], <<N:256>>}.
|
||||||
|
|
||||||
|
-spec from_aevm(aeb_aevm_data:type(), aeso_syntax:type(), aeb_aevm_data:data()) -> aeso_syntax:expr().
|
||||||
|
from_aevm(word, {id, _, "address"}, N) -> address_literal(account_pubkey, N);
|
||||||
|
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
|
||||||
|
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
|
||||||
|
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
|
||||||
|
from_aevm(word, {id, _, "int"}, N) -> <<N1:256/signed>> = <<N:256>>, {int, [], N1};
|
||||||
|
from_aevm(word, {id, _, "bits"}, N) -> error({todo, bits, N});
|
||||||
|
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
|
||||||
|
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
|
||||||
|
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
|
||||||
|
{bytes, [], <<Bytes:Len/unit:8>>};
|
||||||
|
from_aevm({tuple, _}, {bytes_t, _, Len}, Val) ->
|
||||||
|
{bytes, [], binary:part(<< <<W:32/unit:8>> || W <- tuple_to_list(Val) >>, 0, Len)};
|
||||||
|
from_aevm(string, {id, _, "string"}, S) -> {string, [], S};
|
||||||
|
from_aevm({list, VmType}, {app_t, _, {id, _, "list"}, [Type]}, List) ->
|
||||||
|
{list, [], [from_aevm(VmType, Type, X) || X <- List]};
|
||||||
|
from_aevm({variant, [[], [VmType]]}, {app_t, _, {id, _, "option"}, [Type]}, Val) ->
|
||||||
|
case Val of
|
||||||
|
{variant, 0, []} -> {con, [], "None"};
|
||||||
|
{variant, 1, [X]} -> {app, [], {con, [], "Some"}, [from_aevm(VmType, Type, X)]}
|
||||||
|
end;
|
||||||
|
from_aevm({tuple, VmTypes}, {tuple_t, _, Types}, Val)
|
||||||
|
when length(VmTypes) == length(Types),
|
||||||
|
length(VmTypes) == tuple_size(Val) ->
|
||||||
|
{tuple, [], [from_aevm(VmType, Type, X)
|
||||||
|
|| {VmType, Type, X} <- lists:zip3(VmTypes, Types, tuple_to_list(Val))]};
|
||||||
|
from_aevm({tuple, VmTypes}, {record_t, Fields}, Val)
|
||||||
|
when length(VmTypes) == length(Fields),
|
||||||
|
length(VmTypes) == tuple_size(Val) ->
|
||||||
|
{record, [], [ {field, [], [{proj, [], FName}], from_aevm(VmType, FType, X)}
|
||||||
|
|| {VmType, {field_t, _, FName, FType}, X} <- lists:zip3(VmTypes, Fields, tuple_to_list(Val)) ]};
|
||||||
|
from_aevm({map, VmKeyType, VmValType}, {app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
|
||||||
|
when is_map(Map) ->
|
||||||
|
{map, [], [ {from_aevm(VmKeyType, KeyType, Key),
|
||||||
|
from_aevm(VmValType, ValType, Val)}
|
||||||
|
|| {Key, Val} <- maps:to_list(Map) ]};
|
||||||
|
from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
|
||||||
|
when length(VmCons) == length(Cons),
|
||||||
|
length(VmCons) > Tag ->
|
||||||
|
VmTypes = lists:nth(Tag + 1, VmCons),
|
||||||
|
ConType = lists:nth(Tag + 1, Cons),
|
||||||
|
from_aevm(VmTypes, ConType, Args);
|
||||||
|
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
|
||||||
|
when length(VmTypes) == length(Types),
|
||||||
|
length(VmTypes) == length(Args) ->
|
||||||
|
{app, [], Con, [ from_aevm(VmType, Type, Arg)
|
||||||
|
|| {VmType, Type, Arg} <- lists:zip3(VmTypes, Types, Args) ]};
|
||||||
|
from_aevm(_VmType, _Type, _Data) ->
|
||||||
|
throw(cannot_translate_to_sophia).
|
||||||
|
|
||||||
|
|
||||||
|
-spec from_fate(aeso_syntax:type(), aeb_fate_data:fate_type()) -> aeso_syntax:expr().
|
||||||
|
from_fate({id, _, "address"}, ?FATE_ADDRESS(Bin)) -> {account_pubkey, [], Bin};
|
||||||
|
from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey, [], Bin};
|
||||||
|
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
|
||||||
|
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
|
||||||
|
from_fate({bytes_t, _, 32}, ?FATE_HASH(Bin)) -> {bytes, [], Bin};
|
||||||
|
from_fate({bytes_t, _, 64}, ?FATE_SIGNATURE(Bin)) -> {bytes, [], Bin};
|
||||||
|
from_fate({bytes_t, _, N}, Bin) when size(Bin) == N -> {bytes, [], Bin};
|
||||||
|
from_fate({id, _, "bits"}, ?FATE_BITS(Bin)) -> error({todo, bits, Bin});
|
||||||
|
from_fate({id, _, "int"}, N) when is_integer(N) -> {int, [], N};
|
||||||
|
from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B};
|
||||||
|
from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S};
|
||||||
|
from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) ->
|
||||||
|
{list, [], [from_fate(Type, X) || X <- List]};
|
||||||
|
from_fate({app_t, _, {id, _, "option"}, [Type]}, Val) ->
|
||||||
|
case Val of
|
||||||
|
{variant, [0, 1], 0, {}} -> {con, [], "None"};
|
||||||
|
{variant, [0, 1], 1, {X}} -> {app, [], {con, [], "Some"}, [from_fate(Type, X)]}
|
||||||
|
end;
|
||||||
|
from_fate({tuple_t, _, []}, ?FATE_UNIT) ->
|
||||||
|
{tuple, [], []};
|
||||||
|
from_fate({tuple_t, _, Types}, ?FATE_TUPLE(Val))
|
||||||
|
when length(Types) == tuple_size(Val) ->
|
||||||
|
{tuple, [], [from_fate(Type, X)
|
||||||
|
|| {Type, X} <- lists:zip(Types, tuple_to_list(Val))]};
|
||||||
|
from_fate({record_t, Fields}, ?FATE_TUPLE(Val))
|
||||||
|
when length(Fields) == tuple_size(Val) ->
|
||||||
|
{record, [], [ {field, [], [{proj, [], FName}], from_fate(FType, X)}
|
||||||
|
|| {{field_t, _, FName, FType}, X} <- lists:zip(Fields, tuple_to_list(Val)) ]};
|
||||||
|
from_fate({app_t, _, {id, _, "map"}, [KeyType, ValType]}, Map)
|
||||||
|
when is_map(Map) ->
|
||||||
|
{map, [], [ {from_fate(KeyType, Key),
|
||||||
|
from_fate(ValType, Val)}
|
||||||
|
|| {Key, Val} <- maps:to_list(Map) ]};
|
||||||
|
from_fate({variant_t, Cons}, {variant, Ar, Tag, Args})
|
||||||
|
when length(Cons) > Tag ->
|
||||||
|
ConType = lists:nth(Tag + 1, Cons),
|
||||||
|
Arity = lists:nth(Tag + 1, Ar),
|
||||||
|
case tuple_to_list(Args) of
|
||||||
|
ArgList when length(ArgList) == Arity ->
|
||||||
|
from_fate(ConType, ArgList);
|
||||||
|
_ -> throw(cannot_translate_to_sophia)
|
||||||
|
end;
|
||||||
|
from_fate({constr_t, _, Con, Types}, Args)
|
||||||
|
when length(Types) == length(Args) ->
|
||||||
|
{app, [], Con, [ from_fate(Type, Arg)
|
||||||
|
|| {Type, Arg} <- lists:zip(Types, Args) ]};
|
||||||
|
from_fate(_Type, _Data) ->
|
||||||
|
throw(cannot_translate_to_sophia).
|
@ -175,8 +175,10 @@ encode_decode_calldata(FunName, Types, Args, RetType) ->
|
|||||||
encode_decode_calldata_(Code, FunName, Args, RetType).
|
encode_decode_calldata_(Code, FunName, Args, RetType).
|
||||||
|
|
||||||
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
|
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
|
||||||
{ok, Calldata, CalldataType, RetVMType1} = aeso_compiler:create_calldata(Code, FunName, Args),
|
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args),
|
||||||
?assertEqual(RetVMType1, RetVMType),
|
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}]),
|
||||||
|
?assertEqual(RetType, RetVMType),
|
||||||
|
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
|
||||||
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
|
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
|
||||||
case FunName of
|
case FunName of
|
||||||
"init" ->
|
"init" ->
|
||||||
|
@ -16,18 +16,32 @@
|
|||||||
%% are made on the output, just that it is a binary which indicates
|
%% are made on the output, just that it is a binary which indicates
|
||||||
%% that the compilation worked.
|
%% that the compilation worked.
|
||||||
calldata_test_() ->
|
calldata_test_() ->
|
||||||
[ {"Testing the " ++ ContractName ++ " contract with the " ++ atom_to_list(Backend) ++ " backend",
|
[ {"Testing " ++ ContractName ++ " contract calling " ++ Fun,
|
||||||
fun() ->
|
fun() ->
|
||||||
ContractString = aeso_test_utils:read_contract(ContractName),
|
ContractString = aeso_test_utils:read_contract(ContractName),
|
||||||
Res = aeso_compiler:create_calldata(ContractString, Fun, Args, [{backend, Backend}]),
|
AevmExprs =
|
||||||
case Backend of
|
case not lists:member(ContractName, not_yet_compilable(aevm)) of
|
||||||
aevm ->
|
true -> ast_exprs(ContractString, Fun, Args, [{backend, aevm}]);
|
||||||
?assertMatch({ok, _, _, _}, Res);
|
false -> undefined
|
||||||
fate ->
|
end,
|
||||||
?assertMatch({ok, _}, Res)
|
FateExprs =
|
||||||
end
|
case not lists:member(ContractName, not_yet_compilable(fate)) of
|
||||||
end} || {ContractName, Fun, Args} <- compilable_contracts(), Backend <- [aevm, fate],
|
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
|
||||||
not lists:member(ContractName, not_yet_compilable(Backend))].
|
false -> undefined
|
||||||
|
end,
|
||||||
|
case FateExprs == undefined orelse AevmExprs == undefined of
|
||||||
|
true -> ok;
|
||||||
|
false ->
|
||||||
|
?assertEqual(FateExprs, AevmExprs)
|
||||||
|
end
|
||||||
|
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
||||||
|
|
||||||
|
|
||||||
|
ast_exprs(ContractString, Fun, Args, Opts) ->
|
||||||
|
{ok, Data} = aeso_compiler:create_calldata(ContractString, Fun, Args, Opts),
|
||||||
|
{ok, _Types, Exprs} = aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts),
|
||||||
|
?assert(is_list(Exprs)),
|
||||||
|
Exprs.
|
||||||
|
|
||||||
check_errors(Expect, ErrorString) ->
|
check_errors(Expect, ErrorString) ->
|
||||||
%% This removes the final single \n as well.
|
%% This removes the final single \n as well.
|
||||||
@ -45,7 +59,20 @@ compilable_contracts() ->
|
|||||||
[
|
[
|
||||||
{"identity", "init", []},
|
{"identity", "init", []},
|
||||||
{"maps", "init", []},
|
{"maps", "init", []},
|
||||||
{"oracles", "init", []},
|
{"funargs", "menot", ["false"]},
|
||||||
|
{"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]},
|
||||||
|
%% TODO {"funargs", "bitsum", ["Bits.all"]},
|
||||||
|
{"funargs", "read", ["{label = \"question 1\", result = 4}"]},
|
||||||
|
{"funargs", "sjutton", ["#0011012003100011012003100011012003"]},
|
||||||
|
{"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940"
|
||||||
|
"414243444546474849505152535455565758596061626364656667"]},
|
||||||
|
{"funargs", "trettiotva", ["#0102030405060708091011121314151617181920212223242526272829303132"]},
|
||||||
|
{"funargs", "find_oracle", ["ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5"]},
|
||||||
|
{"funargs", "find_query", ["oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY"]},
|
||||||
|
{"funargs", "traffic_light", ["Green"]},
|
||||||
|
{"funargs", "traffic_light", ["Pantone(12)"]},
|
||||||
|
{"funargs", "tuples", ["()"]},
|
||||||
|
%% TODO {"funargs", "due", ["FixedTTL(1020)"]},
|
||||||
{"variant_types", "init", []},
|
{"variant_types", "init", []},
|
||||||
{"basic_auth", "init", []},
|
{"basic_auth", "init", []},
|
||||||
{"address_literals", "init", []},
|
{"address_literals", "init", []},
|
||||||
@ -62,19 +89,14 @@ compilable_contracts() ->
|
|||||||
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}}"]},
|
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}}"]},
|
||||||
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}, [3] = {x = 5, y = 6}}"]},
|
{"maps", "get_i", ["1", "{[1] = {x = 3, y = 4}, [2] = {x = 4, y = 5}, [3] = {x = 5, y = 6}}"]},
|
||||||
{"strings", "str_concat", ["\"test\"","\"me\""]},
|
{"strings", "str_concat", ["\"test\"","\"me\""]},
|
||||||
{"complex_types", "filter_some", ["[Some(1), Some(2), None]"]},
|
{"complex_types", "filter_some", ["[Some(11), Some(12), None]"]},
|
||||||
{"complex_types", "init", ["ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ"]},
|
{"complex_types", "init", ["ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ"]},
|
||||||
{"__call" "init", []},
|
{"__call" "init", []},
|
||||||
{"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637"]},
|
{"bitcoin_auth", "authorize", ["1", "#0102030405060708090a0b0c0d0e0f101718192021222324252627282930313233343536373839401a1b1c1d1e1f202122232425262728293031323334353637"]},
|
||||||
{"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]}
|
{"bitcoin_auth", "to_sign", ["#0102030405060708090a0b0c0d0e0f1017181920212223242526272829303132", "2"]}
|
||||||
|
|
||||||
].
|
].
|
||||||
|
|
||||||
not_yet_compilable(fate) ->
|
not_yet_compilable(fate) ->
|
||||||
["oracles", %% Oracle.register
|
["address_chain"];
|
||||||
"events",
|
|
||||||
"address_literals", %% oracle_query_id literals
|
|
||||||
"address_chain" %% Oracle.check_query
|
|
||||||
];
|
|
||||||
not_yet_compilable(aevm) ->
|
not_yet_compilable(aevm) ->
|
||||||
["__call"].
|
["__call"].
|
||||||
|
47
test/contracts/funargs.aes
Normal file
47
test/contracts/funargs.aes
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
contract FunctionArguments =
|
||||||
|
|
||||||
|
function sum(n : int, m: int) =
|
||||||
|
n + m
|
||||||
|
|
||||||
|
function append(xs : list(string)) =
|
||||||
|
switch(xs)
|
||||||
|
[] => ""
|
||||||
|
y :: ys => String.concat(y, append(ys))
|
||||||
|
|
||||||
|
function menot(b) =
|
||||||
|
!b
|
||||||
|
|
||||||
|
function bitsum(b : bits) =
|
||||||
|
Bits.sum(b)
|
||||||
|
|
||||||
|
record answer('a) = {label : string, result : 'a}
|
||||||
|
|
||||||
|
function read(a : answer(int)) =
|
||||||
|
a.result
|
||||||
|
|
||||||
|
function sjutton(b : bytes(17)) =
|
||||||
|
b
|
||||||
|
|
||||||
|
function sextiosju(b : bytes(67)) =
|
||||||
|
b
|
||||||
|
|
||||||
|
function trettiotva(b : bytes(32)) =
|
||||||
|
b
|
||||||
|
|
||||||
|
function find_oracle(o : oracle(int, bool)) =
|
||||||
|
true
|
||||||
|
|
||||||
|
function find_query(q : oracle_query(int, bool)) =
|
||||||
|
true
|
||||||
|
|
||||||
|
datatype colour() = Green | Yellow | Red | Pantone(int)
|
||||||
|
|
||||||
|
function traffic_light(c : colour) =
|
||||||
|
Red
|
||||||
|
|
||||||
|
function tuples(t : ()) =
|
||||||
|
t
|
||||||
|
|
||||||
|
function due(t : Chain.ttl) =
|
||||||
|
true
|
Loading…
x
Reference in New Issue
Block a user