161 lines
5.0 KiB
Erlang
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.
|