
Add raw error message for 2 errors The errors: `unnamed_map_update_with_default` and `unbound_variable`. Revert "Add raw error message for 2 errors" This reverts commit 0db6d16140d52e29cd8a12d8719ee57ca874d5f5. Remove trailing new lines and at POS from error messages Convert multiple line error messages into single line error messages Remove at POS from pp_why_record context Change error message with new line before code Fix tests for changed error messages Fix the rest of the error messages Add new line after error message Remove new line from the end of data error messages
257 lines
11 KiB
Erlang
257 lines
11 KiB
Erlang
-module(aeso_abi_tests).
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
-compile([export_all, nowarn_export_all]).
|
|
|
|
-define(SANDBOX(Code), sandbox(fun() -> Code end)).
|
|
-define(DUMMY_HASH_WORD, 16#123).
|
|
-define(DUMMY_HASH, <<0:30/unit:8, 127, 119>>). %% 16#123
|
|
-define(DUMMY_HASH_LIT, "#0000000000000000000000000000000000000000000000000000000000000123").
|
|
|
|
sandbox(Code) ->
|
|
Parent = self(),
|
|
Tag = make_ref(),
|
|
{Pid, Ref} = spawn_monitor(fun() -> Parent ! {Tag, Code()} end),
|
|
receive
|
|
{Tag, Res} -> erlang:demonitor(Ref, [flush]), {ok, Res};
|
|
{'DOWN', Ref, process, Pid, Reason} -> {error, Reason}
|
|
after 100 ->
|
|
exit(Pid, kill),
|
|
{error, loop}
|
|
end.
|
|
|
|
malicious_from_binary_test() ->
|
|
CircularList = from_words([32, 1, 32]), %% Xs = 1 :: Xs
|
|
{ok, {error, circular_references}} = ?SANDBOX(aeb_heap:from_binary({list, word}, CircularList)),
|
|
{ok, {error, {binary_too_short, _}}} = ?SANDBOX(aeb_heap:from_binary(word, <<1, 2, 3, 4>>)),
|
|
ok.
|
|
|
|
from_words(Ws) ->
|
|
<< <<(from_word(W))/binary>> || W <- Ws >>.
|
|
|
|
from_word(W) when is_integer(W) ->
|
|
<<W:256>>;
|
|
from_word(S) when is_list(S) ->
|
|
Len = length(S),
|
|
Bin = <<(list_to_binary(S))/binary, 0:(32 - Len)/unit:8>>,
|
|
<<Len:256, Bin/binary>>.
|
|
|
|
encode_decode_test() ->
|
|
encode_decode(word, 42),
|
|
42 = encode_decode(word, 42),
|
|
-1 = encode_decode(signed_word, -1),
|
|
<<"Hello world">> = encode_decode(string, <<"Hello world">>),
|
|
{} = encode_decode({tuple, []}, {}),
|
|
{42} = encode_decode({tuple, [word]}, {42}),
|
|
{42, 0} = encode_decode({tuple, [word, word]}, {42, 0}),
|
|
[] = encode_decode({list, word}, []),
|
|
[32] = encode_decode({list, word}, [32]),
|
|
none = encode_decode({option, word}, none),
|
|
{some, 1} = encode_decode({option, word}, {some, 1}),
|
|
string = encode_decode(typerep, string),
|
|
word = encode_decode(typerep, word),
|
|
{list, word} = encode_decode(typerep, {list, word}),
|
|
{tuple, [word]} = encode_decode(typerep, {tuple, [word]}),
|
|
1 = encode_decode(word, 1),
|
|
0 = encode_decode(word, 0),
|
|
ok.
|
|
|
|
encode_decode_sophia_test() ->
|
|
Check = fun(Type, Str) -> case {encode_decode_sophia_string(Type, Str), Str} of
|
|
{X, X} -> ok;
|
|
Other -> Other
|
|
end end,
|
|
ok = Check("int", "42"),
|
|
ok = Check("int", "- 42"),
|
|
ok = Check("bool", "true"),
|
|
ok = Check("bool", "false"),
|
|
ok = Check("string", "\"Hello\""),
|
|
ok = Check("string * list(int) * option(bool)",
|
|
"(\"Hello\", [1, 2, 3], Some(true))"),
|
|
ok = Check("variant", "Blue({[\"x\"] = 1})"),
|
|
ok = Check("r", "{x = (\"foo\", 0), y = Red}"),
|
|
ok.
|
|
|
|
to_sophia_value_neg_test() ->
|
|
Code = [ "contract Foo =\n"
|
|
" entrypoint x(y : int) : string = \"hello\"\n" ],
|
|
|
|
{error, [Err1]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12)),
|
|
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err1)),
|
|
{error, [Err2]} = aeso_compiler:to_sophia_value(Code, "x", ok, encode(12), [{backend, fate}]),
|
|
?assertEqual("Data error:\nFailed to decode binary as type string\n", aeso_errors:pp(Err2)),
|
|
|
|
{error, [Err3]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12)),
|
|
?assertEqual("Data error:\nCould not interpret the revert message\n", aeso_errors:pp(Err3)),
|
|
{error, [Err4]} = aeso_compiler:to_sophia_value(Code, "x", revert, encode(12), [{backend, fate}]),
|
|
?assertEqual("Data error:\nCould not deserialize the revert message\n", aeso_errors:pp(Err4)),
|
|
ok.
|
|
|
|
encode_calldata_neg_test() ->
|
|
Code = [ "contract Foo =\n"
|
|
" entrypoint x(y : int) : string = \"hello\"\n" ],
|
|
|
|
ExpErr1 = "Type error at line 5, col 34:\nCannot unify `int` and `bool`\n"
|
|
"when checking the application of\n"
|
|
" `x : (int) => string`\n"
|
|
"to arguments\n"
|
|
" `true : bool`\n",
|
|
{error, [Err1]} = aeso_compiler:create_calldata(Code, "x", ["true"]),
|
|
?assertEqual(ExpErr1, aeso_errors:pp(Err1)),
|
|
{error, [Err2]} = aeso_compiler:create_calldata(Code, "x", ["true"], [{backend, fate}]),
|
|
?assertEqual(ExpErr1, aeso_errors:pp(Err2)),
|
|
|
|
ok.
|
|
|
|
decode_calldata_neg_test() ->
|
|
Code1 = [ "contract Foo =\n"
|
|
" entrypoint x(y : int) : string = \"hello\"\n" ],
|
|
Code2 = [ "contract Foo =\n"
|
|
" entrypoint x(y : string) : int = 42\n" ],
|
|
|
|
{ok, CallDataAEVM} = aeso_compiler:create_calldata(Code1, "x", ["42"]),
|
|
{ok, CallDataFATE} = aeso_compiler:create_calldata(Code1, "x", ["42"], [{backend, fate}]),
|
|
|
|
{error, [Err1]} = aeso_compiler:decode_calldata(Code2, "x", CallDataAEVM),
|
|
?assertEqual("Data error:\nFailed to decode calldata as type {tuple,[string]}\n", aeso_errors:pp(Err1)),
|
|
{error, [Err2]} = aeso_compiler:decode_calldata(Code2, "x", <<1,2,3>>, [{backend, fate}]),
|
|
?assertEqual("Data error:\nFailed to decode calldata binary\n", aeso_errors:pp(Err2)),
|
|
{error, [Err3]} = aeso_compiler:decode_calldata(Code2, "x", CallDataFATE, [{backend, fate}]),
|
|
?assertEqual("Data error:\nCannot translate FATE value \"*\"\n to Sophia type (string)\n", aeso_errors:pp(Err3)),
|
|
|
|
{error, [Err4]} = aeso_compiler:decode_calldata(Code2, "y", CallDataAEVM),
|
|
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err4)),
|
|
{error, [Err5]} = aeso_compiler:decode_calldata(Code2, "y", CallDataFATE, [{backend, fate}]),
|
|
?assertEqual("Data error at line 1, col 1:\nFunction 'y' is missing in contract\n", aeso_errors:pp(Err5)),
|
|
ok.
|
|
|
|
|
|
encode_decode_sophia_string(SophiaType, String) ->
|
|
io:format("String ~p~n", [String]),
|
|
Code = [ "contract MakeCall =\n"
|
|
, " type arg_type = ", SophiaType, "\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"
|
|
, " entrypoint foo : arg_type => arg_type\n" ],
|
|
case aeso_compiler:check_call(lists:flatten(Code), "foo", [String], [no_code]) of
|
|
{ok, _, {[Type], _}, [Arg]} ->
|
|
io:format("Type ~p~n", [Type]),
|
|
Data = encode(Arg),
|
|
case aeso_compiler:to_sophia_value(Code, "foo", ok, Data, [no_code]) of
|
|
{ok, Sophia} ->
|
|
lists:flatten(io_lib:format("~s", [prettypr:format(aeso_pretty:expr(Sophia))]));
|
|
{error, Err} ->
|
|
io:format("~s\n", [Err]),
|
|
{error, Err}
|
|
end;
|
|
{error, Err} ->
|
|
io:format("~s\n", [Err]),
|
|
{error, Err}
|
|
end.
|
|
|
|
calldata_test() ->
|
|
[42, <<"foobar">>] = encode_decode_calldata("foo", ["int", "string"], ["42", "\"foobar\""]),
|
|
Map = #{ <<"a">> => 4 },
|
|
[{variant, 1, [Map]}, {{<<"b">>, 5}, {variant, 0, []}}] =
|
|
encode_decode_calldata("foo", ["variant", "r"], ["Blue({[\"a\"] = 4})", "{x = (\"b\", 5), y = Red}"]),
|
|
[?DUMMY_HASH_WORD, 16#456] = encode_decode_calldata("foo", ["bytes(32)", "address"],
|
|
[?DUMMY_HASH_LIT, "ak_1111111111111111111111111111113AFEFpt5"]),
|
|
[?DUMMY_HASH_WORD, ?DUMMY_HASH_WORD] =
|
|
encode_decode_calldata("foo", ["bytes(32)", "hash"], [?DUMMY_HASH_LIT, ?DUMMY_HASH_LIT]),
|
|
|
|
[119, {0, 0}] = encode_decode_calldata("foo", ["int", "signature"], ["119", [$# | lists:duplicate(128, $0)]]),
|
|
|
|
[16#456] = encode_decode_calldata("foo", ["Remote"], ["ct_1111111111111111111111111111113AFEFpt5"]),
|
|
|
|
ok.
|
|
|
|
calldata_init_test() ->
|
|
encode_decode_calldata("init", ["int"], ["42"], {tuple, [typerep, word]}),
|
|
|
|
Code = parameterized_contract("foo", ["int"]),
|
|
encode_decode_calldata_(Code, "init", [], {tuple, [typerep, {tuple, []}]}).
|
|
|
|
calldata_indent_test() ->
|
|
Test = fun(Extra) ->
|
|
Code = parameterized_contract(Extra, "foo", ["int"]),
|
|
encode_decode_calldata_(Code, "foo", ["42"], word)
|
|
end,
|
|
Test(" stateful entrypoint bla() = ()"),
|
|
Test(" type x = int"),
|
|
Test(" stateful entrypoint bla(x : int) =\n"
|
|
" x + 1"),
|
|
Test(" stateful entrypoint bla(x : int) : int =\n"
|
|
" x + 1"),
|
|
ok.
|
|
|
|
parameterized_contract(FunName, Types) ->
|
|
parameterized_contract([], FunName, Types).
|
|
|
|
parameterized_contract(ExtraCode, FunName, Types) ->
|
|
lists:flatten(
|
|
["contract Remote =\n"
|
|
" entrypoint bla : () => unit\n\n"
|
|
"main 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"
|
|
" entrypoint ", FunName, " : (", string:join(Types, ", "), ") => int\n" ]).
|
|
|
|
oracle_test() ->
|
|
Contract =
|
|
"contract OracleTest =\n"
|
|
" entrypoint 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", ["ok_111111111111111111111111111111ZrdqRz9",
|
|
"oq_1111111111111111111111111111113AFEFpt5"], [no_code]),
|
|
|
|
ok.
|
|
|
|
permissive_literals_fail_test() ->
|
|
Contract =
|
|
"contract OracleTest =\n"
|
|
" stateful entrypoint haxx(o : oracle(list(string), option(int))) =\n"
|
|
" Chain.spend(o, 1000000)\n",
|
|
{error, [Err]} =
|
|
aeso_compiler:check_call(Contract, "haxx", ["#123"], []),
|
|
?assertMatch("Type error at line 3, col 5:\nCannot unify" ++ _, aeso_errors:pp(Err)),
|
|
?assertEqual(type_error, aeso_errors:type(Err)),
|
|
ok.
|
|
|
|
encode_decode_calldata(FunName, Types, Args) ->
|
|
encode_decode_calldata(FunName, Types, Args, word).
|
|
|
|
encode_decode_calldata(FunName, Types, Args, RetType) ->
|
|
Code = parameterized_contract(FunName, Types),
|
|
encode_decode_calldata_(Code, FunName, Args, RetType).
|
|
|
|
encode_decode_calldata_(Code, FunName, Args, RetVMType) ->
|
|
{ok, Calldata} = aeso_compiler:create_calldata(Code, FunName, Args, []),
|
|
{ok, _, {ArgTypes, RetType}, _} = aeso_compiler:check_call(Code, FunName, Args, [{backend, aevm}, no_code]),
|
|
?assertEqual(RetType, RetVMType),
|
|
CalldataType = {tuple, [word, {tuple, ArgTypes}]},
|
|
{ok, {_Hash, ArgTuple}} = aeb_heap:from_binary(CalldataType, Calldata),
|
|
case FunName of
|
|
"init" ->
|
|
ok;
|
|
_ ->
|
|
{ok, _ArgTypes, ValueASTs} = aeso_compiler:decode_calldata(Code, FunName, Calldata, []),
|
|
Values = [ prettypr:format(aeso_pretty:expr(V)) || V <- ValueASTs ],
|
|
?assertMatch({X, X}, {Args, Values})
|
|
end,
|
|
tuple_to_list(ArgTuple).
|
|
|
|
encode_decode(T, D) ->
|
|
?assertEqual(D, decode(T, encode(D))),
|
|
D.
|
|
|
|
encode(D) ->
|
|
aeb_heap:to_binary(D).
|
|
|
|
decode(T,B) ->
|
|
{ok, D} = aeb_heap:from_binary(T, B),
|
|
D.
|