From 5fdf1de4effba205625271cce8e496647385b0ab Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 25 Feb 2025 10:00:14 +0900 Subject: [PATCH 01/16] Adjustments --- src/hz.erl | 112 ++++++++++++++++++++++++++--------------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index f48f4dd..9745a25 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -48,7 +48,7 @@ dry_run/1, dry_run/2, dry_run/3, dry_run_map/1, tx/1, tx_info/1, post_tx/1, - contract/1, contract_code/1, + contract/1, contract_code/1, contract_source/1, contract_poi/1, name/1, % channel/1, @@ -64,6 +64,7 @@ contract_create_built/3, contract_create/8, prepare_contract/1, + prepare_aaci/1, aaci_lookup_spec/2, contract_call/5, contract_call/6, @@ -216,7 +217,7 @@ -spec network_id() -> NetworkID when NetworkID :: string() | none. %% @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 %% 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. @@ -249,8 +250,8 @@ chain_nodes() -> when List :: [chain_node()], Reason :: {invalid, [term()]}. %% @doc -%% Sets the AE nodes that are intended to be used as your interface to the AE peer -%% network. The common situation is that your project runs a non-mining AE node as +%% 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 node as %% 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. %% The Hakuzaru manager will load balance by round-robin distribution. @@ -304,7 +305,7 @@ timeout(MS) -> -%%% AE node JSON query interface functions +%%% JSON query interface functions -spec top_height() -> {ok, Height} | {error, Reason} @@ -656,13 +657,13 @@ dry_run_map(Map) -> %% @doc %% 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 %% information provided by an AACI. decode_bytearray_fate(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 <<>> -> {ok, none}; <<"Out of gas">> -> {error, out_of_gas}; @@ -670,7 +671,7 @@ decode_bytearray_fate(EncodedStr) -> % FIXME there may be other errors that are encoded directly into % the byte array. We could try and catch to at least return % *something* for cases that we don't already detect. - Object = aeb_fate_encoding:deserialize(Binary), + Object = gmb_fate_encoding:deserialize(Binary), {ok, Object} end. @@ -758,6 +759,21 @@ contract_code(ID) -> 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} when ID :: contract_id(), Bytecode :: contract_byte_array(), @@ -952,7 +968,7 @@ contract_create(CreatorID, Path, InitArgs) -> %% %%
  • %% GasPrice: -%% 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 %% 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. @@ -995,12 +1011,12 @@ contract_create(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Path, InitArgs) -> {ok, Source} -> Dir = filename:dirname(Path), {ok, CWD} = file:get_cwd(), - SrcDir = aeso_utils:canonical_dir(Path), + SrcDir = so_utils:canonical_dir(Path), Options = [{aci, json}, {src_file, Path}, {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, Source, Options, InitArgs); Error -> @@ -1037,7 +1053,7 @@ contract_create_built(CreatorID, Compiled, InitArgs) -> contract_create2(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Source, Options, InitArgs) -> - case aeso_compiler:from_string(Source, Options) of + case so_compiler:from_string(Source, Options) of {ok, Compiled} -> contract_create3(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, InitArgs); @@ -1058,22 +1074,22 @@ contract_create3(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, InitArg contract_create4(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData) -> PK = unicode:characters_to_binary(CreatorID), 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) catch Error:Reason -> {Error, Reason} end. contract_create5(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData) -> - Code = aeser_contract_code:serialize(Compiled), - Source = maps:get(contract_source, Compiled, <<>>), + Code = gmser_contract_code:serialize(Compiled), + Source = unicode:characters_to_binary(maps:get(contract_source, Compiled, <<>>)), VM = 1, ABI = 1, <> = <>, ContractCreateVersion = 1, Type = contract_create_tx, Fields = - [{owner_id, aeser_id:create(account, OwnerID)}, + [{owner_id, gmser_id:create(account, OwnerID)}, {nonce, Nonce}, {code, Code}, {source, Source}, @@ -1096,9 +1112,9 @@ contract_create5(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData) {gas_price, int}, {gas, int}, {call_data, binary}], - TXB = aeser_chain_objects:serialize(Type, ContractCreateVersion, Template, Fields), + TXB = gmser_chain_objects:serialize(Type, ContractCreateVersion, Template, Fields), try - {ok, aeser_api_encoder:encode(transaction, TXB)} + {ok, gmser_api_encoder:encode(transaction, TXB)} catch error:Reason -> {error, Reason} end. @@ -1110,14 +1126,11 @@ contract_create5(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData) ACI :: tuple(), % FIXME: Change to correct Sophia record Reason :: file:posix() | bad_aci. %% @doc -%% This function reads the contents of an .aci file produced by AEL (the Gajumaru -%% Launcher). ACI data is required for the contract call encoder to function properly. +%% This function reads the contents of an .aci file. +%% 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 %% 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 -%% 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. +%% represented internally, and here we need the actual native representation. %% %% 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 @@ -1271,7 +1284,7 @@ contract_call(CallerID, Gas, AACI, ConID, Fun, Args) -> %%
  • %%
  • %% GasPrice: -%% 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 %% 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. @@ -1329,7 +1342,7 @@ contract_call(CallerID, Nonce, Gas, GP, Amount, TTL, AACI, ConID, Fun, Args) -> contract_call2(CallerID, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) -> CallerBin = unicode:characters_to_binary(CallerID), 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) catch Error:Reason -> {Error, Reason} @@ -1338,7 +1351,7 @@ contract_call2(CallerID, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) -> contract_call3(PK, Nonce, Gas, GasPrice, Amount, TTL, ConID, CallData) -> ConBin = unicode:characters_to_binary(ConID), 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) catch Error:Reason -> {Error, Reason} @@ -1349,9 +1362,9 @@ contract_call4(PK, Nonce, Gas, GasPrice, Amount, TTL, CK, CallData) -> CallVersion = 1, Type = contract_call_tx, Fields = - [{caller_id, aeser_id:create(account, PK)}, + [{caller_id, gmser_id:create(account, PK)}, {nonce, Nonce}, - {contract_id, aeser_id:create(contract, CK)}, + {contract_id, gmser_id:create(contract, CK)}, {abi_version, ABI}, {ttl, TTL}, {amount, Amount}, @@ -1368,9 +1381,9 @@ contract_call4(PK, Nonce, Gas, GasPrice, Amount, TTL, CK, CallData) -> {gas_price, int}, {gas, int}, {call_data, binary}], - TXB = aeser_chain_objects:serialize(Type, CallVersion, Template, Fields), + TXB = gmser_chain_objects:serialize(Type, CallVersion, Template, Fields), try - {ok, aeser_api_encoder:encode(transaction, TXB)} + {ok, gmser_api_encoder:encode(transaction, TXB)} catch error:Reason -> {error, Reason} end. @@ -1385,7 +1398,7 @@ contract_call4(PK, Nonce, Gas, GasPrice, Amount, TTL, CK, CallData) -> %% of calldata 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)}; Error -> Error end. @@ -1453,7 +1466,7 @@ simplify_args(#{name := NameBin, type := TypeDef}, 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 aebytecode can serialize. Second, we need +% 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`. @@ -1708,7 +1721,7 @@ coerce({O, N, integer}, S, to_fate) when is_list(S) -> end; coerce({O, N, address}, S, to_fate) -> 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}}; _ -> single_error({invalid, O, N, S}) end @@ -1716,11 +1729,11 @@ coerce({O, N, address}, S, to_fate) -> error:_ -> single_error({invalid, O, N, S}) end; 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)}; coerce({O, N, contract}, S, to_fate) -> 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}}; _ -> single_error({invalid, O, N, S}) end @@ -1728,7 +1741,7 @@ coerce({O, N, contract}, S, to_fate) -> error:_ -> single_error({invalid, O, N, S}) end; 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)}; coerce({_, _, boolean}, true, _) -> {ok, true}; @@ -1995,8 +2008,7 @@ aaci_lookup_spec({aaci, _, FunDefs, _}, Fun) -> %% @doc %% 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(), -%% (the default set in aeternity_config_schema.json). +%% This is the minimum gas price returned by aec_tx_pool:minimum_miner_gas_price() %% %% 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 @@ -2010,16 +2022,10 @@ min_gas_price() -> -spec min_gas() -> integer(). %% @doc -%% This function always returns 20,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). +%% This function always returns 200,000 in the current version. min_gas() -> - 20000. + 200000. encode_call_data({aaci, _ContractName, FunDefs, _TypeDefs}, Fun, Args) -> @@ -2030,7 +2036,7 @@ encode_call_data({aaci, _ContractName, FunDefs, _TypeDefs}, Fun, Args) -> encode_call_data2(ArgDef, Fun, Args) -> 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 end. @@ -2050,7 +2056,7 @@ encode_call_data2(ArgDef, Fun, Args) -> %% check failed before verification was able to pass or fail (bad key encoding or similar). 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); Other -> {error, {bad_key, Other}} end. @@ -2060,13 +2066,7 @@ verify_signature2(Sig, Message, PK) -> % the hash is what gets signed in order to protect % the user from accidentally signing a transaction disguised as a message. % - % Salt the message then hash with blake2b. See: - % 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 + % Salt the message then hash with blake2b. Prefix = <<"Gajumaru Signed Message:\n">>, {ok, PSize} = vencode(byte_size(Prefix)), {ok, MSize} = vencode(byte_size(Message)), From e53da36f2e0f8b02910a45303fc707fafb828343 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Tue, 25 Feb 2025 10:06:12 +0900 Subject: [PATCH 02/16] Update deps and verup --- ebin/hakuzaru.app | 2 +- src/hakuzaru.erl | 2 +- src/hz.erl | 2 +- src/hz_fetcher.erl | 2 +- src/hz_man.erl | 2 +- src/hz_sup.erl | 2 +- zomp.meta | 8 ++++---- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ebin/hakuzaru.app b/ebin/hakuzaru.app index 9c10ad5..7553cfa 100644 --- a/ebin/hakuzaru.app +++ b/ebin/hakuzaru.app @@ -3,6 +3,6 @@ {included_applications,[]}, {applications,[stdlib,kernel]}, {description,"Gajumaru interoperation library"}, - {vsn,"0.2.0"}, + {vsn,"0.3.0"}, {modules,[hakuzaru,hz,hz_fetcher,hz_man,hz_sup]}, {mod,{hakuzaru,[]}}]}. diff --git a/src/hakuzaru.erl b/src/hakuzaru.erl index e5e4493..f0a926d 100644 --- a/src/hakuzaru.erl +++ b/src/hakuzaru.erl @@ -6,7 +6,7 @@ %%% @end -module(hakuzaru). --vsn("0.2.0"). +-vsn("0.3.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/hz.erl b/src/hz.erl index 9745a25..0001cef 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -23,7 +23,7 @@ %%% @end -module(hz). --vsn("0.2.0"). +-vsn("0.3.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/hz_fetcher.erl b/src/hz_fetcher.erl index a7803be..7481d2c 100644 --- a/src/hz_fetcher.erl +++ b/src/hz_fetcher.erl @@ -1,5 +1,5 @@ -module(hz_fetcher). --vsn("0.2.0"). +-vsn("0.3.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("MIT"). diff --git a/src/hz_man.erl b/src/hz_man.erl index 31f7e66..1ead871 100644 --- a/src/hz_man.erl +++ b/src/hz_man.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_man). --vsn("0.2.0"). +-vsn("0.3.0"). -behavior(gen_server). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/src/hz_sup.erl b/src/hz_sup.erl index 80b1f60..1b67475 100644 --- a/src/hz_sup.erl +++ b/src/hz_sup.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_sup). --vsn("0.2.0"). +-vsn("0.3.0"). -behaviour(supervisor). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/zomp.meta b/zomp.meta index 3de14e3..8f748f5 100644 --- a/zomp.meta +++ b/zomp.meta @@ -2,15 +2,15 @@ {type,app}. {modules,[]}. {prefix,"hz"}. -{author,"Craig Everett"}. {desc,"Gajumaru interoperation library"}. -{package_id,{"otpr","hakuzaru",{0,2,0}}}. -{deps,[{"otpr","gmbytecode",{3,4,1}}, +{author,"Craig Everett"}. +{package_id,{"otpr","hakuzaru",{0,3,0}}}. +{deps,[{"otpr","sophia",{8,0,1}}, + {"otpr","gmbytecode",{3,4,1}}, {"otpr","gmserialization",{0,1,2}}, {"otpr","base58",{0,1,1}}, {"otpr","eblake2",{1,0,1}}, {"otpr","ec_utils",{1,0,0}}, - {"otpr","aesophia",{7,1,2}}, {"otpr","zj",{1,1,0}}, {"otpr","getopt",{1,0,2}}]}. {key_name,none}. From cf8970dad24dfa480b88be69fd305b5d5f36b7a0 Mon Sep 17 00:00:00 2001 From: SpiveeWorks Date: Fri, 24 Jan 2025 16:17:25 +1100 Subject: [PATCH 03/16] Add unit tests for some simple coercions --- src/hz.erl | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/hz.erl b/src/hz.erl index 0001cef..017ca71 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -77,6 +77,7 @@ -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 network_id() :: string(). @@ -2140,3 +2141,58 @@ eu(N, Size) -> % /v3/debug/check-tx/pool/{hash} % /v3/debug/token-supply/height/{height} % /v3/debug/crash + + +try_coerce(Type, Sophia, Fate) -> + FateActual = coerce(Type, Sophia, to_fate), + SophiaActual = coerce(Type, Fate, from_fate), + case {ok, Fate} == FateActual of + true -> + ok; + false -> + erlang:error({to_fate_failed, Fate, FateActual}) + end, + case {ok, Sophia} == SophiaActual of + true -> + ok; + false -> + erlang:error({from_fate_failed, Sophia, SophiaActual}) + end, + ok. + +coerce_int_test() -> + {ok, Type} = flatten_opaque_type(integer, #{}), + try_coerce(Type, 123, 123). + +coerce_record_test() -> + {ok, Type} = flatten_opaque_type({record, [{"a", integer}, {"b", integer}]}, #{}), + try_coerce(Type, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}). + +aaci_from_string(String) -> + case so_compiler:from_string(String, [{aci, json}]) of + {ok, #{aci := ACI}} -> {ok, prepare_aaci(ACI)}; + Error -> Error + end. + +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}}). + +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}}). + From 04311f9c9958e9948a7f273df3e144db4db01e85 Mon Sep 17 00:00:00 2001 From: SpiveeWorks Date: Fri, 24 Jan 2025 18:24:08 +1100 Subject: [PATCH 04/16] Even more unit tests Trying to test all the basic types that coerce covers, and a couple more type parameter and nested cases. --- src/hz.erl | 113 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 10 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index 017ca71..84b1c85 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -2143,6 +2143,8 @@ eu(N, Size) -> % /v3/debug/crash +%%% Simple coerce/3 tests + try_coerce(Type, Sophia, Fate) -> FateActual = coerce(Type, Sophia, to_fate), SophiaActual = coerce(Type, Fate, from_fate), @@ -2164,26 +2166,63 @@ coerce_int_test() -> {ok, Type} = flatten_opaque_type(integer, #{}), try_coerce(Type, 123, 123). +coerce_address_test() -> + {ok, Type} = flatten_opaque_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} = flatten_opaque_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} = flatten_opaque_type(boolean, #{}), + try_coerce(Type, true, true), + try_coerce(Type, false, false). + +coerce_string_test() -> + {ok, Type} = flatten_opaque_type(string, #{}), + try_coerce(Type, "hello world", <<"hello world">>). + +coerce_list_test() -> + {ok, Type} = flatten_opaque_type({list, [string]}, #{}), + try_coerce(Type, ["hello world", [65, 32, 65]], [<<"hello world">>, <<65, 32, 65>>]). + +coerce_map_test() -> + {ok, Type} = flatten_opaque_type({map, [string, {list, [integer]}]}, #{}), + try_coerce(Type, #{"a" => "a", "b" => "b"}, #{<<"a">> => "a", <<"b">> => "b"}). + +coerce_tuple_test() -> + {ok, Type} = flatten_opaque_type({tuple, [integer, string]}, #{}), + try_coerce(Type, {123, "456"}, {tuple, {123, <<"456">>}}). + +coerce_variant_test() -> + {ok, Type} = flatten_opaque_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} = flatten_opaque_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. -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}}). - namespace_coerce_test() -> Contract = " namespace N = @@ -2196,3 +2235,57 @@ namespace_coerce_test() -> {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). + From 4441f6ff37d2d50be0a45fbc170e6f178ea70b27 Mon Sep 17 00:00:00 2001 From: SpiveeWorks Date: Fri, 24 Jan 2025 18:33:47 +1100 Subject: [PATCH 05/16] Also prepare AACI for namespace types --- src/hz.erl | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index 84b1c85..7143e30 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -1405,10 +1405,7 @@ prepare_contract(File) -> end. prepare_aaci(ACI) -> - % NOTE this will also pick up the main contract; as a result the main - % contract extraction later on shouldn't bother with typedefs. - Contracts = [ContractDef || #{contract := ContractDef} <- ACI], - Types = simplify_contract_types(Contracts, #{}), + Types = lists:foldl(fun prepare_namespace_types/2, #{}, ACI), [{NameBin, SpecDefs}] = [{N, F} @@ -1419,22 +1416,29 @@ prepare_aaci(ACI) -> Specs = simplify_specs(SpecDefs, #{}, Types), {aaci, Name, Specs, Types}. -simplify_contract_types([], Types) -> - Types; -simplify_contract_types([Next | Rest], Types) -> - TypeDefs = maps:get(typedefs, Next), - NameBin = maps:get(name, Next), +prepare_namespace_types(#{namespace := NS}, Types) -> + prepare_namespace_types2(NS, false, Types); +prepare_namespace_types(#{contract := NS}, Types) -> + prepare_namespace_types2(NS, true, Types). + +prepare_namespace_types2(NS, IsContract, Types) -> + TypeDefs = maps:get(typedefs, NS), + NameBin = maps:get(name, NS), Name = binary_to_list(NameBin), - Types2 = maps:put(Name, {[], contract}, Types), - Types3 = case maps:find(state, Next) of + Types2 = case IsContract of + true -> + maps:put(Name, {[], contract}, Types); + false -> + Types + end, + Types3 = case maps:find(state, NS) of {ok, StateDefACI} -> 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(TypeDefs, Types3, Name ++ "."). simplify_typedefs([], Types, _NamePrefix) -> Types; From 7eb29827a61f5228063d6347b9703ac95cb854fd Mon Sep 17 00:00:00 2001 From: SpiveeWorks Date: Fri, 24 Jan 2025 18:50:41 +1100 Subject: [PATCH 06/16] Fix type substitution into variants and records Variants were working by accident, since {variant, [{"VariantName", [Element]}]} had a similar enough form to the opaque types that would come from something like `type1(type2(int))`, but records were not working, since they have a different form. Now both are handled explicitly so that only the intended forms of each are handled. --- src/hz.erl | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/hz.erl b/src/hz.erl index 7143e30..55fb64c 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -1662,12 +1662,39 @@ substitute_opaque_type(Bindings, {var, VarName}) -> false -> {error, invalid_aci}; {_, TypeArg} -> {ok, TypeArg} 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}) -> case substitute_opaque_types(Bindings, Args, []) of {ok, Result} -> {ok, {Connective, Result}}; Error -> Error 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) -> case substitute_opaque_type(Bindings, Next) of From ad7be7c8dbd1b7b5217ec47e44bb104cde300f8f Mon Sep 17 00:00:00 2001 From: SpiveeWorks Date: Fri, 31 Jan 2025 13:54:42 +1100 Subject: [PATCH 07/16] Break up prepare_aaci logic Now we convert the ACI into trees of opaque types, then flatten the tree into a map and a list of function specs, and only then dereference the types in the function specs down to our accelerated annotated types. --- src/hz.erl | 200 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 117 insertions(+), 83 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index 55fb64c..d330a11 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -1405,69 +1405,141 @@ prepare_contract(File) -> end. prepare_aaci(ACI) -> - Types = lists:foldl(fun prepare_namespace_types/2, #{}, ACI), + % We want to take the types represented by the ACI, things like N1.T(N2.T), + % and dereference them down to concrete types like + % {tuple, [integer, string]}. Our type dereferencing algorithms + % 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. + Specs = expand_contract_specs(OpaqueSpecs, TypeDefs, #{}), + + {aaci, Name, Specs, TypeDefs}. + +expand_contract_specs([], _Types, Specs) -> + Specs; +expand_contract_specs([{Name, ArgsOpaque, ResultOpaque} | Rest], Types, Specs) -> + {ok, Args} = flatten_opaque_types(ArgsOpaque, Types, []), + {ok, Result} = flatten_opaque_type(ResultOpaque, Types), + NewSpecs = maps:put(Name, {Args, Result}, Specs), + expand_contract_specs(Rest, Types, NewSpecs). + +convert_aci_types(ACI) -> + % Find the main contract, so we can get the specifications of its + % entrypoints. [{NameBin, SpecDefs}] = [{N, F} || #{contract := #{kind := contract_main, functions := F, name := N}} <- ACI], Name = binary_to_list(NameBin), - Specs = simplify_specs(SpecDefs, #{}, Types), - {aaci, Name, Specs, Types}. + % Turn these specifications into opaque types that we can reason about. + Specs = lists:map(fun convert_function_spec/1, SpecDefs), -prepare_namespace_types(#{namespace := NS}, Types) -> - prepare_namespace_types2(NS, false, Types); -prepare_namespace_types(#{contract := NS}, Types) -> - prepare_namespace_types2(NS, true, Types). + % These specifications can reference other type definitions from the main + % contract and any other namespaces, so extract these types and convert + % them too. + TypeDefTree = lists:map(fun convert_namespace_typedefs/1, ACI), + % 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, #{}), -prepare_namespace_types2(NS, IsContract, Types) -> + % 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), + ArgTypes = lists:map(fun convert_arg/1, Args), + ResultType = opaque_type([], Result), + {Name, ArgTypes, ResultType}. + +convert_arg(#{name := NameBin, type := TypeDef}) -> + Name = binary_to_list(NameBin), + {ok, Type} = opaque_type([], TypeDef), + {Name, Type}. + +convert_namespace_typedefs(#{namespace := NS}) -> + convert_namespace_typedefs2(NS, false); +convert_namespace_typedefs(#{contract := NS}) -> + convert_namespace_typedefs2(NS, true). + +convert_namespace_typedefs2(NS, IsContract) -> TypeDefs = maps:get(typedefs, NS), NameBin = maps:get(name, NS), Name = binary_to_list(NameBin), - Types2 = case IsContract of - true -> - maps:put(Name, {[], contract}, Types); - false -> - Types - end, - Types3 = case maps:find(state, NS) of - {ok, StateDefACI} -> - StateDefOpaque = opaque_type([], StateDefACI), - maps:put(Name ++ ".state", {[], StateDefOpaque}, Types2); - error -> - Types2 - end, - simplify_typedefs(TypeDefs, Types3, Name ++ "."). + ContractAsType = case IsContract of + true -> {Name, [], contract}; + false -> [] + end, + State = case maps:find(state, NS) of + {ok, StateDefACI} -> + StateDefOpaque = opaque_type([], StateDefACI), + {Name ++ ".state", [], StateDefOpaque}; + error -> + [] + end, + ExplicitTypeDefs = convert_explicit_typedefs(TypeDefs, Name ++ ".", []), + % Throw all the weird sources of types into one messy deeplist. + [ContractAsType, State, ExplicitTypeDefs]. -simplify_typedefs([], Types, _NamePrefix) -> +% The easiest step, turn a deep list of opaque types into a map. +collect_opaque_types([], Types) -> Types; -simplify_typedefs([Next | Rest], Types, NamePrefix) -> - #{name := NameBin, vars := ParamDefs, typedef := T} = Next, +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_explicit_typedefs([], _NamePrefix, Converted) -> + Converted; +convert_explicit_typedefs([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], - Type = opaque_type(Params, T), - NewTypes = maps:put(Name, {Params, Type}, Types), - simplify_typedefs(Rest, NewTypes, NamePrefix). + Def = opaque_type(Params, DefACI), + convert_explicit_typedefs(Rest, NamePrefix, [Converted, {Name, Params, Def}]). -simplify_specs([], Specs, _Types) -> - Specs; -simplify_specs([Next | Rest], Specs, Types) -> - #{name := NameBin, arguments := ArgDefs, returns := ResultDef} = Next, - Name = binary_to_list(NameBin), - ArgTypes = [simplify_args(Arg, Types) || Arg <- ArgDefs], - {ok, ResultType} = type(ResultDef, Types), - NewSpecs = maps:put(Name, {ArgTypes, ResultType}, Specs), - simplify_specs(Rest, NewSpecs, 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) -> + Name = opaque_type_name(NameBin), + case not is_atom(Name) and lists:member(Name, Params) of + false -> Name; + true -> {var, Name} + end; +opaque_type(Params, #{record := FieldDefs}) -> + Fields = [{binary_to_list(Name), opaque_type(Params, Type)} + || #{name := Name, type := Type} <- FieldDefs], + {record, Fields}; +opaque_type(Params, #{variant := VariantDefs}) -> + ConvertVariant = fun(Pair) -> + [{Name, Types}] = maps:to_list(Pair), + {binary_to_list(Name), [opaque_type(Params, Type) || Type <- Types]} + end, + Variants = lists:map(ConvertVariant, VariantDefs), + {variant, Variants}; +opaque_type(Params, #{tuple := TypeDefs}) -> + {tuple, [opaque_type(Params, Type) || Type <- TypeDefs]}; +opaque_type(Params, Pair) when is_map(Pair) -> + [{Name, TypeArgs}] = maps:to_list(Pair), + {opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}. -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}. +% atoms for builtins, strings (lists) for user-defined types +opaque_type_name(<<"int">>) -> integer; +opaque_type_name(<<"address">>) -> address; +opaque_type_name(<<"contract">>) -> contract; +opaque_type_name(<<"bool">>) -> boolean; +opaque_type_name(<<"option">>) -> option; +opaque_type_name(<<"list">>) -> list; +opaque_type_name(<<"map">>) -> map; +opaque_type_name(<<"string">>) -> string; +opaque_type_name(Name) -> binary_to_list(Name). % 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 @@ -1494,44 +1566,6 @@ simplify_args(#{name := NameBin, type := TypeDef}, Types) -> % can simply render the normalized type expression and know that the error will % make sense. -type(T, Types) -> - O = opaque_type([], T), - flatten_opaque_type(O, Types). - -opaque_type(Params, NameBin) when is_binary(NameBin) -> - Name = opaque_type_name(NameBin), - case not is_atom(Name) and lists:member(Name, Params) of - false -> Name; - true -> {var, Name} - end; -opaque_type(Params, #{record := FieldDefs}) -> - Fields = [{binary_to_list(Name), opaque_type(Params, Type)} - || #{name := Name, type := Type} <- FieldDefs], - {record, Fields}; -opaque_type(Params, #{variant := VariantDefs}) -> - ConvertVariant = fun(Pair) -> - [{Name, Types}] = maps:to_list(Pair), - {binary_to_list(Name), [opaque_type(Params, Type) || Type <- Types]} - end, - Variants = lists:map(ConvertVariant, VariantDefs), - {variant, Variants}; -opaque_type(Params, #{tuple := TypeDefs}) -> - {tuple, [opaque_type(Params, Type) || Type <- TypeDefs]}; -opaque_type(Params, Pair) when is_map(Pair) -> - [{Name, TypeArgs}] = maps:to_list(Pair), - {opaque_type_name(Name), [opaque_type(Params, Arg) || Arg <- TypeArgs]}. - -% atoms for builtins, lists for user defined types -opaque_type_name(<<"int">>) -> integer; -opaque_type_name(<<"address">>) -> address; -opaque_type_name(<<"contract">>) -> contract; -opaque_type_name(<<"bool">>) -> boolean; -opaque_type_name(<<"option">>) -> option; -opaque_type_name(<<"list">>) -> list; -opaque_type_name(<<"map">>) -> map; -opaque_type_name(<<"string">>) -> string; -opaque_type_name(Name) -> binary_to_list(Name). - flatten_opaque_type(T, Types) -> case normalize_opaque_type(T, Types) of {ok, AlreadyNormalized, NOpaque, NExpanded} -> From c27005c42467dcc76e058f50c579b3a2390d50c3 Mon Sep 17 00:00:00 2001 From: SpiveeWorks Date: Wed, 29 Jan 2025 17:59:13 +1100 Subject: [PATCH 08/16] Rename 'flatten' and so on to 'annotate' --- src/hz.erl | 108 +++++++++++++++++++++++++++-------------------------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index d330a11..4cefd8d 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -1414,19 +1414,13 @@ prepare_aaci(ACI) -> {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. - Specs = expand_contract_specs(OpaqueSpecs, TypeDefs, #{}), + % 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}. -expand_contract_specs([], _Types, Specs) -> - Specs; -expand_contract_specs([{Name, ArgsOpaque, ResultOpaque} | Rest], Types, Specs) -> - {ok, Args} = flatten_opaque_types(ArgsOpaque, Types, []), - {ok, Result} = flatten_opaque_type(ResultOpaque, Types), - NewSpecs = maps:put(Name, {Args, Result}, Specs), - expand_contract_specs(Rest, Types, NewSpecs). - convert_aci_types(ACI) -> % Find the main contract, so we can get the specifications of its % entrypoints. @@ -1552,10 +1546,10 @@ opaque_type_name(Name) -> binary_to_list(Name). % 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 +% 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 flattened types will each be +% 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. @@ -1566,16 +1560,24 @@ opaque_type_name(Name) -> binary_to_list(Name). % can simply render the normalized type expression and know that the error will % make sense. -flatten_opaque_type(T, Types) -> +annotate_function_specs([], _Types, Specs) -> + Specs; +annotate_function_specs([{Name, ArgsOpaque, ResultOpaque} | Rest], Types, Specs) -> + {ok, Args} = annotate_types(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 {ok, AlreadyNormalized, NOpaque, NExpanded} -> - flatten_opaque_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types); + annotate_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types); Error -> Error end. -flatten_opaque_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types) -> - case flatten_normalized_type(NExpanded, Types) of +annotate_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types) -> + case annotate_type_subexpressions(NExpanded, Types) of {ok, Flat} -> case AlreadyNormalized of true -> {ok, {T, already_normalized, Flat}}; @@ -1585,48 +1587,48 @@ flatten_opaque_type2(T, AlreadyNormalized, NOpaque, NExpanded, Types) -> Error end. -flatten_opaque_types([T | Rest], Types, Acc) -> - case flatten_opaque_type(T, Types) of - {ok, Type} -> flatten_opaque_types(Rest, Types, [Type | Acc]); +annotate_types([T | Rest], Types, Acc) -> + case annotate_type(T, Types) of + {ok, Type} -> annotate_types(Rest, Types, [Type | Acc]); Error -> Error end; -flatten_opaque_types([], _Types, Acc) -> +annotate_types([], _Types, Acc) -> {ok, lists:reverse(Acc)}. -flatten_opaque_bindings([{Name, T} | Rest], Types, Acc) -> - 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) -> +annotate_type_subexpressions(PrimitiveType, _Types) when is_atom(PrimitiveType) -> {ok, PrimitiveType}; -flatten_normalized_type({variant, VariantsOpaque}, Types) -> - case flatten_opaque_variants(VariantsOpaque, Types, []) of +annotate_type_subexpressions({variant, VariantsOpaque}, Types) -> + case annotate_variants(VariantsOpaque, Types, []) of {ok, Variants} -> {ok, {variant, Variants}}; Error -> Error end; -flatten_normalized_type({record, FieldsOpaque}, Types) -> - case flatten_opaque_bindings(FieldsOpaque, Types, []) of +annotate_type_subexpressions({record, FieldsOpaque}, Types) -> + case annotate_bindings(FieldsOpaque, Types, []) of {ok, Fields} -> {ok, {record, Fields}}; Error -> Error end; -flatten_normalized_type({T, ElemsOpaque}, Types) -> - case flatten_opaque_types(ElemsOpaque, Types, []) of +annotate_type_subexpressions({T, ElemsOpaque}, Types) -> + case annotate_types(ElemsOpaque, Types, []) of {ok, Elems} -> {ok, {T, Elems}}; Error -> Error 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) -> case type_is_expanded(T) of false -> normalize_opaque_type(T, Types, true); @@ -2228,11 +2230,11 @@ try_coerce(Type, Sophia, Fate) -> ok. coerce_int_test() -> - {ok, Type} = flatten_opaque_type(integer, #{}), + {ok, Type} = annotate_type(integer, #{}), try_coerce(Type, 123, 123). coerce_address_test() -> - {ok, Type} = flatten_opaque_type(address, #{}), + {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, @@ -2240,7 +2242,7 @@ coerce_address_test() -> 210,39,214>>}). coerce_contract_test() -> - {ok, Type} = flatten_opaque_type(contract, #{}), + {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, @@ -2248,35 +2250,35 @@ coerce_contract_test() -> 210,39,214>>}). coerce_bool_test() -> - {ok, Type} = flatten_opaque_type(boolean, #{}), + {ok, Type} = annotate_type(boolean, #{}), try_coerce(Type, true, true), try_coerce(Type, false, false). coerce_string_test() -> - {ok, Type} = flatten_opaque_type(string, #{}), + {ok, Type} = annotate_type(string, #{}), try_coerce(Type, "hello world", <<"hello world">>). coerce_list_test() -> - {ok, Type} = flatten_opaque_type({list, [string]}, #{}), + {ok, Type} = annotate_type({list, [string]}, #{}), try_coerce(Type, ["hello world", [65, 32, 65]], [<<"hello world">>, <<65, 32, 65>>]). coerce_map_test() -> - {ok, Type} = flatten_opaque_type({map, [string, {list, [integer]}]}, #{}), + {ok, Type} = annotate_type({map, [string, {list, [integer]}]}, #{}), try_coerce(Type, #{"a" => "a", "b" => "b"}, #{<<"a">> => "a", <<"b">> => "b"}). coerce_tuple_test() -> - {ok, Type} = flatten_opaque_type({tuple, [integer, string]}, #{}), + {ok, Type} = annotate_type({tuple, [integer, string]}, #{}), try_coerce(Type, {123, "456"}, {tuple, {123, <<"456">>}}). coerce_variant_test() -> - {ok, Type} = flatten_opaque_type({variant, [{"A", [integer]}, + {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} = flatten_opaque_type({record, [{"a", integer}, {"b", integer}]}, #{}), + {ok, Type} = annotate_type({record, [{"a", integer}, {"b", integer}]}, #{}), try_coerce(Type, #{"a" => 123, "b" => 456}, {tuple, {123, 456}}). From 4e71d3215baee9d81ef1b0ea42b515cc306b37ed Mon Sep 17 00:00:00 2001 From: SpiveeWorks Date: Wed, 29 Jan 2025 19:16:44 +1100 Subject: [PATCH 09/16] Factor handling of different ACI typedef cases A lot of this complexity was a consequence of trying to avoid redundant extraction of the namespace's/contract's name, so on the other hand letting it be redundant made all of the complexity kind of evaporate. Add to that that we're now building a little deep list and then flattening it, and this logic was able to get really neat in a way that I couldn't work out a year ago. --- src/hz.erl | 60 ++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index 4cefd8d..664baf1 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -1457,30 +1457,38 @@ convert_arg(#{name := NameBin, type := TypeDef}) -> {Name, Type}. convert_namespace_typedefs(#{namespace := NS}) -> - convert_namespace_typedefs2(NS, false); + Name = namespace_name(NS), + convert_typedefs(NS, Name); convert_namespace_typedefs(#{contract := NS}) -> - convert_namespace_typedefs2(NS, true). + Name = namespace_name(NS), + ImplicitTypes = convert_implicit_types(NS, Name), + ExplicitTypes = convert_typedefs(NS, Name), + [ImplicitTypes, ExplicitTypes]. -convert_namespace_typedefs2(NS, IsContract) -> - TypeDefs = maps:get(typedefs, NS), - NameBin = maps:get(name, NS), - Name = binary_to_list(NameBin), - ContractAsType = case IsContract of - true -> {Name, [], contract}; - false -> [] - end, - State = case maps:find(state, NS) of - {ok, StateDefACI} -> - StateDefOpaque = opaque_type([], StateDefACI), - {Name ++ ".state", [], StateDefOpaque}; - error -> - [] - end, - ExplicitTypeDefs = convert_explicit_typedefs(TypeDefs, Name ++ ".", []), - % Throw all the weird sources of types into one messy deeplist. - [ContractAsType, State, ExplicitTypeDefs]. +namespace_name(#{name := NameBin}) -> + binary_to_list(NameBin). + +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}]). -% The easiest step, turn a deep list of opaque types into a map. collect_opaque_types([], Types) -> Types; collect_opaque_types([L | R], Types) -> @@ -1489,16 +1497,6 @@ collect_opaque_types([L | R], Types) -> collect_opaque_types({Name, Params, Def}, Types) -> maps:put(Name, {Params, Def}, Types). - -convert_explicit_typedefs([], _NamePrefix, Converted) -> - Converted; -convert_explicit_typedefs([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_explicit_typedefs(Rest, NamePrefix, [Converted, {Name, Params, Def}]). - % 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) -> From 36a9b17b783ad963dc525a9a452f9865a6fbfcd6 Mon Sep 17 00:00:00 2001 From: SpiveeWorks Date: Thu, 27 Feb 2025 23:12:33 +1100 Subject: [PATCH 10/16] Clean up try_coerce slightly --- src/hz.erl | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index 664baf1..fda0311 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -2210,19 +2210,23 @@ eu(N, Size) -> %%% 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) -> - FateActual = coerce(Type, Sophia, to_fate), - SophiaActual = coerce(Type, Fate, from_fate), - case {ok, Fate} == FateActual of - true -> + % 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; - false -> + _ -> erlang:error({to_fate_failed, Fate, FateActual}) end, - case {ok, Sophia} == SophiaActual of - true -> + case SophiaActual of + Sophia -> ok; - false -> + _ -> erlang:error({from_fate_failed, Sophia, SophiaActual}) end, ok. From d83ac6eb59a791b94d3d6c346311ce511fa6433a Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 27 Feb 2025 22:21:34 +0900 Subject: [PATCH 11/16] verup --- ebin/hakuzaru.app | 2 +- src/hakuzaru.erl | 2 +- src/hz.erl | 2 +- src/hz_fetcher.erl | 2 +- src/hz_man.erl | 2 +- src/hz_sup.erl | 2 +- zomp.meta | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ebin/hakuzaru.app b/ebin/hakuzaru.app index 7553cfa..b2de17c 100644 --- a/ebin/hakuzaru.app +++ b/ebin/hakuzaru.app @@ -3,6 +3,6 @@ {included_applications,[]}, {applications,[stdlib,kernel]}, {description,"Gajumaru interoperation library"}, - {vsn,"0.3.0"}, + {vsn,"0.3.1"}, {modules,[hakuzaru,hz,hz_fetcher,hz_man,hz_sup]}, {mod,{hakuzaru,[]}}]}. diff --git a/src/hakuzaru.erl b/src/hakuzaru.erl index f0a926d..d134594 100644 --- a/src/hakuzaru.erl +++ b/src/hakuzaru.erl @@ -6,7 +6,7 @@ %%% @end -module(hakuzaru). --vsn("0.3.0"). +-vsn("0.3.1"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/hz.erl b/src/hz.erl index fda0311..1fec2c3 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -23,7 +23,7 @@ %%% @end -module(hz). --vsn("0.3.0"). +-vsn("0.3.1"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/hz_fetcher.erl b/src/hz_fetcher.erl index 7481d2c..66de62d 100644 --- a/src/hz_fetcher.erl +++ b/src/hz_fetcher.erl @@ -1,5 +1,5 @@ -module(hz_fetcher). --vsn("0.3.0"). +-vsn("0.3.1"). -author("Craig Everett "). -copyright("Craig Everett "). -license("MIT"). diff --git a/src/hz_man.erl b/src/hz_man.erl index 1ead871..9cdfe85 100644 --- a/src/hz_man.erl +++ b/src/hz_man.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_man). --vsn("0.3.0"). +-vsn("0.3.1"). -behavior(gen_server). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/src/hz_sup.erl b/src/hz_sup.erl index 1b67475..90bec77 100644 --- a/src/hz_sup.erl +++ b/src/hz_sup.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_sup). --vsn("0.3.0"). +-vsn("0.3.1"). -behaviour(supervisor). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/zomp.meta b/zomp.meta index 8f748f5..8be1d9a 100644 --- a/zomp.meta +++ b/zomp.meta @@ -2,9 +2,9 @@ {type,app}. {modules,[]}. {prefix,"hz"}. -{desc,"Gajumaru interoperation library"}. {author,"Craig Everett"}. -{package_id,{"otpr","hakuzaru",{0,3,0}}}. +{desc,"Gajumaru interoperation library"}. +{package_id,{"otpr","hakuzaru",{0,3,1}}}. {deps,[{"otpr","sophia",{8,0,1}}, {"otpr","gmbytecode",{3,4,1}}, {"otpr","gmserialization",{0,1,2}}, From ede98b9e96bf4dda6fcdf67f82a3363744be1fc5 Mon Sep 17 00:00:00 2001 From: SpiveeWorks Date: Fri, 28 Feb 2025 23:59:00 +1100 Subject: [PATCH 12/16] Fix function spec AACI preparation --- src/hz.erl | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index 1fec2c3..27a22cf 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -1453,7 +1453,7 @@ convert_function_spec(#{name := NameBin, arguments := Args, returns := Result}) convert_arg(#{name := NameBin, type := TypeDef}) -> Name = binary_to_list(NameBin), - {ok, Type} = opaque_type([], TypeDef), + Type = opaque_type([], TypeDef), {Name, Type}. convert_namespace_typedefs(#{namespace := NS}) -> @@ -1561,7 +1561,7 @@ opaque_type_name(Name) -> binary_to_list(Name). annotate_function_specs([], _Types, Specs) -> Specs; annotate_function_specs([{Name, ArgsOpaque, ResultOpaque} | Rest], Types, Specs) -> - {ok, Args} = annotate_types(ArgsOpaque, Types, []), + {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). @@ -2358,3 +2358,14 @@ state_coerce_test() -> {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). + From 4efc38d319f0657a9ad210e843c6ab126eab6918 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Sat, 1 Mar 2025 12:42:23 +0900 Subject: [PATCH 13/16] Make dryrun operate on the top block. --- src/hz.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index 27a22cf..f6e1854 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -619,9 +619,9 @@ dry_run(TX) -> %% supplied accounts. dry_run(TX, Accounts) -> - case kb_current_hash() of - {ok, Hash} -> dry_run(TX, Accounts, Hash); - Error -> Error + case top_block() of + {ok, #{"hash" := Hash}} -> dry_run(TX, Accounts, Hash); + Error -> Error end. From 23b6256aaee911e8f805b6740598aa687985953a Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Sat, 1 Mar 2025 12:47:41 +0900 Subject: [PATCH 14/16] verup --- ebin/hakuzaru.app | 2 +- src/hakuzaru.erl | 2 +- src/hz.erl | 2 +- src/hz_fetcher.erl | 2 +- src/hz_man.erl | 2 +- src/hz_sup.erl | 2 +- zomp.meta | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ebin/hakuzaru.app b/ebin/hakuzaru.app index b2de17c..5670719 100644 --- a/ebin/hakuzaru.app +++ b/ebin/hakuzaru.app @@ -3,6 +3,6 @@ {included_applications,[]}, {applications,[stdlib,kernel]}, {description,"Gajumaru interoperation library"}, - {vsn,"0.3.1"}, + {vsn,"0.3.2"}, {modules,[hakuzaru,hz,hz_fetcher,hz_man,hz_sup]}, {mod,{hakuzaru,[]}}]}. diff --git a/src/hakuzaru.erl b/src/hakuzaru.erl index d134594..dbed4b7 100644 --- a/src/hakuzaru.erl +++ b/src/hakuzaru.erl @@ -6,7 +6,7 @@ %%% @end -module(hakuzaru). --vsn("0.3.1"). +-vsn("0.3.2"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/hz.erl b/src/hz.erl index f6e1854..948f1ef 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -23,7 +23,7 @@ %%% @end -module(hz). --vsn("0.3.1"). +-vsn("0.3.2"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/hz_fetcher.erl b/src/hz_fetcher.erl index 66de62d..d9b3049 100644 --- a/src/hz_fetcher.erl +++ b/src/hz_fetcher.erl @@ -1,5 +1,5 @@ -module(hz_fetcher). --vsn("0.3.1"). +-vsn("0.3.2"). -author("Craig Everett "). -copyright("Craig Everett "). -license("MIT"). diff --git a/src/hz_man.erl b/src/hz_man.erl index 9cdfe85..bd0fc12 100644 --- a/src/hz_man.erl +++ b/src/hz_man.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_man). --vsn("0.3.1"). +-vsn("0.3.2"). -behavior(gen_server). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/src/hz_sup.erl b/src/hz_sup.erl index 90bec77..856067c 100644 --- a/src/hz_sup.erl +++ b/src/hz_sup.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_sup). --vsn("0.3.1"). +-vsn("0.3.2"). -behaviour(supervisor). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/zomp.meta b/zomp.meta index 8be1d9a..0a9753b 100644 --- a/zomp.meta +++ b/zomp.meta @@ -4,7 +4,7 @@ {prefix,"hz"}. {author,"Craig Everett"}. {desc,"Gajumaru interoperation library"}. -{package_id,{"otpr","hakuzaru",{0,3,1}}}. +{package_id,{"otpr","hakuzaru",{0,3,2}}}. {deps,[{"otpr","sophia",{8,0,1}}, {"otpr","gmbytecode",{3,4,1}}, {"otpr","gmserialization",{0,1,2}}, From b37f1e1efe181da29bea4a73c3a58335a3dda50a Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Sat, 1 Mar 2025 14:00:44 +0900 Subject: [PATCH 15/16] Expose complete deployment parameters --- src/hz.erl | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/hz.erl b/src/hz.erl index 948f1ef..2335cbd 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -62,6 +62,7 @@ min_gas_price/0, contract_create/3, contract_create_built/3, + contract_create_built/8, contract_create/8, prepare_contract/1, prepare_aaci/1, @@ -1025,6 +1026,16 @@ contract_create(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Path, InitArgs) -> 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 when CreatorID :: unicode:chardata(), Compiled :: map(), @@ -1045,43 +1056,35 @@ contract_create_built(CreatorID, Compiled, InitArgs) -> TTL = Height + 262980, Gas = 500000, GasPrice = min_gas_price(), - contract_create3(CreatorID, Nonce, - Amount, TTL, Gas, GasPrice, - Compiled, InitArgs); + contract_create_built(CreatorID, Nonce, + Amount, TTL, Gas, GasPrice, + Compiled, InitArgs); Error -> Error end. -contract_create2(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Source, Options, InitArgs) -> - case so_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) -> +contract_create_built(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, InitArgs) -> AACI = prepare_aaci(maps:get(aci, Compiled)), case encode_call_data(AACI, "init", InitArgs) of {ok, CallData} -> - contract_create4(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, - Compiled, CallData); + assemble_calldata(CreatorID, Nonce, Amount, TTL, Gas, GasPrice, + Compiled, CallData); Error -> Error 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), try {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 Error:Reason -> {Error, Reason} end. -contract_create5(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData) -> +assemble_calldata2(OwnerID, Nonce, Amount, TTL, Gas, GasPrice, Compiled, CallData) -> Code = gmser_contract_code:serialize(Compiled), Source = unicode:characters_to_binary(maps:get(contract_source, Compiled, <<>>)), VM = 1, From b6cb79d81e957a1e4e98a66f539a25e2d72b6d47 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Sat, 1 Mar 2025 14:04:48 +0900 Subject: [PATCH 16/16] verup --- ebin/hakuzaru.app | 2 +- src/hakuzaru.erl | 2 +- src/hz.erl | 2 +- src/hz_fetcher.erl | 2 +- src/hz_man.erl | 2 +- src/hz_sup.erl | 2 +- zomp.meta | 4 ++-- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ebin/hakuzaru.app b/ebin/hakuzaru.app index 5670719..38ed048 100644 --- a/ebin/hakuzaru.app +++ b/ebin/hakuzaru.app @@ -3,6 +3,6 @@ {included_applications,[]}, {applications,[stdlib,kernel]}, {description,"Gajumaru interoperation library"}, - {vsn,"0.3.2"}, + {vsn,"0.4.0"}, {modules,[hakuzaru,hz,hz_fetcher,hz_man,hz_sup]}, {mod,{hakuzaru,[]}}]}. diff --git a/src/hakuzaru.erl b/src/hakuzaru.erl index dbed4b7..27b897a 100644 --- a/src/hakuzaru.erl +++ b/src/hakuzaru.erl @@ -6,7 +6,7 @@ %%% @end -module(hakuzaru). --vsn("0.3.2"). +-vsn("0.4.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/hz.erl b/src/hz.erl index 2335cbd..255aef8 100644 --- a/src/hz.erl +++ b/src/hz.erl @@ -23,7 +23,7 @@ %%% @end -module(hz). --vsn("0.3.2"). +-vsn("0.4.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/hz_fetcher.erl b/src/hz_fetcher.erl index d9b3049..07116dc 100644 --- a/src/hz_fetcher.erl +++ b/src/hz_fetcher.erl @@ -1,5 +1,5 @@ -module(hz_fetcher). --vsn("0.3.2"). +-vsn("0.4.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("MIT"). diff --git a/src/hz_man.erl b/src/hz_man.erl index bd0fc12..82aa6f1 100644 --- a/src/hz_man.erl +++ b/src/hz_man.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_man). --vsn("0.3.2"). +-vsn("0.4.0"). -behavior(gen_server). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/src/hz_sup.erl b/src/hz_sup.erl index 856067c..e9f98b8 100644 --- a/src/hz_sup.erl +++ b/src/hz_sup.erl @@ -9,7 +9,7 @@ %%% @end -module(hz_sup). --vsn("0.3.2"). +-vsn("0.4.0"). -behaviour(supervisor). -author("Craig Everett "). -copyright("Craig Everett "). diff --git a/zomp.meta b/zomp.meta index 0a9753b..bd3847a 100644 --- a/zomp.meta +++ b/zomp.meta @@ -2,9 +2,9 @@ {type,app}. {modules,[]}. {prefix,"hz"}. -{author,"Craig Everett"}. {desc,"Gajumaru interoperation library"}. -{package_id,{"otpr","hakuzaru",{0,3,2}}}. +{author,"Craig Everett"}. +{package_id,{"otpr","hakuzaru",{0,4,0}}}. {deps,[{"otpr","sophia",{8,0,1}}, {"otpr","gmbytecode",{3,4,1}}, {"otpr","gmserialization",{0,1,2}},