Add function to validate byte code against source code
This commit is contained in:
parent
ac58eb4259
commit
878140e03c
@ -23,6 +23,7 @@
|
|||||||
, decode_calldata/4
|
, decode_calldata/4
|
||||||
, parse/2
|
, parse/2
|
||||||
, add_include_path/2
|
, add_include_path/2
|
||||||
|
, validate_byte_code/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||||
@ -558,6 +559,87 @@ pp(Code, Options, Option, PPFun) ->
|
|||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% -- Byte code validation ---------------------------------------------------
|
||||||
|
|
||||||
|
-define(protect(Tag, Code), fun() -> try Code catch _:Err1 -> throw({Tag, Err1}) end end()).
|
||||||
|
|
||||||
|
-spec validate_byte_code(map(), string(), options()) -> ok | {error, [aeso_errors:error()]}.
|
||||||
|
validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) ->
|
||||||
|
Fail = fun(Err) -> {error, [aeso_errors:new(data_error, Err)]} end,
|
||||||
|
case proplists:get_value(backend, Options, aevm) of
|
||||||
|
B when B /= fate -> Fail(io_lib:format("Unsupported backend: ~s\n", [B]));
|
||||||
|
fate ->
|
||||||
|
try
|
||||||
|
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
|
||||||
|
{FCode2, SrcPayable} =
|
||||||
|
?protect(compile,
|
||||||
|
begin
|
||||||
|
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
|
||||||
|
from_string1(fate, Source, Options),
|
||||||
|
FCode = aeb_fate_code:deserialize(SrcByteCode),
|
||||||
|
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
|
||||||
|
end),
|
||||||
|
case compare_fate_code(FCode1, FCode2) of
|
||||||
|
ok when SrcPayable /= Payable ->
|
||||||
|
Not = fun(true) -> ""; (false) -> " not" end,
|
||||||
|
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
|
||||||
|
[Not(Payable), Not(SrcPayable)]));
|
||||||
|
ok -> ok;
|
||||||
|
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
throw:{deserialize, _} -> Fail("Invalid byte code");
|
||||||
|
throw:{compile, {error, Errs}} -> {error, Errs}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
compare_fate_code(FCode1, FCode2) ->
|
||||||
|
Funs1 = aeb_fate_code:functions(FCode1),
|
||||||
|
Funs2 = aeb_fate_code:functions(FCode2),
|
||||||
|
Syms1 = aeb_fate_code:symbols(FCode1),
|
||||||
|
Syms2 = aeb_fate_code:symbols(FCode2),
|
||||||
|
FunHashes1 = maps:keys(Funs1),
|
||||||
|
FunHashes2 = maps:keys(Funs2),
|
||||||
|
case FunHashes1 == FunHashes2 of
|
||||||
|
false ->
|
||||||
|
InByteCode = [ binary_to_list(maps:get(H, Syms1)) || H <- FunHashes1 -- FunHashes2 ],
|
||||||
|
InSourceCode = [ binary_to_list(maps:get(H, Syms2)) || H <- FunHashes2 -- FunHashes1 ],
|
||||||
|
Msg = [ io_lib:format("- Functions in the byte code but not in the source code:\n"
|
||||||
|
" ~s\n", [string:join(InByteCode, ", ")]) || InByteCode /= [] ] ++
|
||||||
|
[ io_lib:format("- Functions in the source code but not in the byte code:\n"
|
||||||
|
" ~s\n", [string:join(InSourceCode, ", ")]) || InSourceCode /= [] ],
|
||||||
|
{error, Msg};
|
||||||
|
true ->
|
||||||
|
case lists:append([ compare_fate_fun(maps:get(H, Syms1), Fun1, Fun2)
|
||||||
|
|| {{H, Fun1}, {_, Fun2}} <- lists:zip(maps:to_list(Funs1),
|
||||||
|
maps:to_list(Funs2)) ]) of
|
||||||
|
[] -> ok;
|
||||||
|
Errs -> {error, Errs}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
compare_fate_fun(_Name, Fun, Fun) -> [];
|
||||||
|
compare_fate_fun(Name, {Attr, Type, _}, {Attr, Type, _}) ->
|
||||||
|
[io_lib:format("- The implementation of the function ~s is different.\n", [Name])];
|
||||||
|
compare_fate_fun(Name, {Attr1, Type, _}, {Attr2, Type, _}) ->
|
||||||
|
[io_lib:format("- The attributes of the function ~s differ:\n"
|
||||||
|
" Byte code: ~s\n"
|
||||||
|
" Source code: ~s\n",
|
||||||
|
[Name, string:join([ atom_to_list(A) || A <- Attr1 ], ", "),
|
||||||
|
string:join([ atom_to_list(A) || A <- Attr2 ], ", ")])];
|
||||||
|
compare_fate_fun(Name, {_, Type1, _}, {_, Type2, _}) ->
|
||||||
|
[io_lib:format("- The type of the function ~s differs:\n"
|
||||||
|
" Byte code: ~s\n"
|
||||||
|
" Source code: ~s\n",
|
||||||
|
[Name, pp_fate_sig(Type1), pp_fate_sig(Type2)])].
|
||||||
|
|
||||||
|
pp_fate_sig({[Arg], Res}) ->
|
||||||
|
io_lib:format("~s => ~s", [pp_fate_type(Arg), pp_fate_type(Res)]);
|
||||||
|
pp_fate_sig({Args, Res}) ->
|
||||||
|
io_lib:format("(~s) => ~s", [string:join([pp_fate_type(Arg) || Arg <- Args], ", "), pp_fate_type(Res)]).
|
||||||
|
|
||||||
|
pp_fate_type(T) -> io_lib:format("~w", [T]).
|
||||||
|
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
|
||||||
sophia_type_to_typerep(String) ->
|
sophia_type_to_typerep(String) ->
|
||||||
|
@ -12,6 +12,14 @@
|
|||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
run_test(Test) ->
|
||||||
|
TestFun = list_to_atom(lists:concat([Test, "_test_"])),
|
||||||
|
[ begin
|
||||||
|
io:format("~s\n", [Label]),
|
||||||
|
Fun()
|
||||||
|
end || {Label, Fun} <- ?MODULE:TestFun() ],
|
||||||
|
ok.
|
||||||
|
|
||||||
%% Very simply test compile the given contracts. Only basic checks
|
%% Very simply test compile the given contracts. Only basic checks
|
||||||
%% 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.
|
||||||
@ -702,3 +710,44 @@ failing_code_gen_contracts() ->
|
|||||||
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
|
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
|
||||||
].
|
].
|
||||||
|
|
||||||
|
validation_test_() ->
|
||||||
|
[{"Validation fail: " ++ C1 ++ " /= " ++ C2,
|
||||||
|
fun() ->
|
||||||
|
Actual = case validate(C1, C2) of
|
||||||
|
{error, Errs} -> Errs;
|
||||||
|
ok -> #{}
|
||||||
|
end,
|
||||||
|
check_errors(Expect, Actual)
|
||||||
|
end} || {C1, C2, Expect} <- validation_fails()] ++
|
||||||
|
[{"Validation of " ++ C,
|
||||||
|
fun() ->
|
||||||
|
?assertEqual(ok, validate(C, C))
|
||||||
|
end} || C <- compilable_contracts()].
|
||||||
|
|
||||||
|
validation_fails() ->
|
||||||
|
[{"deadcode", "nodeadcode",
|
||||||
|
[<<"Data error:\n"
|
||||||
|
"Byte code does not match source code.\n"
|
||||||
|
"- Functions in the source code but not in the byte code:\n"
|
||||||
|
" .MyList.map2">>]},
|
||||||
|
{"validation_test1", "validation_test2",
|
||||||
|
[<<"Data error:\n"
|
||||||
|
"Byte code does not match source code.\n"
|
||||||
|
"- The implementation of the function code_fail is different.\n"
|
||||||
|
"- The attributes of the function attr_fail differ:\n"
|
||||||
|
" Byte code: payable\n"
|
||||||
|
" Source code: \n"
|
||||||
|
"- The type of the function type_fail differs:\n"
|
||||||
|
" Byte code: integer => integer\n"
|
||||||
|
" Source code: {tvar,0} => {tvar,0}">>]},
|
||||||
|
{"validation_test1", "validation_test3",
|
||||||
|
[<<"Data error:\n"
|
||||||
|
"Byte code contract is not payable, but source code contract is.">>]}].
|
||||||
|
|
||||||
|
validate(Contract1, Contract2) ->
|
||||||
|
ByteCode = #{ fate_code := FCode } = compile(fate, Contract1),
|
||||||
|
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
|
||||||
|
Source = aeso_test_utils:read_contract(Contract2),
|
||||||
|
aeso_compiler:validate_byte_code(ByteCode#{ byte_code := FCode1 }, Source,
|
||||||
|
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).
|
||||||
|
|
||||||
|
4
test/contracts/validation_test1.aes
Normal file
4
test/contracts/validation_test1.aes
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
contract ValidationTest =
|
||||||
|
payable entrypoint attr_fail() = ()
|
||||||
|
entrypoint type_fail(x : int) = x
|
||||||
|
entrypoint code_fail(x) = x + 1
|
4
test/contracts/validation_test2.aes
Normal file
4
test/contracts/validation_test2.aes
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
contract ValidationTest =
|
||||||
|
entrypoint attr_fail() = ()
|
||||||
|
entrypoint type_fail(x) = x
|
||||||
|
entrypoint code_fail(x) = x - 1
|
4
test/contracts/validation_test3.aes
Normal file
4
test/contracts/validation_test3.aes
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
payable contract ValidationTest =
|
||||||
|
payable entrypoint attr_fail() = ()
|
||||||
|
entrypoint type_fail(x : int) = x
|
||||||
|
entrypoint code_fail(x) = x + 1
|
Loading…
x
Reference in New Issue
Block a user