Merge branch 'master' into eureka
This commit is contained in:
commit
b487f98d9e
@ -3,6 +3,6 @@
|
|||||||
{included_applications,[]},
|
{included_applications,[]},
|
||||||
{applications,[stdlib,kernel]},
|
{applications,[stdlib,kernel]},
|
||||||
{description,"Gajumaru interoperation library"},
|
{description,"Gajumaru interoperation library"},
|
||||||
{vsn,"0.2.0"},
|
{vsn,"0.4.0"},
|
||||||
{modules,[hakuzaru,hz,hz_fetcher,hz_man,hz_sup]},
|
{modules,[hakuzaru,hz,hz_fetcher,hz_man,hz_sup]},
|
||||||
{mod,{hakuzaru,[]}}]}.
|
{mod,{hakuzaru,[]}}]}.
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
%%% @end
|
%%% @end
|
||||||
|
|
||||||
-module(hakuzaru).
|
-module(hakuzaru).
|
||||||
-vsn("0.2.0").
|
-vsn("0.4.0").
|
||||||
-author("Craig Everett <ceverett@tsuriai.jp>").
|
-author("Craig Everett <ceverett@tsuriai.jp>").
|
||||||
-copyright("Craig Everett <ceverett@tsuriai.jp>").
|
-copyright("Craig Everett <ceverett@tsuriai.jp>").
|
||||||
-license("GPL-3.0-or-later").
|
-license("GPL-3.0-or-later").
|
||||||
|
590
src/hz.erl
590
src/hz.erl
@ -23,7 +23,7 @@
|
|||||||
%%% @end
|
%%% @end
|
||||||
|
|
||||||
-module(hz).
|
-module(hz).
|
||||||
-vsn("0.2.0").
|
-vsn("0.4.0").
|
||||||
-author("Craig Everett <ceverett@tsuriai.jp>").
|
-author("Craig Everett <ceverett@tsuriai.jp>").
|
||||||
-copyright("Craig Everett <ceverett@tsuriai.jp>").
|
-copyright("Craig Everett <ceverett@tsuriai.jp>").
|
||||||
-license("GPL-3.0-or-later").
|
-license("GPL-3.0-or-later").
|
||||||
@ -48,7 +48,7 @@
|
|||||||
dry_run/1, dry_run/2, dry_run/3, dry_run_map/1,
|
dry_run/1, dry_run/2, dry_run/3, dry_run_map/1,
|
||||||
tx/1, tx_info/1,
|
tx/1, tx_info/1,
|
||||||
post_tx/1,
|
post_tx/1,
|
||||||
contract/1, contract_code/1,
|
contract/1, contract_code/1, contract_source/1,
|
||||||
contract_poi/1,
|
contract_poi/1,
|
||||||
name/1,
|
name/1,
|
||||||
% channel/1,
|
% channel/1,
|
||||||
@ -62,8 +62,10 @@
|
|||||||
min_gas_price/0,
|
min_gas_price/0,
|
||||||
contract_create/3,
|
contract_create/3,
|
||||||
contract_create_built/3,
|
contract_create_built/3,
|
||||||
|
contract_create_built/8,
|
||||||
contract_create/8,
|
contract_create/8,
|
||||||
prepare_contract/1,
|
prepare_contract/1,
|
||||||
|
prepare_aaci/1,
|
||||||
aaci_lookup_spec/2,
|
aaci_lookup_spec/2,
|
||||||
contract_call/5,
|
contract_call/5,
|
||||||
contract_call/6,
|
contract_call/6,
|
||||||
@ -76,6 +78,7 @@
|
|||||||
|
|
||||||
-export_type([chain_node/0, network_id/0, chain_error/0]).
|
-export_type([chain_node/0, network_id/0, chain_error/0]).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-type chain_node() :: {inet:ip_address(), inet:port_number()}.
|
-type chain_node() :: {inet:ip_address(), inet:port_number()}.
|
||||||
-type network_id() :: string().
|
-type network_id() :: string().
|
||||||
@ -216,7 +219,7 @@
|
|||||||
-spec network_id() -> NetworkID
|
-spec network_id() -> NetworkID
|
||||||
when NetworkID :: string() | none.
|
when NetworkID :: string() | none.
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Returns the AE network ID or the atom `none' if it is unset.
|
%% Returns the network ID or the atom `none' if it is unset.
|
||||||
%% Checking this is not normally necessary, but if network ID assignment is dynamic
|
%% Checking this is not normally necessary, but if network ID assignment is dynamic
|
||||||
%% in your system it may be necessary to call this before attempting to form
|
%% in your system it may be necessary to call this before attempting to form
|
||||||
%% call data or perform other actions on chain that require a signature.
|
%% call data or perform other actions on chain that require a signature.
|
||||||
@ -249,8 +252,8 @@ chain_nodes() ->
|
|||||||
when List :: [chain_node()],
|
when List :: [chain_node()],
|
||||||
Reason :: {invalid, [term()]}.
|
Reason :: {invalid, [term()]}.
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Sets the AE nodes that are intended to be used as your interface to the AE peer
|
%% Sets the nodes that are intended to be used as your interface to the peer
|
||||||
%% network. The common situation is that your project runs a non-mining AE node as
|
%% network. The common situation is that your project runs a non-mining node as
|
||||||
%% part of your backend infrastructure. Typically one or two nodes is plenty, but
|
%% part of your backend infrastructure. Typically one or two nodes is plenty, but
|
||||||
%% this may need to expand depending on how much query load your application generates.
|
%% this may need to expand depending on how much query load your application generates.
|
||||||
%% The Hakuzaru manager will load balance by round-robin distribution.
|
%% The Hakuzaru manager will load balance by round-robin distribution.
|
||||||
@ -304,7 +307,7 @@ timeout(MS) ->
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%% AE node JSON query interface functions
|
%%% JSON query interface functions
|
||||||
|
|
||||||
|
|
||||||
-spec top_height() -> {ok, Height} | {error, Reason}
|
-spec top_height() -> {ok, Height} | {error, Reason}
|
||||||
@ -617,8 +620,8 @@ dry_run(TX) ->
|
|||||||
%% supplied accounts.
|
%% supplied accounts.
|
||||||
|
|
||||||
dry_run(TX, Accounts) ->
|
dry_run(TX, Accounts) ->
|
||||||
case kb_current_hash() of
|
case top_block() of
|
||||||
{ok, Hash} -> dry_run(TX, Accounts, Hash);
|
{ok, #{"hash" := Hash}} -> dry_run(TX, Accounts, Hash);
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -656,13 +659,13 @@ dry_run_map(Map) ->
|
|||||||
|
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Decode the "cb_XXXX" string that came out of a tx_info or dry_run, to
|
%% Decode the "cb_XXXX" string that came out of a tx_info or dry_run, to
|
||||||
%% the Erlang representation of FATE objects used by aeb_fate_encoding. See
|
%% the Erlang representation of FATE objects used by gmb_fate_encoding. See
|
||||||
%% decode_bytearray/2 for an alternative that provides simpler outputs based on
|
%% decode_bytearray/2 for an alternative that provides simpler outputs based on
|
||||||
%% information provided by an AACI.
|
%% information provided by an AACI.
|
||||||
|
|
||||||
decode_bytearray_fate(EncodedStr) ->
|
decode_bytearray_fate(EncodedStr) ->
|
||||||
Encoded = unicode:characters_to_binary(EncodedStr),
|
Encoded = unicode:characters_to_binary(EncodedStr),
|
||||||
{contract_bytearray, Binary} = aeser_api_encoder:decode(Encoded),
|
{contract_bytearray, Binary} = gmser_api_encoder:decode(Encoded),
|
||||||
case Binary of
|
case Binary of
|
||||||
<<>> -> {ok, none};
|
<<>> -> {ok, none};
|
||||||
<<"Out of gas">> -> {error, out_of_gas};
|
<<"Out of gas">> -> {error, out_of_gas};
|
||||||
@ -670,7 +673,7 @@ decode_bytearray_fate(EncodedStr) ->
|
|||||||
% FIXME there may be other errors that are encoded directly into
|
% FIXME there may be other errors that are encoded directly into
|
||||||
% the byte array. We could try and catch to at least return
|
% the byte array. We could try and catch to at least return
|
||||||
% *something* for cases that we don't already detect.
|
% *something* for cases that we don't already detect.
|
||||||
Object = aeb_fate_encoding:deserialize(Binary),
|
Object = gmb_fate_encoding:deserialize(Binary),
|
||||||
{ok, Object}
|
{ok, Object}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -758,6 +761,21 @@ contract_code(ID) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec contract_source(ID) -> {ok, Bytecode} | {error, Reason}
|
||||||
|
when ID :: contract_id(),
|
||||||
|
Bytecode :: contract_byte_array(),
|
||||||
|
Reason :: chain_error() | string().
|
||||||
|
%% @doc
|
||||||
|
%% Retrieve the code of a contract as represented on chain.
|
||||||
|
|
||||||
|
contract_source(ID) ->
|
||||||
|
case request(["/v3/contracts/", ID, "/source"]) of
|
||||||
|
{ok, #{"source" := Source}} -> {ok, Source};
|
||||||
|
{ok, #{"reason" := Reason}} -> {error, Reason};
|
||||||
|
Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec contract_poi(ID) -> {ok, Bytecode} | {error, Reason}
|
-spec contract_poi(ID) -> {ok, Bytecode} | {error, Reason}
|
||||||
when ID :: contract_id(),
|
when ID :: contract_id(),
|
||||||
Bytecode :: contract_byte_array(),
|
Bytecode :: contract_byte_array(),
|
||||||
@ -952,7 +970,7 @@ contract_create(CreatorID, Path, InitArgs) ->
|
|||||||
%% </li>
|
%% </li>
|
||||||
%% <li>
|
%% <li>
|
||||||
%% <b>GasPrice:</b>
|
%% <b>GasPrice:</b>
|
||||||
%% This is a factor that is used calculate a value in aettos (the smallest unit of
|
%% This is a factor that is used calculate a value in pucks (the smallest unit of
|
||||||
%% Gajumaru's currency value) for the gas consumed. In times of high contention
|
%% Gajumaru's currency value) for the gas consumed. In times of high contention
|
||||||
%% in the mempool increasing the gas price increases the value of mining a given
|
%% in the mempool increasing the gas price increases the value of mining a given
|
||||||
%% transaction, thus making miners more likely to prioritize the high value ones.
|
%% transaction, thus making miners more likely to prioritize the high value ones.
|
||||||
@ -995,12 +1013,12 @@ contract_create(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Path, InitArgs) ->
|
|||||||
{ok, Source} ->
|
{ok, Source} ->
|
||||||
Dir = filename:dirname(Path),
|
Dir = filename:dirname(Path),
|
||||||
{ok, CWD} = file:get_cwd(),
|
{ok, CWD} = file:get_cwd(),
|
||||||
SrcDir = aeso_utils:canonical_dir(Path),
|
SrcDir = so_utils:canonical_dir(Path),
|
||||||
Options =
|
Options =
|
||||||
[{aci, json},
|
[{aci, json},
|
||||||
{src_file, Path},
|
{src_file, Path},
|
||||||
{src_dir, SrcDir},
|
{src_dir, SrcDir},
|
||||||
{include, {file_system, [CWD, aeso_utils:canonical_dir(Dir)]}}],
|
{include, {file_system, [CWD, so_utils:canonical_dir(Dir)]}}],
|
||||||
contract_create2(CreatorID, Nonce, Amount, TTL, Gas, GasPrice,
|
contract_create2(CreatorID, Nonce, Amount, TTL, Gas, GasPrice,
|
||||||
Source, Options, InitArgs);
|
Source, Options, InitArgs);
|
||||||
Error ->
|
Error ->
|
||||||
@ -1008,6 +1026,16 @@ contract_create(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Path, InitArgs) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
contract_create2(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Source, Options, InitArgs) ->
|
||||||
|
case so_compiler:from_string(Source, Options) of
|
||||||
|
{ok, Compiled} ->
|
||||||
|
contract_create_built(CreatorID, Nonce, Amount, TTL, Gas, GasPrice,
|
||||||
|
Compiled, InitArgs);
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec contract_create_built(CreatorID, Compiled, InitArgs) -> Result
|
-spec contract_create_built(CreatorID, Compiled, InitArgs) -> Result
|
||||||
when CreatorID :: unicode:chardata(),
|
when CreatorID :: unicode:chardata(),
|
||||||
Compiled :: map(),
|
Compiled :: map(),
|
||||||
@ -1028,7 +1056,7 @@ contract_create_built(CreatorID, Compiled, InitArgs) ->
|
|||||||
TTL = Height + 262980,
|
TTL = Height + 262980,
|
||||||
Gas = 500000,
|
Gas = 500000,
|
||||||
GasPrice = min_gas_price(),
|
GasPrice = min_gas_price(),
|
||||||
contract_create3(CreatorID, Nonce,
|
contract_create_built(CreatorID, Nonce,
|
||||||
Amount, TTL, Gas, GasPrice,
|
Amount, TTL, Gas, GasPrice,
|
||||||
Compiled, InitArgs);
|
Compiled, InitArgs);
|
||||||
Error ->
|
Error ->
|
||||||
@ -1036,44 +1064,36 @@ contract_create_built(CreatorID, Compiled, InitArgs) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
contract_create2(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Source, Options, InitArgs) ->
|
contract_create_built(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, InitArgs) ->
|
||||||
case aeso_compiler:from_string(Source, Options) of
|
|
||||||
{ok, Compiled} ->
|
|
||||||
contract_create3(CreatorID, Nonce, Amount, TTL, Gas, GasPrice,
|
|
||||||
Compiled, InitArgs);
|
|
||||||
Error ->
|
|
||||||
Error
|
|
||||||
end.
|
|
||||||
|
|
||||||
contract_create3(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, InitArgs) ->
|
|
||||||
AACI = prepare_aaci(maps:get(aci, Compiled)),
|
AACI = prepare_aaci(maps:get(aci, Compiled)),
|
||||||
case encode_call_data(AACI, "init", InitArgs) of
|
case encode_call_data(AACI, "init", InitArgs) of
|
||||||
{ok, CallData} ->
|
{ok, CallData} ->
|
||||||
contract_create4(CreatorID, Nonce, Amount, TTL, Gas, GasPrice,
|
assemble_calldata(CreatorID, Nonce, Amount, TTL, Gas, GasPrice,
|
||||||
Compiled, CallData);
|
Compiled, CallData);
|
||||||
Error ->
|
Error ->
|
||||||
Error
|
Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
contract_create4(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData) ->
|
|
||||||
|
assemble_calldata(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData) ->
|
||||||
PK = unicode:characters_to_binary(CreatorID),
|
PK = unicode:characters_to_binary(CreatorID),
|
||||||
try
|
try
|
||||||
{account_pubkey, OwnerID} = aeser_api_encoder:decode(PK),
|
{account_pubkey, OwnerID} = gmser_api_encoder:decode(PK),
|
||||||
contract_create5(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData)
|
assemble_calldata2(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData)
|
||||||
catch
|
catch
|
||||||
Error:Reason -> {Error, Reason}
|
Error:Reason -> {Error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
contract_create5(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData) ->
|
assemble_calldata2(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData) ->
|
||||||
Code = aeser_contract_code:serialize(Compiled),
|
Code = gmser_contract_code:serialize(Compiled),
|
||||||
Source = maps:get(contract_source, Compiled, <<>>),
|
Source = unicode:characters_to_binary(maps:get(contract_source, Compiled, <<>>)),
|
||||||
VM = 1,
|
VM = 1,
|
||||||
ABI = 1,
|
ABI = 1,
|
||||||
<<CTVersion:32>> = <<VM:16, ABI:16>>,
|
<<CTVersion:32>> = <<VM:16, ABI:16>>,
|
||||||
ContractCreateVersion = 1,
|
ContractCreateVersion = 1,
|
||||||
Type = contract_create_tx,
|
Type = contract_create_tx,
|
||||||
Fields =
|
Fields =
|
||||||
[{owner_id, aeser_id:create(account, OwnerID)},
|
[{owner_id, gmser_id:create(account, OwnerID)},
|
||||||
{nonce, Nonce},
|
{nonce, Nonce},
|
||||||
{code, Code},
|
{code, Code},
|
||||||
{source, Source},
|
{source, Source},
|
||||||
@ -1096,9 +1116,9 @@ contract_create5(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData)
|
|||||||
{gas_price, int},
|
{gas_price, int},
|
||||||
{gas, int},
|
{gas, int},
|
||||||
{call_data, binary}],
|
{call_data, binary}],
|
||||||
TXB = aeser_chain_objects:serialize(Type, ContractCreateVersion, Template, Fields),
|
TXB = gmser_chain_objects:serialize(Type, ContractCreateVersion, Template, Fields),
|
||||||
try
|
try
|
||||||
{ok, aeser_api_encoder:encode(transaction, TXB)}
|
{ok, gmser_api_encoder:encode(transaction, TXB)}
|
||||||
catch
|
catch
|
||||||
error:Reason -> {error, Reason}
|
error:Reason -> {error, Reason}
|
||||||
end.
|
end.
|
||||||
@ -1110,14 +1130,11 @@ contract_create5(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData)
|
|||||||
ACI :: tuple(), % FIXME: Change to correct Sophia record
|
ACI :: tuple(), % FIXME: Change to correct Sophia record
|
||||||
Reason :: file:posix() | bad_aci.
|
Reason :: file:posix() | bad_aci.
|
||||||
%% @doc
|
%% @doc
|
||||||
%% This function reads the contents of an .aci file produced by AEL (the Gajumaru
|
%% This function reads the contents of an .aci file.
|
||||||
%% Launcher). ACI data is required for the contract call encoder to function properly.
|
%% ACI data is required for the contract call encoder to function properly.
|
||||||
%% ACI data is can be generated and stored in JSON data, and the Sophia CLI tool
|
%% ACI data is can be generated and stored in JSON data, and the Sophia CLI tool
|
||||||
%% can perform this action. Unfortunately, JSON is not the way that ACI data is
|
%% can perform this action. Unfortunately, JSON is not the way that ACI data is
|
||||||
%% represented internally, and here we need the actual native representation. For
|
%% represented internally, and here we need the actual native representation.
|
||||||
%% that reason Gajumaru's GUI launcher (AEL) has a "Developer's Workbench" tool
|
|
||||||
%% that can produce an .aci file from a contract's source code and store it in the
|
|
||||||
%% native Erlang format.
|
|
||||||
%%
|
%%
|
||||||
%% ACI encding/decoding and contract call encoding is significantly complex enough that
|
%% ACI encding/decoding and contract call encoding is significantly complex enough that
|
||||||
%% this provides for a pretty large savings in complexity for this library, dramatically
|
%% this provides for a pretty large savings in complexity for this library, dramatically
|
||||||
@ -1271,7 +1288,7 @@ contract_call(CallerID, Gas, AACI, ConID, Fun, Args) ->
|
|||||||
%% </li>
|
%% </li>
|
||||||
%% <li>
|
%% <li>
|
||||||
%% <b>GasPrice:</b>
|
%% <b>GasPrice:</b>
|
||||||
%% This is a factor that is used calculate a value in aettos (the smallest unit of
|
%% This is a factor that is used calculate a value in pucks (the smallest unit of
|
||||||
%% Gajumaru's currency value) for the gas consumed. In times of high contention
|
%% Gajumaru's currency value) for the gas consumed. In times of high contention
|
||||||
%% in the mempool increasing the gas price increases the value of mining a given
|
%% in the mempool increasing the gas price increases the value of mining a given
|
||||||
%% transaction, thus making miners more likely to prioritize the high value ones.
|
%% transaction, thus making miners more likely to prioritize the high value ones.
|
||||||
@ -1329,7 +1346,7 @@ contract_call(CallerID, Nonce, Gas, GP, Amount, TTL, AACI, ConID, Fun, Args) ->
|
|||||||
contract_call2(CallerID, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) ->
|
contract_call2(CallerID, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) ->
|
||||||
CallerBin = unicode:characters_to_binary(CallerID),
|
CallerBin = unicode:characters_to_binary(CallerID),
|
||||||
try
|
try
|
||||||
{account_pubkey, PK} = aeser_api_encoder:decode(CallerBin),
|
{account_pubkey, PK} = gmser_api_encoder:decode(CallerBin),
|
||||||
contract_call3(PK, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData)
|
contract_call3(PK, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData)
|
||||||
catch
|
catch
|
||||||
Error:Reason -> {Error, Reason}
|
Error:Reason -> {Error, Reason}
|
||||||
@ -1338,7 +1355,7 @@ contract_call2(CallerID, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) ->
|
|||||||
contract_call3(PK, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) ->
|
contract_call3(PK, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) ->
|
||||||
ConBin = unicode:characters_to_binary(ConID),
|
ConBin = unicode:characters_to_binary(ConID),
|
||||||
try
|
try
|
||||||
{contract_pubkey, CK} = aeser_api_encoder:decode(ConBin),
|
{contract_pubkey, CK} = gmser_api_encoder:decode(ConBin),
|
||||||
contract_call4(PK, Nonce, Gas, GasPrice, Amount, TTL, CK, CallData)
|
contract_call4(PK, Nonce, Gas, GasPrice, Amount, TTL, CK, CallData)
|
||||||
catch
|
catch
|
||||||
Error:Reason -> {Error, Reason}
|
Error:Reason -> {Error, Reason}
|
||||||
@ -1349,9 +1366,9 @@ contract_call4(PK, Nonce, Gas, GasPrice, Amount, TTL, CK, CallData) ->
|
|||||||
CallVersion = 1,
|
CallVersion = 1,
|
||||||
Type = contract_call_tx,
|
Type = contract_call_tx,
|
||||||
Fields =
|
Fields =
|
||||||
[{caller_id, aeser_id:create(account, PK)},
|
[{caller_id, gmser_id:create(account, PK)},
|
||||||
{nonce, Nonce},
|
{nonce, Nonce},
|
||||||
{contract_id, aeser_id:create(contract, CK)},
|
{contract_id, gmser_id:create(contract, CK)},
|
||||||
{abi_version, ABI},
|
{abi_version, ABI},
|
||||||
{ttl, TTL},
|
{ttl, TTL},
|
||||||
{amount, Amount},
|
{amount, Amount},
|
||||||
@ -1368,9 +1385,9 @@ contract_call4(PK, Nonce, Gas, GasPrice, Amount, TTL, CK, CallData) ->
|
|||||||
{gas_price, int},
|
{gas_price, int},
|
||||||
{gas, int},
|
{gas, int},
|
||||||
{call_data, binary}],
|
{call_data, binary}],
|
||||||
TXB = aeser_chain_objects:serialize(Type, CallVersion, Template, Fields),
|
TXB = gmser_chain_objects:serialize(Type, CallVersion, Template, Fields),
|
||||||
try
|
try
|
||||||
{ok, aeser_api_encoder:encode(transaction, TXB)}
|
{ok, gmser_api_encoder:encode(transaction, TXB)}
|
||||||
catch
|
catch
|
||||||
error:Reason -> {error, Reason}
|
error:Reason -> {error, Reason}
|
||||||
end.
|
end.
|
||||||
@ -1385,101 +1402,106 @@ contract_call4(PK, Nonce, Gas, GasPrice, Amount, TTL, CK, CallData) ->
|
|||||||
%% of calldata
|
%% of calldata
|
||||||
|
|
||||||
prepare_contract(File) ->
|
prepare_contract(File) ->
|
||||||
case aeso_compiler:file(File, [{aci, json}]) of
|
case so_compiler:file(File, [{aci, json}]) of
|
||||||
{ok, #{aci := ACI}} -> {ok, prepare_aaci(ACI)};
|
{ok, #{aci := ACI}} -> {ok, prepare_aaci(ACI)};
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
prepare_aaci(ACI) ->
|
prepare_aaci(ACI) ->
|
||||||
% NOTE this will also pick up the main contract; as a result the main
|
% We want to take the types represented by the ACI, things like N1.T(N2.T),
|
||||||
% contract extraction later on shouldn't bother with typedefs.
|
% and dereference them down to concrete types like
|
||||||
Contracts = [ContractDef || #{contract := ContractDef} <- ACI],
|
% {tuple, [integer, string]}. Our type dereferencing algorithms
|
||||||
Types = simplify_contract_types(Contracts, #{}),
|
% shouldn't act directly on the JSON-based structures that the compiler
|
||||||
|
% gives us, though, though, so before we do the analysis, we should strip
|
||||||
|
% the ACI down to a list of 'opaque' type defintions and function specs.
|
||||||
|
{Name, OpaqueSpecs, TypeDefs} = convert_aci_types(ACI),
|
||||||
|
|
||||||
|
% Now that we have the opaque types, we can dereference the function specs
|
||||||
|
% down to the concrete types they actually represent. We annotate each
|
||||||
|
% subexpression of this concrete type with other info too, in case it helps
|
||||||
|
% make error messages easier to understand.
|
||||||
|
Specs = annotate_function_specs(OpaqueSpecs, TypeDefs, #{}),
|
||||||
|
|
||||||
|
{aaci, Name, Specs, TypeDefs}.
|
||||||
|
|
||||||
|
convert_aci_types(ACI) ->
|
||||||
|
% Find the main contract, so we can get the specifications of its
|
||||||
|
% entrypoints.
|
||||||
[{NameBin, SpecDefs}] =
|
[{NameBin, SpecDefs}] =
|
||||||
[{N, F}
|
[{N, F}
|
||||||
|| #{contract := #{kind := contract_main,
|
|| #{contract := #{kind := contract_main,
|
||||||
functions := F,
|
functions := F,
|
||||||
name := N}} <- ACI],
|
name := N}} <- ACI],
|
||||||
Name = binary_to_list(NameBin),
|
Name = binary_to_list(NameBin),
|
||||||
Specs = simplify_specs(SpecDefs, #{}, Types),
|
% Turn these specifications into opaque types that we can reason about.
|
||||||
{aaci, Name, Specs, Types}.
|
Specs = lists:map(fun convert_function_spec/1, SpecDefs),
|
||||||
|
|
||||||
simplify_contract_types([], Types) ->
|
% These specifications can reference other type definitions from the main
|
||||||
Types;
|
% contract and any other namespaces, so extract these types and convert
|
||||||
simplify_contract_types([Next | Rest], Types) ->
|
% them too.
|
||||||
TypeDefs = maps:get(typedefs, Next),
|
TypeDefTree = lists:map(fun convert_namespace_typedefs/1, ACI),
|
||||||
NameBin = maps:get(name, Next),
|
% The tree structure of the ACI naturally leads to a tree of opaque types,
|
||||||
|
% but we want a map, so flatten it out before we continue.
|
||||||
|
TypeDefMap = collect_opaque_types(TypeDefTree, #{}),
|
||||||
|
|
||||||
|
% This is all the information we actually need from the ACI, the rest is
|
||||||
|
% just pre-compute and acceleration.
|
||||||
|
{Name, Specs, TypeDefMap}.
|
||||||
|
|
||||||
|
convert_function_spec(#{name := NameBin, arguments := Args, returns := Result}) ->
|
||||||
Name = binary_to_list(NameBin),
|
Name = binary_to_list(NameBin),
|
||||||
Types2 = maps:put(Name, {[], contract}, Types),
|
ArgTypes = lists:map(fun convert_arg/1, Args),
|
||||||
Types3 = case maps:find(state, Next) of
|
ResultType = opaque_type([], Result),
|
||||||
{ok, StateDefACI} ->
|
{Name, ArgTypes, ResultType}.
|
||||||
StateDefOpaque = opaque_type([], StateDefACI),
|
|
||||||
maps:put(Name ++ ".state", {[], StateDefOpaque}, Types2);
|
|
||||||
error ->
|
|
||||||
Types2
|
|
||||||
end,
|
|
||||||
Types4 = simplify_typedefs(TypeDefs, Types3, Name ++ "."),
|
|
||||||
simplify_contract_types(Rest, Types4).
|
|
||||||
|
|
||||||
simplify_typedefs([], Types, _NamePrefix) ->
|
convert_arg(#{name := NameBin, type := TypeDef}) ->
|
||||||
Types;
|
|
||||||
simplify_typedefs([Next | Rest], Types, NamePrefix) ->
|
|
||||||
#{name := NameBin, vars := ParamDefs, typedef := T} = Next,
|
|
||||||
Name = NamePrefix ++ binary_to_list(NameBin),
|
|
||||||
Params = [binary_to_list(Param) || #{name := Param} <- ParamDefs],
|
|
||||||
Type = opaque_type(Params, T),
|
|
||||||
NewTypes = maps:put(Name, {Params, Type}, Types),
|
|
||||||
simplify_typedefs(Rest, NewTypes, NamePrefix).
|
|
||||||
|
|
||||||
simplify_specs([], Specs, _Types) ->
|
|
||||||
Specs;
|
|
||||||
simplify_specs([Next | Rest], Specs, Types) ->
|
|
||||||
#{name := NameBin, arguments := ArgDefs, returns := ResultDef} = Next,
|
|
||||||
Name = binary_to_list(NameBin),
|
Name = binary_to_list(NameBin),
|
||||||
ArgTypes = [simplify_args(Arg, Types) || Arg <- ArgDefs],
|
Type = opaque_type([], TypeDef),
|
||||||
{ok, ResultType} = type(ResultDef, Types),
|
|
||||||
NewSpecs = maps:put(Name, {ArgTypes, ResultType}, Specs),
|
|
||||||
simplify_specs(Rest, NewSpecs, Types).
|
|
||||||
|
|
||||||
simplify_args(#{name := NameBin, type := TypeDef}, Types) ->
|
|
||||||
Name = binary_to_list(NameBin),
|
|
||||||
% FIXME We should make this error more informative, and continue
|
|
||||||
% propogating it up, so that the user can provide their own ACI and find
|
|
||||||
% out whether it worked or not. At that point ACI -> AACI could almost be a
|
|
||||||
% module or package of its own.
|
|
||||||
{ok, Type} = type(TypeDef, Types),
|
|
||||||
{Name, Type}.
|
{Name, Type}.
|
||||||
|
|
||||||
% Type preparation has two goals. First, we need a data structure that can be
|
convert_namespace_typedefs(#{namespace := NS}) ->
|
||||||
% traversed quickly, to take sophia-esque erlang expressions and turn them into
|
Name = namespace_name(NS),
|
||||||
% fate-esque erlang expressions that aebytecode can serialize. Second, we need
|
convert_typedefs(NS, Name);
|
||||||
% partially substituted names, so that error messages can be generated for why
|
convert_namespace_typedefs(#{contract := NS}) ->
|
||||||
% "foobar" is not valid as the third field of a `bazquux`, because the third
|
Name = namespace_name(NS),
|
||||||
% field is supposed to be `option(integer)`, not `string`.
|
ImplicitTypes = convert_implicit_types(NS, Name),
|
||||||
%
|
ExplicitTypes = convert_typedefs(NS, Name),
|
||||||
% To achieve this we need three representations of each type expression, which
|
[ImplicitTypes, ExplicitTypes].
|
||||||
% together form an 'annotated type'. First, we need the fully opaque name,
|
|
||||||
% "bazquux", then we need the normalized name, which is an opaque name with the
|
|
||||||
% bare-minimum substitution needed to make the outer-most type-constructor an
|
|
||||||
% identifiable built-in, ADT, or record type, and then we need the flattened
|
|
||||||
% type, which is the raw {variant, [{Name, Fields}, ...]} or
|
|
||||||
% {record, [{Name, Type}]} expression that can be used in actual Sophia->FATE
|
|
||||||
% coercion. The type sub-expressions in these flattened types will each be
|
|
||||||
% fully annotated as well, i.e. they will each contain *all three* of the above
|
|
||||||
% representations, so that coercion of subexpressions remains fast AND
|
|
||||||
% informative.
|
|
||||||
%
|
|
||||||
% In a lot of cases the opaque type given will already be normalized, in which
|
|
||||||
% case either the normalized field or the non-normalized field of an annotated
|
|
||||||
% type can simple be the atom `already_normalized`, which means error messages
|
|
||||||
% can simply render the normalized type expression and know that the error will
|
|
||||||
% make sense.
|
|
||||||
|
|
||||||
type(T, Types) ->
|
namespace_name(#{name := NameBin}) ->
|
||||||
O = opaque_type([], T),
|
binary_to_list(NameBin).
|
||||||
flatten_opaque_type(O, Types).
|
|
||||||
|
|
||||||
|
convert_implicit_types(#{state := StateDefACI}, Name) ->
|
||||||
|
StateDefOpaque = opaque_type([], StateDefACI),
|
||||||
|
[{Name, [], contract},
|
||||||
|
{Name ++ ".state", [], StateDefOpaque}];
|
||||||
|
convert_implicit_types(_, Name) ->
|
||||||
|
[{Name, [], contract}].
|
||||||
|
|
||||||
|
convert_typedefs(#{typedefs := TypeDefs}, Name) ->
|
||||||
|
convert_typedefs_loop(TypeDefs, Name ++ ".", []).
|
||||||
|
|
||||||
|
% Take a namespace that has already had a period appended, and use that as a
|
||||||
|
% prefix to convert and annotate a list of types.
|
||||||
|
convert_typedefs_loop([], _NamePrefix, Converted) ->
|
||||||
|
Converted;
|
||||||
|
convert_typedefs_loop([Next | Rest], NamePrefix, Converted) ->
|
||||||
|
#{name := NameBin, vars := ParamDefs, typedef := DefACI} = Next,
|
||||||
|
Name = NamePrefix ++ binary_to_list(NameBin),
|
||||||
|
Params = [binary_to_list(Param) || #{name := Param} <- ParamDefs],
|
||||||
|
Def = opaque_type(Params, DefACI),
|
||||||
|
convert_typedefs_loop(Rest, NamePrefix, [Converted, {Name, Params, Def}]).
|
||||||
|
|
||||||
|
collect_opaque_types([], Types) ->
|
||||||
|
Types;
|
||||||
|
collect_opaque_types([L | R], Types) ->
|
||||||
|
NewTypes = collect_opaque_types(L, Types),
|
||||||
|
collect_opaque_types(R, NewTypes);
|
||||||
|
collect_opaque_types({Name, Params, Def}, Types) ->
|
||||||
|
maps:put(Name, {Params, Def}, Types).
|
||||||
|
|
||||||
|
% Convert an ACI type defintion/spec into the 'opaque type' representation that
|
||||||
|
% our dereferencing algorithms can reason about.
|
||||||
opaque_type(Params, NameBin) when is_binary(NameBin) ->
|
opaque_type(Params, NameBin) when is_binary(NameBin) ->
|
||||||
Name = opaque_type_name(NameBin),
|
Name = opaque_type_name(NameBin),
|
||||||
case not is_atom(Name) and lists:member(Name, Params) of
|
case not is_atom(Name) and lists:member(Name, Params) of
|
||||||
@ -1503,7 +1525,7 @@ opaque_type(Params, Pair) when is_map(Pair) ->
|
|||||||
[{Name, TypeArgs}] = maps:to_list(Pair),
|
[{Name, TypeArgs}] = maps:to_list(Pair),
|
||||||
{opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}.
|
{opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}.
|
||||||
|
|
||||||
% atoms for builtins, lists for user defined types
|
% atoms for builtins, strings (lists) for user-defined types
|
||||||
opaque_type_name(<<"int">>) -> integer;
|
opaque_type_name(<<"int">>) -> integer;
|
||||||
opaque_type_name(<<"address">>) -> address;
|
opaque_type_name(<<"address">>) -> address;
|
||||||
opaque_type_name(<<"contract">>) -> contract;
|
opaque_type_name(<<"contract">>) -> contract;
|
||||||
@ -1514,16 +1536,49 @@ opaque_type_name(<<"map">>) -> map;
|
|||||||
opaque_type_name(<<"string">>) -> string;
|
opaque_type_name(<<"string">>) -> string;
|
||||||
opaque_type_name(Name) -> binary_to_list(Name).
|
opaque_type_name(Name) -> binary_to_list(Name).
|
||||||
|
|
||||||
flatten_opaque_type(T, Types) ->
|
% Type preparation has two goals. First, we need a data structure that can be
|
||||||
|
% traversed quickly, to take sophia-esque erlang expressions and turn them into
|
||||||
|
% fate-esque erlang expressions that gmbytecode can serialize. Second, we need
|
||||||
|
% partially substituted names, so that error messages can be generated for why
|
||||||
|
% "foobar" is not valid as the third field of a `bazquux`, because the third
|
||||||
|
% field is supposed to be `option(integer)`, not `string`.
|
||||||
|
%
|
||||||
|
% To achieve this we need three representations of each type expression, which
|
||||||
|
% together form an 'annotated type'. First, we need the fully opaque name,
|
||||||
|
% "bazquux", then we need the normalized name, which is an opaque name with the
|
||||||
|
% bare-minimum substitution needed to make the outer-most type-constructor an
|
||||||
|
% identifiable built-in, ADT, or record type, and then we need the dereferenced
|
||||||
|
% type, which is the raw {variant, [{Name, Fields}, ...]} or
|
||||||
|
% {record, [{Name, Type}]} expression that can be used in actual Sophia->FATE
|
||||||
|
% coercion. The type sub-expressions in these dereferenced types will each be
|
||||||
|
% fully annotated as well, i.e. they will each contain *all three* of the above
|
||||||
|
% representations, so that coercion of subexpressions remains fast AND
|
||||||
|
% informative.
|
||||||
|
%
|
||||||
|
% In a lot of cases the opaque type given will already be normalized, in which
|
||||||
|
% case either the normalized field or the non-normalized field of an annotated
|
||||||
|
% type can simple be the atom `already_normalized`, which means error messages
|
||||||
|
% can simply render the normalized type expression and know that the error will
|
||||||
|
% make sense.
|
||||||
|
|
||||||
|
annotate_function_specs([], _Types, Specs) ->
|
||||||
|
Specs;
|
||||||
|
annotate_function_specs([{Name, ArgsOpaque, ResultOpaque} | Rest], Types, Specs) ->
|
||||||
|
{ok, Args} = annotate_bindings(ArgsOpaque, Types, []),
|
||||||
|
{ok, Result} = annotate_type(ResultOpaque, Types),
|
||||||
|
NewSpecs = maps:put(Name, {Args, Result}, Specs),
|
||||||
|
annotate_function_specs(Rest, Types, NewSpecs).
|
||||||
|
|
||||||
|
annotate_type(T, Types) ->
|
||||||
case normalize_opaque_type(T, Types) of
|
case normalize_opaque_type(T, Types) of
|
||||||
{ok, AlreadyNormalized, NOpaque, NExpanded} ->
|
{ok, AlreadyNormalized, NOpaque, NExpanded} ->
|
||||||
flatten_opaque_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types);
|
annotate_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types);
|
||||||
Error ->
|
Error ->
|
||||||
Error
|
Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
flatten_opaque_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types) ->
|
annotate_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types) ->
|
||||||
case flatten_normalized_type(NExpanded, Types) of
|
case annotate_type_subexpressions(NExpanded, Types) of
|
||||||
{ok, Flat} ->
|
{ok, Flat} ->
|
||||||
case AlreadyNormalized of
|
case AlreadyNormalized of
|
||||||
true -> {ok, {T, already_normalized, Flat}};
|
true -> {ok, {T, already_normalized, Flat}};
|
||||||
@ -1533,48 +1588,48 @@ flatten_opaque_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types) ->
|
|||||||
Error
|
Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
flatten_opaque_types([T | Rest], Types, Acc) ->
|
annotate_types([T | Rest], Types, Acc) ->
|
||||||
case flatten_opaque_type(T, Types) of
|
case annotate_type(T, Types) of
|
||||||
{ok, Type} -> flatten_opaque_types(Rest, Types, [Type | Acc]);
|
{ok, Type} -> annotate_types(Rest, Types, [Type | Acc]);
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
flatten_opaque_types([], _Types, Acc) ->
|
annotate_types([], _Types, Acc) ->
|
||||||
{ok, lists:reverse(Acc)}.
|
{ok, lists:reverse(Acc)}.
|
||||||
|
|
||||||
flatten_opaque_bindings([{Name, T} | Rest], Types, Acc) ->
|
annotate_type_subexpressions(PrimitiveType, _Types) when is_atom(PrimitiveType) ->
|
||||||
case flatten_opaque_type(T, Types) of
|
|
||||||
{ok, Type} -> flatten_opaque_bindings(Rest, Types, [{Name, Type} | Acc]);
|
|
||||||
Error -> Error
|
|
||||||
end;
|
|
||||||
flatten_opaque_bindings([], _Types, Acc) ->
|
|
||||||
{ok, lists:reverse(Acc)}.
|
|
||||||
|
|
||||||
flatten_opaque_variants([{Name, Elems} | Rest], Types, Acc) ->
|
|
||||||
case flatten_opaque_types(Elems, Types, []) of
|
|
||||||
{ok, ElemsFlat} -> flatten_opaque_variants(Rest, Types, [{Name, ElemsFlat} | Acc]);
|
|
||||||
Error -> Error
|
|
||||||
end;
|
|
||||||
flatten_opaque_variants([], _Types, Acc) ->
|
|
||||||
{ok, lists:reverse(Acc)}.
|
|
||||||
|
|
||||||
flatten_normalized_type(PrimitiveType, _Types) when is_atom(PrimitiveType) ->
|
|
||||||
{ok, PrimitiveType};
|
{ok, PrimitiveType};
|
||||||
flatten_normalized_type({variant, VariantsOpaque}, Types) ->
|
annotate_type_subexpressions({variant, VariantsOpaque}, Types) ->
|
||||||
case flatten_opaque_variants(VariantsOpaque, Types, []) of
|
case annotate_variants(VariantsOpaque, Types, []) of
|
||||||
{ok, Variants} -> {ok, {variant, Variants}};
|
{ok, Variants} -> {ok, {variant, Variants}};
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
flatten_normalized_type({record, FieldsOpaque}, Types) ->
|
annotate_type_subexpressions({record, FieldsOpaque}, Types) ->
|
||||||
case flatten_opaque_bindings(FieldsOpaque, Types, []) of
|
case annotate_bindings(FieldsOpaque, Types, []) of
|
||||||
{ok, Fields} -> {ok, {record, Fields}};
|
{ok, Fields} -> {ok, {record, Fields}};
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
flatten_normalized_type({T, ElemsOpaque}, Types) ->
|
annotate_type_subexpressions({T, ElemsOpaque}, Types) ->
|
||||||
case flatten_opaque_types(ElemsOpaque, Types, []) of
|
case annotate_types(ElemsOpaque, Types, []) of
|
||||||
{ok, Elems} -> {ok, {T, Elems}};
|
{ok, Elems} -> {ok, {T, Elems}};
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
annotate_bindings([{Name, T} | Rest], Types, Acc) ->
|
||||||
|
case annotate_type(T, Types) of
|
||||||
|
{ok, Type} -> annotate_bindings(Rest, Types, [{Name, Type} | Acc]);
|
||||||
|
Error -> Error
|
||||||
|
end;
|
||||||
|
annotate_bindings([], _Types, Acc) ->
|
||||||
|
{ok, lists:reverse(Acc)}.
|
||||||
|
|
||||||
|
annotate_variants([{Name, Elems} | Rest], Types, Acc) ->
|
||||||
|
case annotate_types(Elems, Types, []) of
|
||||||
|
{ok, ElemsFlat} -> annotate_variants(Rest, Types, [{Name, ElemsFlat} | Acc]);
|
||||||
|
Error -> Error
|
||||||
|
end;
|
||||||
|
annotate_variants([], _Types, Acc) ->
|
||||||
|
{ok, lists:reverse(Acc)}.
|
||||||
|
|
||||||
normalize_opaque_type(T, Types) ->
|
normalize_opaque_type(T, Types) ->
|
||||||
case type_is_expanded(T) of
|
case type_is_expanded(T) of
|
||||||
false -> normalize_opaque_type(T, Types, true);
|
false -> normalize_opaque_type(T, Types, true);
|
||||||
@ -1644,12 +1699,39 @@ substitute_opaque_type(Bindings, {var, VarName}) ->
|
|||||||
false -> {error, invalid_aci};
|
false -> {error, invalid_aci};
|
||||||
{_, TypeArg} -> {ok, TypeArg}
|
{_, TypeArg} -> {ok, TypeArg}
|
||||||
end;
|
end;
|
||||||
|
substitute_opaque_type(Bindings, {variant, Args}) ->
|
||||||
|
case substitute_variant_types(Bindings, Args, []) of
|
||||||
|
{ok, Result} -> {ok, {variant, Result}};
|
||||||
|
Error -> Error
|
||||||
|
end;
|
||||||
|
substitute_opaque_type(Bindings, {record, Args}) ->
|
||||||
|
case substitute_record_types(Bindings, Args, []) of
|
||||||
|
{ok, Result} -> {ok, {record, Result}};
|
||||||
|
Error -> Error
|
||||||
|
end;
|
||||||
substitute_opaque_type(Bindings, {Connective, Args}) ->
|
substitute_opaque_type(Bindings, {Connective, Args}) ->
|
||||||
case substitute_opaque_types(Bindings, Args, []) of
|
case substitute_opaque_types(Bindings, Args, []) of
|
||||||
{ok, Result} -> {ok, {Connective, Result}};
|
{ok, Result} -> {ok, {Connective, Result}};
|
||||||
Error -> Error
|
Error -> Error
|
||||||
end;
|
end;
|
||||||
substitute_opaque_type(_Bindings, Type) -> {ok, Type}.
|
substitute_opaque_type(_Bindings, Type) ->
|
||||||
|
{ok, Type}.
|
||||||
|
|
||||||
|
substitute_variant_types(Bindings, [{VariantName, Elements} | Rest], Acc) ->
|
||||||
|
case substitute_opaque_types(Bindings, Elements, []) of
|
||||||
|
{ok, Result} -> substitute_variant_types(Bindings, Rest, [{VariantName, Result} | Acc]);
|
||||||
|
Error -> Error
|
||||||
|
end;
|
||||||
|
substitute_variant_types(_Bindings, [], Acc) ->
|
||||||
|
{ok, lists:reverse(Acc)}.
|
||||||
|
|
||||||
|
substitute_record_types(Bindings, [{ElementName, Type} | Rest], Acc) ->
|
||||||
|
case substitute_opaque_type(Bindings, Type) of
|
||||||
|
{ok, Result} -> substitute_record_types(Bindings, Rest, [{ElementName, Result} | Acc]);
|
||||||
|
Error -> Error
|
||||||
|
end;
|
||||||
|
substitute_record_types(_Bindings, [], Acc) ->
|
||||||
|
{ok, lists:reverse(Acc)}.
|
||||||
|
|
||||||
substitute_opaque_types(Bindings, [Next | Rest], Acc) ->
|
substitute_opaque_types(Bindings, [Next | Rest], Acc) ->
|
||||||
case substitute_opaque_type(Bindings, Next) of
|
case substitute_opaque_type(Bindings, Next) of
|
||||||
@ -1708,7 +1790,7 @@ coerce({O, N, integer}, S, to_fate) when is_list(S) ->
|
|||||||
end;
|
end;
|
||||||
coerce({O, N, address}, S, to_fate) ->
|
coerce({O, N, address}, S, to_fate) ->
|
||||||
try
|
try
|
||||||
case aeser_api_encoder:decode(unicode:characters_to_binary(S)) of
|
case gmser_api_encoder:decode(unicode:characters_to_binary(S)) of
|
||||||
{account_pubkey, Key} -> {ok, {address, Key}};
|
{account_pubkey, Key} -> {ok, {address, Key}};
|
||||||
_ -> single_error({invalid, O, N, S})
|
_ -> single_error({invalid, O, N, S})
|
||||||
end
|
end
|
||||||
@ -1716,11 +1798,11 @@ coerce({O, N, address}, S, to_fate) ->
|
|||||||
error:_ -> single_error({invalid, O, N, S})
|
error:_ -> single_error({invalid, O, N, S})
|
||||||
end;
|
end;
|
||||||
coerce({_, _, address}, {address, Bin}, from_fate) ->
|
coerce({_, _, address}, {address, Bin}, from_fate) ->
|
||||||
Address = aeser_api_encoder:encode(account_pubkey, Bin),
|
Address = gmser_api_encoder:encode(account_pubkey, Bin),
|
||||||
{ok, unicode:characters_to_list(Address)};
|
{ok, unicode:characters_to_list(Address)};
|
||||||
coerce({O, N, contract}, S, to_fate) ->
|
coerce({O, N, contract}, S, to_fate) ->
|
||||||
try
|
try
|
||||||
case aeser_api_encoder:decode(unicode:characters_to_binary(S)) of
|
case gmser_api_encoder:decode(unicode:characters_to_binary(S)) of
|
||||||
{contract_pubkey, Key} -> {ok, {contract, Key}};
|
{contract_pubkey, Key} -> {ok, {contract, Key}};
|
||||||
_ -> single_error({invalid, O, N, S})
|
_ -> single_error({invalid, O, N, S})
|
||||||
end
|
end
|
||||||
@ -1728,7 +1810,7 @@ coerce({O, N, contract}, S, to_fate) ->
|
|||||||
error:_ -> single_error({invalid, O, N, S})
|
error:_ -> single_error({invalid, O, N, S})
|
||||||
end;
|
end;
|
||||||
coerce({_, _, contract}, {contract, Bin}, from_fate) ->
|
coerce({_, _, contract}, {contract, Bin}, from_fate) ->
|
||||||
Address = aeser_api_encoder:encode(contract_pubkey, Bin),
|
Address = gmser_api_encoder:encode(contract_pubkey, Bin),
|
||||||
{ok, unicode:characters_to_list(Address)};
|
{ok, unicode:characters_to_list(Address)};
|
||||||
coerce({_, _, boolean}, true, _) ->
|
coerce({_, _, boolean}, true, _) ->
|
||||||
{ok, true};
|
{ok, true};
|
||||||
@ -1995,8 +2077,7 @@ aaci_lookup_spec({aaci, _, FunDefs, _}, Fun) ->
|
|||||||
%% @doc
|
%% @doc
|
||||||
%% This function always returns 1,000,000,000 in the current version.
|
%% This function always returns 1,000,000,000 in the current version.
|
||||||
%%
|
%%
|
||||||
%% This is the minimum gas price returned by aec_tx_pool:minimum_miner_gas_price(),
|
%% This is the minimum gas price returned by aec_tx_pool:minimum_miner_gas_price()
|
||||||
%% (the default set in aeternity_config_schema.json).
|
|
||||||
%%
|
%%
|
||||||
%% Surely there can be some more nuance to this, but until a "gas station" type
|
%% Surely there can be some more nuance to this, but until a "gas station" type
|
||||||
%% market/chain survey service exists we will use this naive value as a default
|
%% market/chain survey service exists we will use this naive value as a default
|
||||||
@ -2010,16 +2091,10 @@ min_gas_price() ->
|
|||||||
|
|
||||||
-spec min_gas() -> integer().
|
-spec min_gas() -> integer().
|
||||||
%% @doc
|
%% @doc
|
||||||
%% This function always returns 20,000 in the current version.
|
%% This function always returns 200,000 in the current version.
|
||||||
%%
|
|
||||||
%% There is no actual minimum gas price, but this figure provides a lower limit toward
|
|
||||||
%% successful completion of general contract calls while not too severely limiting the
|
|
||||||
%% number of TXs that may appear in a single microblock based on the per-block gas
|
|
||||||
%% maximum (6,000,000 / 20,000 = 300 TXs in a microblock -- which at the moment seems
|
|
||||||
%% like plenty).
|
|
||||||
|
|
||||||
min_gas() ->
|
min_gas() ->
|
||||||
20000.
|
200000.
|
||||||
|
|
||||||
|
|
||||||
encode_call_data({aaci, _ContractName, FunDefs, _TypeDefs}, Fun, Args) ->
|
encode_call_data({aaci, _ContractName, FunDefs, _TypeDefs}, Fun, Args) ->
|
||||||
@ -2030,7 +2105,7 @@ encode_call_data({aaci, _ContractName, FunDefs, _TypeDefs}, Fun, Args) ->
|
|||||||
|
|
||||||
encode_call_data2(ArgDef, Fun, Args) ->
|
encode_call_data2(ArgDef, Fun, Args) ->
|
||||||
case coerce_bindings(ArgDef, Args, to_fate) of
|
case coerce_bindings(ArgDef, Args, to_fate) of
|
||||||
{ok, Coerced} -> aeb_fate_abi:create_calldata(Fun, Coerced);
|
{ok, Coerced} -> gmb_fate_abi:create_calldata(Fun, Coerced);
|
||||||
Errors -> Errors
|
Errors -> Errors
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -2050,7 +2125,7 @@ encode_call_data2(ArgDef, Fun, Args) ->
|
|||||||
%% check failed before verification was able to pass or fail (bad key encoding or similar).
|
%% check failed before verification was able to pass or fail (bad key encoding or similar).
|
||||||
|
|
||||||
verify_signature(Sig, Message, PubKey) ->
|
verify_signature(Sig, Message, PubKey) ->
|
||||||
case aeser_api_encoder:decode(PubKey) of
|
case gmser_api_encoder:decode(PubKey) of
|
||||||
{account_pubkey, PK} -> verify_signature2(Sig, Message, PK);
|
{account_pubkey, PK} -> verify_signature2(Sig, Message, PK);
|
||||||
Other -> {error, {bad_key, Other}}
|
Other -> {error, {bad_key, Other}}
|
||||||
end.
|
end.
|
||||||
@ -2060,13 +2135,7 @@ verify_signature2(Sig, Message, PK) ->
|
|||||||
% the hash is what gets signed in order to protect
|
% the hash is what gets signed in order to protect
|
||||||
% the user from accidentally signing a transaction disguised as a message.
|
% the user from accidentally signing a transaction disguised as a message.
|
||||||
%
|
%
|
||||||
% Salt the message then hash with blake2b. See:
|
% Salt the message then hash with blake2b.
|
||||||
% 1. Erlang Blake2 blake2b/2 function:
|
|
||||||
% https://gitlab.com/ioecs/eblake2/blob/60a079f00d72d1bfcc25de8e6996d28f912db3fd/src/eblake2.erl#L23-L25
|
|
||||||
% 2. SDK salting step:
|
|
||||||
% https://gitlab.com/ioecs/aepp-sdk-js/blob/370f1e30064ad0239ba59931908d9aba0a2e86b6/src/utils/crypto.ts#L171-L175
|
|
||||||
% 3. SDK hashing:
|
|
||||||
% https://gitlab.com/ioecs/aepp-sdk-js/blob/370f1e30064ad0239ba59931908d9aba0a2e86b6/src/utils/crypto.ts#L83-L85
|
|
||||||
Prefix = <<"Gajumaru Signed Message:\n">>,
|
Prefix = <<"Gajumaru Signed Message:\n">>,
|
||||||
{ok, PSize} = vencode(byte_size(Prefix)),
|
{ok, PSize} = vencode(byte_size(Prefix)),
|
||||||
{ok, MSize} = vencode(byte_size(Message)),
|
{ok, MSize} = vencode(byte_size(Message)),
|
||||||
@ -2140,3 +2209,166 @@ eu(N, Size) ->
|
|||||||
% /v3/debug/check-tx/pool/{hash}
|
% /v3/debug/check-tx/pool/{hash}
|
||||||
% /v3/debug/token-supply/height/{height}
|
% /v3/debug/token-supply/height/{height}
|
||||||
% /v3/debug/crash
|
% /v3/debug/crash
|
||||||
|
|
||||||
|
|
||||||
|
%%% Simple coerce/3 tests
|
||||||
|
|
||||||
|
% Round trip coerce run for the eunit tests below. If these results don't match
|
||||||
|
% then the test should fail.
|
||||||
|
try_coerce(Type, Sophia, Fate) ->
|
||||||
|
% Run both first, to see if they fail to produce any result.
|
||||||
|
{ok, FateActual} = coerce(Type, Sophia, to_fate),
|
||||||
|
{ok, SophiaActual} = coerce(Type, Fate, from_fate),
|
||||||
|
% Now check that the results were what we expected.
|
||||||
|
case FateActual of
|
||||||
|
Fate ->
|
||||||
|
ok;
|
||||||
|
_ ->
|
||||||
|
erlang:error({to_fate_failed, Fate, FateActual})
|
||||||
|
end,
|
||||||
|
case SophiaActual of
|
||||||
|
Sophia ->
|
||||||
|
ok;
|
||||||
|
_ ->
|
||||||
|
erlang:error({from_fate_failed, Sophia, SophiaActual})
|
||||||
|
end,
|
||||||
|
ok.
|
||||||
|
|
||||||
|
coerce_int_test() ->
|
||||||
|
{ok, Type} = annotate_type(integer, #{}),
|
||||||
|
try_coerce(Type, 123, 123).
|
||||||
|
|
||||||
|
coerce_address_test() ->
|
||||||
|
{ok, Type} = annotate_type(address, #{}),
|
||||||
|
try_coerce(Type,
|
||||||
|
"ak_2FTnrGfV8qsfHpaSEHpBrziioCpwwzLqSevHqfxQY3PaAAdARx",
|
||||||
|
{address, <<164,136,155,90,124,22,40,206,255,76,213,56,238,123,
|
||||||
|
167,208,53,78,40,235,2,163,132,36,47,183,228,151,9,
|
||||||
|
210,39,214>>}).
|
||||||
|
|
||||||
|
coerce_contract_test() ->
|
||||||
|
{ok, Type} = annotate_type(contract, #{}),
|
||||||
|
try_coerce(Type,
|
||||||
|
"ct_2FTnrGfV8qsfHpaSEHpBrziioCpwwzLqSevHqfxQY3PaAAdARx",
|
||||||
|
{contract, <<164,136,155,90,124,22,40,206,255,76,213,56,238,123,
|
||||||
|
167,208,53,78,40,235,2,163,132,36,47,183,228,151,9,
|
||||||
|
210,39,214>>}).
|
||||||
|
|
||||||
|
coerce_bool_test() ->
|
||||||
|
{ok, Type} = annotate_type(boolean, #{}),
|
||||||
|
try_coerce(Type, true, true),
|
||||||
|
try_coerce(Type, false, false).
|
||||||
|
|
||||||
|
coerce_string_test() ->
|
||||||
|
{ok, Type} = annotate_type(string, #{}),
|
||||||
|
try_coerce(Type, "hello world", <<"hello world">>).
|
||||||
|
|
||||||
|
coerce_list_test() ->
|
||||||
|
{ok, Type} = annotate_type({list, [string]}, #{}),
|
||||||
|
try_coerce(Type, ["hello world", [65, 32, 65]], [<<"hello world">>, <<65, 32, 65>>]).
|
||||||
|
|
||||||
|
coerce_map_test() ->
|
||||||
|
{ok, Type} = annotate_type({map, [string, {list, [integer]}]}, #{}),
|
||||||
|
try_coerce(Type, #{"a" => "a", "b" => "b"}, #{<<"a">> => "a", <<"b">> => "b"}).
|
||||||
|
|
||||||
|
coerce_tuple_test() ->
|
||||||
|
{ok, Type} = annotate_type({tuple, [integer, string]}, #{}),
|
||||||
|
try_coerce(Type, {123, "456"}, {tuple, {123, <<"456">>}}).
|
||||||
|
|
||||||
|
coerce_variant_test() ->
|
||||||
|
{ok, Type} = annotate_type({variant, [{"A", [integer]},
|
||||||
|
{"B", [integer, integer]}]},
|
||||||
|
#{}),
|
||||||
|
try_coerce(Type, {"A", 123}, {variant, [1, 2], 0, {123}}),
|
||||||
|
try_coerce(Type, {"B", 456, 789}, {variant, [1, 2], 1, {456, 789}}).
|
||||||
|
|
||||||
|
coerce_record_test() ->
|
||||||
|
{ok, Type} = annotate_type({record, [{"a", integer}, {"b", integer}]}, #{}),
|
||||||
|
try_coerce(Type, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}).
|
||||||
|
|
||||||
|
|
||||||
|
%%% Complex AACI paramter and namespace tests
|
||||||
|
|
||||||
|
aaci_from_string(String) ->
|
||||||
|
case so_compiler:from_string(String, [{aci, json}]) of
|
||||||
|
{ok, #{aci := ACI}} -> {ok, prepare_aaci(ACI)};
|
||||||
|
Error -> Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
namespace_coerce_test() ->
|
||||||
|
Contract = "
|
||||||
|
namespace N =
|
||||||
|
record pair = { a : int, b : int }
|
||||||
|
|
||||||
|
contract C =
|
||||||
|
entrypoint f(): N.pair = { a = 1, b = 2 }
|
||||||
|
",
|
||||||
|
{ok, AACI} = aaci_from_string(Contract),
|
||||||
|
{ok, {[], Output}} = aaci_lookup_spec(AACI, "f"),
|
||||||
|
try_coerce(Output, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}).
|
||||||
|
|
||||||
|
record_substitution_test() ->
|
||||||
|
Contract = "
|
||||||
|
contract C =
|
||||||
|
record pair('t) = { a : 't, b : 't }
|
||||||
|
entrypoint f(): pair(int) = { a = 1, b = 2 }
|
||||||
|
",
|
||||||
|
{ok, AACI} = aaci_from_string(Contract),
|
||||||
|
{ok, {[], Output}} = aaci_lookup_spec(AACI, "f"),
|
||||||
|
try_coerce(Output, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}).
|
||||||
|
|
||||||
|
tuple_substitution_test() ->
|
||||||
|
Contract = "
|
||||||
|
contract C =
|
||||||
|
type triple('t1, 't2) = int * 't1 * 't2
|
||||||
|
entrypoint f(): triple(int, string) = (1, 2, \"hello\")
|
||||||
|
",
|
||||||
|
{ok, AACI} = aaci_from_string(Contract),
|
||||||
|
{ok, {[], Output}} = aaci_lookup_spec(AACI, "f"),
|
||||||
|
try_coerce(Output, {1, 2, "hello"}, {tuple, {1, 2, <<"hello">>}}).
|
||||||
|
|
||||||
|
variant_substitution_test() ->
|
||||||
|
Contract = "
|
||||||
|
contract C =
|
||||||
|
datatype adt('a, 'b) = Left('a, 'b) | Right('b, int)
|
||||||
|
entrypoint f(): adt(string, int) = Left(\"hi\", 1)
|
||||||
|
",
|
||||||
|
{ok, AACI} = aaci_from_string(Contract),
|
||||||
|
{ok, {[], Output}} = aaci_lookup_spec(AACI, "f"),
|
||||||
|
try_coerce(Output, {"Left", "hi", 1}, {variant, [2, 2], 0, {<<"hi">>, 1}}),
|
||||||
|
try_coerce(Output, {"Right", 2, 3}, {variant, [2, 2], 1, {2, 3}}).
|
||||||
|
|
||||||
|
nested_coerce_test() ->
|
||||||
|
Contract = "
|
||||||
|
contract C =
|
||||||
|
type pair('t) = 't * 't
|
||||||
|
record r = { f1 : pair(int), f2: pair(string) }
|
||||||
|
entrypoint f(): r = { f1 = (1, 2), f2 = (\"a\", \"b\") }
|
||||||
|
",
|
||||||
|
{ok, AACI} = aaci_from_string(Contract),
|
||||||
|
{ok, {[], Output}} = aaci_lookup_spec(AACI, "f"),
|
||||||
|
try_coerce(Output,
|
||||||
|
#{ "f1" => {1, 2}, "f2" => {"a", "b"}},
|
||||||
|
{tuple, {{tuple, {1, 2}}, {tuple, {<<"a">>, <<"b">>}}}}).
|
||||||
|
|
||||||
|
state_coerce_test() ->
|
||||||
|
Contract = "
|
||||||
|
contract C =
|
||||||
|
type state = int
|
||||||
|
entrypoint init(): state = 0
|
||||||
|
",
|
||||||
|
{ok, AACI} = aaci_from_string(Contract),
|
||||||
|
{ok, {[], Output}} = aaci_lookup_spec(AACI, "init"),
|
||||||
|
try_coerce(Output, 0, 0).
|
||||||
|
|
||||||
|
param_test() ->
|
||||||
|
Contract = "
|
||||||
|
contract C =
|
||||||
|
type state = int
|
||||||
|
entrypoint init(x): state = x
|
||||||
|
",
|
||||||
|
{ok, AACI} = aaci_from_string(Contract),
|
||||||
|
{ok, {[{"x", Input}], Output}} = aaci_lookup_spec(AACI, "init"),
|
||||||
|
try_coerce(Input, 0, 0),
|
||||||
|
try_coerce(Output, 0, 0).
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
-module(hz_fetcher).
|
-module(hz_fetcher).
|
||||||
-vsn("0.2.0").
|
-vsn("0.4.0").
|
||||||
-author("Craig Everett <ceverett@tsuriai.jp>").
|
-author("Craig Everett <ceverett@tsuriai.jp>").
|
||||||
-copyright("Craig Everett <ceverett@tsuriai.jp>").
|
-copyright("Craig Everett <ceverett@tsuriai.jp>").
|
||||||
-license("MIT").
|
-license("MIT").
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
%%% @end
|
%%% @end
|
||||||
|
|
||||||
-module(hz_man).
|
-module(hz_man).
|
||||||
-vsn("0.2.0").
|
-vsn("0.4.0").
|
||||||
-behavior(gen_server).
|
-behavior(gen_server).
|
||||||
-author("Craig Everett <ceverett@tsuriai.jp>").
|
-author("Craig Everett <ceverett@tsuriai.jp>").
|
||||||
-copyright("Craig Everett <ceverett@tsuriai.jp>").
|
-copyright("Craig Everett <ceverett@tsuriai.jp>").
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
%%% @end
|
%%% @end
|
||||||
|
|
||||||
-module(hz_sup).
|
-module(hz_sup).
|
||||||
-vsn("0.2.0").
|
-vsn("0.4.0").
|
||||||
-behaviour(supervisor).
|
-behaviour(supervisor).
|
||||||
-author("Craig Everett <zxq9@zxq9.com>").
|
-author("Craig Everett <zxq9@zxq9.com>").
|
||||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||||
|
@ -2,15 +2,15 @@
|
|||||||
{type,app}.
|
{type,app}.
|
||||||
{modules,[]}.
|
{modules,[]}.
|
||||||
{prefix,"hz"}.
|
{prefix,"hz"}.
|
||||||
{author,"Craig Everett"}.
|
|
||||||
{desc,"Gajumaru interoperation library"}.
|
{desc,"Gajumaru interoperation library"}.
|
||||||
{package_id,{"otpr","hakuzaru",{0,2,0}}}.
|
{author,"Craig Everett"}.
|
||||||
{deps,[{"otpr","gmbytecode",{3,4,1}},
|
{package_id,{"otpr","hakuzaru",{0,4,0}}}.
|
||||||
|
{deps,[{"otpr","sophia",{8,0,1}},
|
||||||
|
{"otpr","gmbytecode",{3,4,1}},
|
||||||
{"otpr","gmserialization",{0,1,2}},
|
{"otpr","gmserialization",{0,1,2}},
|
||||||
{"otpr","base58",{0,1,1}},
|
{"otpr","base58",{0,1,1}},
|
||||||
{"otpr","eblake2",{1,0,1}},
|
{"otpr","eblake2",{1,0,1}},
|
||||||
{"otpr","ec_utils",{1,0,0}},
|
{"otpr","ec_utils",{1,0,0}},
|
||||||
{"otpr","aesophia",{7,1,2}},
|
|
||||||
{"otpr","zj",{1,1,0}},
|
{"otpr","zj",{1,1,0}},
|
||||||
{"otpr","getopt",{1,0,2}}]}.
|
{"otpr","getopt",{1,0,2}}]}.
|
||||||
{key_name,none}.
|
{key_name,none}.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user