diff --git a/include/aeser_contract_code.hrl b/include/aeser_contract_code.hrl new file mode 100644 index 0000000..9c7b45c --- /dev/null +++ b/include/aeser_contract_code.hrl @@ -0,0 +1,3 @@ +-define(SOPHIA_CONTRACT_VSN_1, 1). +-define(SOPHIA_CONTRACT_VSN_2, 2). +-define(SOPHIA_CONTRACT_VSN_3, 3). diff --git a/rebar.config b/rebar.config index d43c4df..b388e03 100644 --- a/rebar.config +++ b/rebar.config @@ -1,3 +1,4 @@ {erl_opts, [debug_info]}. -{deps, [{base58, {git, "https://github.com/aeternity/erl-base58.git", {ref,"60a3356"}}} +{deps, [ {base58, {git, "https://github.com/aeternity/erl-base58.git", {ref, "60a3356"}}} + , {enacl, {git, "https://github.com/aeternity/enacl.git", {ref, "26180f4"}}} ]}. diff --git a/src/aeser_contract_code.erl b/src/aeser_contract_code.erl new file mode 100644 index 0000000..dab6b17 --- /dev/null +++ b/src/aeser_contract_code.erl @@ -0,0 +1,114 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2019, Aeternity Anstalt +%%% @doc +%%% Serialization of contract code +%%% @end +%%%------------------------------------------------------------------- +-module(aeser_contract_code). + +-include("aeser_contract_code.hrl"). + +-export([ deserialize/1 + , serialize/1 + , serialize/2 ]). + +-spec serialize(map()) -> binary(). +serialize(CodeMap) -> + serialize(CodeMap, ?SOPHIA_CONTRACT_VSN_3). + +-spec serialize(map(), non_neg_integer()) -> binary(). +serialize(CodeMap = #{ byte_code := ByteCode + , type_info := TypeInfo }, SophiaContractVersion) -> + %% Source hash + SourceHash = case CodeMap of + #{ source_hash := SHash } -> SHash; + #{ contract_source := SrcStr } -> + {ok, SHash} = enacl:generichash(32, list_to_binary(SrcStr)), + SHash + end, + + %% Compiler version + Version = maps:get(compiler_version, CodeMap, <<"unknown">>), + BinVersion = if is_integer(Version) -> integer_to_binary(Version); + is_binary(Version) -> Version + end, + + %% Payable + Payable = maps:get(payable, CodeMap, true), + + Fields = [ {source_hash, SourceHash} + , {type_info, TypeInfo} + , {byte_code, ByteCode} ] ++ + [ {compiler_version, BinVersion} + || SophiaContractVersion > ?SOPHIA_CONTRACT_VSN_1 ] ++ + [ {payable, Payable} + || SophiaContractVersion > ?SOPHIA_CONTRACT_VSN_2 ], + aeser_chain_objects:serialize(compiler_sophia, + SophiaContractVersion, + serialization_template(SophiaContractVersion), + Fields). + +-spec deserialize(binary()) -> map(). +deserialize(Binary) -> + case aeser_chain_objects:deserialize_type_and_vsn(Binary) of + {compiler_sophia = Type, ?SOPHIA_CONTRACT_VSN_1 = Vsn, _Rest} -> + Template = serialization_template(Vsn), + [ {source_hash, Hash} + , {type_info, TypeInfo} + , {byte_code, ByteCode} + ] = aeser_chain_objects:deserialize(Type, Vsn, Template, Binary), + #{ source_hash => Hash + , type_info => TypeInfo + , byte_code => ByteCode + , contract_vsn => Vsn + , payable => true + }; + {compiler_sophia = Type, ?SOPHIA_CONTRACT_VSN_2 = Vsn, _Rest} -> + Template = serialization_template(Vsn), + [ {source_hash, Hash} + , {type_info, TypeInfo} + , {byte_code, ByteCode} + , {compiler_version, CompilerVersion} + ] = aeser_chain_objects:deserialize(Type, Vsn, Template, Binary), + #{ source_hash => Hash + , type_info => TypeInfo + , byte_code => ByteCode + , compiler_version => CompilerVersion + , contract_vsn => Vsn + , payable => true + }; + {compiler_sophia = Type, ?SOPHIA_CONTRACT_VSN_3 = Vsn, _Rest} -> + Template = serialization_template(Vsn), + [ {source_hash, Hash} + , {type_info, TypeInfo} + , {byte_code, ByteCode} + , {compiler_version, CompilerVersion} + , {payable, Payable} + ] = aeser_chain_objects:deserialize(Type, Vsn, Template, Binary), + #{ source_hash => Hash + , type_info => TypeInfo + , byte_code => ByteCode + , compiler_version => CompilerVersion + , contract_vsn => Vsn + , payable => Payable + }; + Other -> + error({illegal_code_object, Other}) + end. + +serialization_template(?SOPHIA_CONTRACT_VSN_1) -> + [ {source_hash, binary} + , {type_info, [{binary, binary, binary, binary}]} %% {type hash, name, arg type, out type} + , {byte_code, binary} ]; +serialization_template(?SOPHIA_CONTRACT_VSN_2) -> + [ {source_hash, binary} + , {type_info, [{binary, binary, binary, binary}]} %% {type hash, name, arg type, out type} + , {byte_code, binary} + , {compiler_version, binary} ]; +serialization_template(?SOPHIA_CONTRACT_VSN_3) -> + [ {source_hash, binary} + , {type_info, [{binary, binary, bool, binary, binary}]} %% {type hash, name, payable, arg type, out type} + , {byte_code, binary} + , {compiler_version, binary} + , {payable, bool} ]. + diff --git a/test/aeser_contract_code_tests.erl b/test/aeser_contract_code_tests.erl new file mode 100644 index 0000000..e6a21fe --- /dev/null +++ b/test/aeser_contract_code_tests.erl @@ -0,0 +1,35 @@ +-module(aeser_contract_code_tests). + +-include_lib("eunit/include/eunit.hrl"). +-include("aeser_contract_code.hrl"). + +-define(DUMMY_CODE_MAP_1, + #{ byte_code => <<"DUMMY CODE">> + , type_info => [{<<>>, <<>>, <<>>, <<>>}] + , contract_source => "contract Foo = ..." }). + +-define(DUMMY_CODE_MAP_2, + #{ byte_code => <<"DUMMY CODE">> + , type_info => [{<<>>, <<>>, <<>>, <<>>}] + , compiler_version => <<"3.1.4">> + , source_hash => <<1, 2, 3, 4>> }). + +-define(DUMMY_CODE_MAP_3, + #{ byte_code => <<"DUMMY CODE">> + , type_info => [{<<>>, <<>>, false, <<>>, <<>>}] + , compiler_version => <<"3.1.4">> + , contract_source => "contract Foo = ..." + , payable => true} ). + +vsn_1_test() -> + aeser_contract_code:deserialize( + aeser_contract_code:serialize(?DUMMY_CODE_MAP_1, ?SOPHIA_CONTRACT_VSN_1)). + +vsn_2_test() -> + aeser_contract_code:deserialize( + aeser_contract_code:serialize(?DUMMY_CODE_MAP_2, ?SOPHIA_CONTRACT_VSN_2)). + +vsn_3_test() -> + aeser_contract_code:deserialize( + aeser_contract_code:serialize(?DUMMY_CODE_MAP_3, ?SOPHIA_CONTRACT_VSN_3)). +