sophia/test/aeso_test_utils.erl
2018-12-22 01:23:40 +01:00

161 lines
5.0 KiB
Erlang

%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2017, Aeternity Anstalt
%%% @doc Test utilities for the Sophia language tests.
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(aeso_test_utils).
-include("apps/aecontract/src/aecontract.hrl").
-export([read_contract/1, contract_path/0, pp/1, pp/2,
dump_words/1, show_heap/1, show_heap/2, show_heap_value/1, compile/1]).
-export([spend/3, get_balance/2, call_contract/6, get_store/1, set_store/2,
aens_lookup/4]).
contract_path() ->
{ok, Cwd} = file:get_cwd(),
N = length(filename:split(Cwd)),
Rel = ["apps", "aesophia", "test", "contracts"],
%% Try the first matching directory (../)*Rel
Cand = fun(I) -> filename:join(lists:duplicate(I, "..") ++ Rel) end,
case [ Dir || Dir <- lists:map(Cand, lists:seq(0, N)), filelib:is_dir(Dir) ] of
[Dir | _] -> Dir;
[] -> error(failed_to_find_contract_dir)
end.
%% Read a contract file from the test/contracts directory.
-spec read_contract(string() | atom()) -> string().
read_contract(Name) ->
{ok, Bin} = file:read_file(filename:join(contract_path(), lists:concat([Name, ".aes"]))),
binary_to_list(Bin).
pp(Name) -> pp(Name, []).
pp(Name, Options) ->
case aeso_parser:string(read_contract(Name)) of
{ok, AST} ->
[ io:format("~s\n", [prettypr:format(aeso_pretty:decls(AST))]) || not lists:member(quiet, Options) ];
{error, {{L, C}, parse_error, Err}} ->
io:format("Parse error at ~p:~p:~p\n~s\n", [Name, L, C, Err])
end.
compile(Name) ->
aeso_compiler:from_string(read_contract(Name),
[pp_sophia_code, pp_typed_ast, pp_icode]).
%% Stack simulator
simulate([],Stack) ->
Stack;
simulate(['PUSH1',X|More],S) ->
simulate(More,[X|S]);
simulate([Op|More],Stack) ->
simulate(More,simulate(Op,Stack));
simulate('MSIZE',S) ->
A = new_atom(),
io:format("~p = MSIZE\n",[A]),
[A|S];
simulate('DUP2',[A,B|S]) ->
[B,A,B|S];
simulate('DUP3',[A,B,C|S]) ->
[C,A,B,C|S];
simulate('ADD',[A,B|S]) ->
[add(A,B)|S];
simulate('MSTORE',[Addr,X|S]) ->
io:format("mem(~p) <- ~p\n",[Addr,X]),
S;
simulate('MLOAD',[Addr|S]) ->
A = new_atom(),
io:format("~p = mem(~p)\n",[A,Addr]),
[A|S];
simulate('SWAP1',[A,B|S]) ->
[B,A|S];
simulate('SWAP2',[A,B,C|S]) ->
[C,B,A|S];
simulate('SUB',[A,B|S]) ->
[{A,'-',B}|S];
simulate('POP',[_|S]) ->
S.
add(0,X) ->
X;
add(X,0) ->
X;
add(X,{A,'-',X}) ->
A;
add(X,{A,'+',B}) ->
{A,'+',add(X,B)};
add(A,B) ->
{A,'+',B}.
new_atom() ->
catch ets:new(names,[set,public,named_table]),
case ets:lookup(names,index) of
[] -> I = 0;
[{index,I}] -> ok
end,
ets:insert(names,{index,I+1}),
list_to_atom([$a+I]).
show_heap(Bin) ->
show_heap(0, Bin).
show_heap(Offs, Bin) ->
Words = dump_words(Bin),
Addrs = lists:seq(0, (length(Words) - 1) * 32, 32),
lists:flatten([io_lib:format("~4b ~p\n", [Addr + Offs, Word]) || {Addr, Word} <- lists:zip(Addrs, Words)]).
show_heap_value(HeapValue) ->
Maps = aeso_heap:heap_value_maps(HeapValue),
Offs = aeso_heap:heap_value_offset(HeapValue),
Ptr = aeso_heap:heap_value_pointer(HeapValue),
Mem = aeso_heap:heap_value_heap(HeapValue),
Words = dump_words(Mem),
Addrs = lists:seq(Offs, Offs + (length(Words) - 1) * 32, 32),
lists:flatten(
io_lib:format(" Maps: ~p\n Ptr: ~p\n Heap: ~p",
[Maps, Ptr, lists:zip(Addrs, Words)])).
%% Translate a blob of 256-bit words into readable form. Does a bit of guessing
%% to recover strings. TODO: strings longer than 32 bytes
dump_words(Bin) -> dump_words(Bin, []).
dump_words(<<N:256, W:32/binary, Rest/binary>>, Acc) when N < 32 ->
NotN = (32 - N) * 8,
case W of
<<S:N/binary, 0:NotN>> ->
Str = binary_to_list(S),
case lists:member(0, Str) of
true -> dump_words(<<W/binary, Rest/binary>>, [N | Acc]); %% Not a string
false -> dump_words(Rest, [binary_to_list(S), N | Acc])
end;
_ -> dump_words(<<W/binary, Rest/binary>>, [N | Acc])
end;
dump_words(<<N:256/signed, Rest/binary>>, Acc) ->
dump_words(Rest, [N | Acc]);
dump_words(<<>>, Acc) -> lists:reverse(Acc);
dump_words(Rest, Acc) -> lists:reverse([{error, Rest} | Acc]).
%% -- Chain API for test -----------------------------------------------------
aens_lookup(Name, Key, Type, _S) ->
io:format("aens_lookup(~p, ~p, ~p)\n", [Name, Key, Type]),
{ok, {some, <<0:32/unit:8>>}}.
spend(Recipient, Amount, S) ->
io:format("+++ SPEND(~p, ~p)\n", [Recipient, Amount]),
{ok, S}.
get_balance(_, _) -> 1000000.
call_contract(Contract, Gas, Value, CallData, CallStack, S) ->
io:format("+++ CALL(~p, ~p, ~p, ~p, ~p)\n", [Contract, Gas, Value, CallData, CallStack]),
{ok, <<42:256>>, S}.
get_store(_) -> #{}.
set_store(_, _) -> ok.