From 9fa365e83f1d9fa5e757194cbc4bf968f1d44ce3 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Mon, 6 Apr 2026 10:59:45 +0900 Subject: [PATCH] WIP --- include/gdl.hrl | 5 + src/gd_con.erl | 343 +++++++++++++++++++------------------------ src/gd_gui.erl | 7 +- src/gd_lib.erl | 64 +++++++- src/gd_v_call.erl | 235 ++++++++++++++++++++++++----- src/gd_v_devman.erl | 197 ++----------------------- src/gd_v_netman.erl | 6 +- src/gd_v_wallman.erl | 6 +- 8 files changed, 439 insertions(+), 424 deletions(-) create mode 100644 include/gdl.hrl diff --git a/include/gdl.hrl b/include/gdl.hrl new file mode 100644 index 0000000..0b2f1cc --- /dev/null +++ b/include/gdl.hrl @@ -0,0 +1,5 @@ +% Widgets +-record(w, + {name = none :: atom() | {FunName :: binary(), call | dryr}, + id = 0 :: integer(), + wx = none :: none | wx:wx_object()}). diff --git a/src/gd_con.erl b/src/gd_con.erl index 710d25c..b26f0bb 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -14,9 +14,9 @@ selected/1, network/0, password/2, refresh/0, - nonce/1, spend/1, chain/1, grids/1, - sign_mess/1, sign_binary/1, sign_tx/1, sign_call/3, dry_run/2, - deploy/3, make_call/3, + nonce/1, spend/1, chain_id/0, grids/1, + sign_mess/1, sign_binary/1, sign_tx/1, sign_call/3, + prompt_call/3, make_key/6, recover_key/1, mnemonic/1, rename_key/2, drop_key/1, list_keys/0, add_node/1, set_sole_node/1]). -export([tic/1, update_balance/2]). @@ -154,11 +154,11 @@ spend(TX) -> gen_server:cast(?MODULE, {spend, TX}). --spec chain(ID) -> ok - when ID :: string(). +-spec chain_id() -> {ok, ID} + when ID :: binary(). -chain(ID) -> - gen_server:cast(?MODULE, {chain, ID}). +chain_id() -> + gen_server:cast(?MODULE, {chain_id, ID}). -spec grids(string()) -> ok. @@ -188,21 +188,15 @@ sign_tx(Request) -> gen_server:cast(?MODULE, {sign_tx, Request}). --spec sign_call(ConID, PubKey, TX) -> ok - when ConID :: gajudesk:id(), - PubKey :: gajudesk:id(), - TX :: binary(). +-spec sign_call(ChainID, PubKey, TX) -> Result + when ChainID :: binary(), + PubKey :: gajudesk:id(), + TX :: binary(), + Result :: {ok, SignedTX :: binary()}, + | {error, Reason :: term()}. -sign_call(ConID, PubKey, TX) -> - gen_server:cast(?MODULE, {sign_call, ConID, PubKey, TX}). - - --spec dry_run(ConID, TX) -> ok - when ConID :: gajudesk:id(), - TX :: binary(). - -dry_run(ConID, TX) -> - gen_server:cast(?MODULE, {dry_run, ConID, TX}). +sign_call(ChainID, PubKey, TX) -> + gen_server:call(?MODULE, {sign_call, ChainID, PubKey, TX}). -spec deploy(Build, Params, InitArgs) -> Result @@ -222,6 +216,17 @@ deploy(Build, Params, InitArgs) -> gen_server:cast(?MODULE, {deploy, Build, Params, InitArgs}). +-spec prompt_call(FunDef, ConID, Build) -> ok + when FunDef :: {FunName, FunType}, + FunName :: string(), + FunType :: call | dryr | init, + ConID :: none | binary(), + Build :: map(). % Fixme + +prompt_call(FunDef, ConID, Build) -> + gen_server:cast(?MODULE, {prompt_call, FunDef, ConID, Build}). + + -spec make_key(Type, Size, Name, Seed, Encoding, Transform) -> ok when Type :: {eddsa, ed25519}, Size :: 256, @@ -401,6 +406,12 @@ handle_call(list_keys, _, State) -> handle_call({nonce, ID}, _, State) -> Response = do_nonce(ID), {reply, Response, State}; +handle_call({chain_id, State) -> + Response = do_chain_id(State), + {reply, Response, NewState}; +handle_call({sign_call, ChainID, PubKey, TX}, _, State) -> + Response = do_sign_call(State, ChainID, PubKey, TX), + {reply, Response, State}; handle_call({open_wallet, Path, Phrase}, _, State) -> {Response, NewState} = do_open_wallet(Path, Phrase, State), {reply, Response, NewState}; @@ -456,9 +467,6 @@ handle_cast(refresh, State) -> handle_cast({spend, TX}, State) -> ok = do_spend(TX, State), {noreply, State}; -handle_cast({chain, ID}, State) -> - NewState = do_chain(ID, State), - {noreply, NewState}; handle_cast({grids, String}, State) -> ok = do_grids(String), {noreply, State}; @@ -471,15 +479,12 @@ handle_cast({sign_binary, Request}, State) -> handle_cast({sign_tx, Request}, State) -> ok = do_sign_tx(Request, State), {noreply, State}; -handle_cast({sign_call, ConID, PubKey, TX}, State) -> - ok = do_sign_call(State, ConID, PubKey, TX), - {noreply, State}; -handle_cast({dry_run, ConID, TX}, State) -> - ok = do_dry_run(ConID, TX), - {noreply, State}; handle_cast({deploy, Build, Params, InitArgs}, State) -> ok = do_deploy(Build, Params, InitArgs, State), {noreply, State}; +handle_cast({prompt_call, FunDef, ConID, Build}) -> + NewState = do_prompt_call(FunDef, ConID, Build, State), + {noreply, NewState}; handle_cast({make_key, Name, Seed, Encoding, Transform}, State) -> NewState = do_make_key(Name, Seed, Encoding, Transform, State), {noreply, NewState}; @@ -588,9 +593,11 @@ task_data(gd_v_devman, #s{}) -> %%% Network operations -do_chain(_, State) -> - tell("Would be doing chain in do_chain/2 here"), - State. +% NOTE: This is temporary. As GD becomes more chain aware this will move. +do_chain_id(#s{wallet = #wallet{chain_id = ChainID}) -> + {ok, ChainID}; +do_chain_id(_) -> + {error, no_chain}. do_add_node(New, State) -> @@ -802,41 +809,13 @@ post_grids_response(ResponseKeys, Request = #{"url" := URL}) -> end. -do_sign_call(#s{wallet = #wallet{keys = Keys, chain_id = ChainID}}, - ConID, - PubKey, - TX) -> - #key{pair = #{secret := SecKey}} = lists:keyfind(PubKey, #key.id, Keys), - SignedTX = hz:sign_tx(TX, SecKey, ChainID), - case hz:post_tx(SignedTX) of - {ok, Data = #{"tx_hash" := TXHash}} -> - ok = tell("TX succeded with: ~p", [TXHash]), - do_sign_call2(ConID, Data); - {ok, WTF} -> - gd_v_devman:trouble({error, WTF}); - Error -> - gd_v_devman:trouble(Error) - end; -do_sign_call(_, _, _, _) -> - gd_v_devman:trouble({error, no_chain}). - -do_sign_call2(ConID, #{"tx_hash" := TXHash}) -> - case hz:tx_info(TXHash) of - {ok, CallInfo = #{"call_info" := #{"return_type" := "ok"}}} -> - gd_v_devman:call_result(ConID, CallInfo); - {error, "Tx not mined"} -> - gd_v_devman:trouble({tx_hash, TXHash}); - {ok, Reason = #{"call_info" := #{"return_type" := "revert"}}} -> - gd_v_devman:trouble({error, Reason}); - Error -> - gd_v_devman:trouble(Error) - end. - - -do_dry_run(ConID, TX) -> - case hz:dry_run(TX) of - {ok, Result} -> gd_v_devman:dryrun_result(ConID, Result); - Other -> gd_v_devmam:trouble({error, ConID, Other}) +do_sign_call(#s{wallet = #wallet{keys = Keys}}, ChainID, PubKey, TX) -> + case lists:keyfind(PubKey, #key.id, Keys) of + #key{pair = #{secret := SecKey}} -> + SignedTX = hz:sign_tx(TX, SecKey, ChainID), + {ok, SignedTX}; + false -> + {error, bad_key} end. @@ -883,23 +862,26 @@ do_network(#s{wallet = #wallet{chain_id = ChainID}}) -> {ok, ChainID}. - -%%% Stateless Operations - -encrypt(Pass, Binary) -> - Flags = [{encrypt, true}, {padding, pkcs_padding}], - crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags). - - -decrypt(Pass, Binary) -> - Flags = [{encrypt, false}, {padding, pkcs_padding}], - crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags). - - -pass(none) -> - none; -pass(Phrase) -> - crypto:hash(sha3_256, Phrase). +do_prompt_call(FunDef, ConID, Build, State = #s{tasks = Tasks, prefs = Prefs}) -> + Name = {ConID, FunDef}, + case do_list_keys(State) of + {ok, Selected, KeyIDs} -> + case lists:keyfind(Name, #ui.name, Tasks) of + #ui{wx = Win} -> + ok = gd_v_call:to_front(Win), + State; + false -> + CallPrefs = maps:get(gd_v_call, Prefs, #{}), + Win = gd_v_call:start_link({CallPrefs, FunDef, ConID, Build, Selected, Keys}), + PID = wx_object:get_pid(Win), + Mon = monitor(process, PID), + UI = #ui{name = Name, pid = PID, wx = Win, mon = Mon}, + State#s{tasks = [UI | Tasks]} + end; + error -> + ok = gd_gui:trouble("ERROR: No Wallet Selected"), + State + end. do_make_key(Name, <<>>, _, Transform, State) -> @@ -950,33 +932,6 @@ do_make_key2(Name, Bin, Transform, State#s{wallet = Updated}. -base64_decode(String) -> - try - {ok, base64:decode(String)} - catch - E:R -> {E, R} - end. - - -transform({sha3, 256}) -> - fun(D) -> crypto:hash(sha3_256, D) end; -transform({sha2, 256}) -> - fun(D) -> crypto:hash(sha256, D) end; -transform({x_or, 256}) -> - fun t_xor/1. - - -t_xor(Bin) -> t_xor(Bin, <<0:256>>). - -t_xor(<>, A) -> - t_xor(T, crypto:exor(H, A)); -t_xor(<<>>, A) -> - A; -t_xor(B, A) -> - H = <<0:(256 - bit_size(B)), B/binary>>, - crypto:exor(H, A). - - do_recover_key(Mnemonic, State) -> case hz_key_master:decode(Mnemonic) of {ok, Seed} -> @@ -1005,54 +960,6 @@ do_recover_key2(Seed, State = #s{wallet = Current, wallets = Wallets, pass = Pas end. -do_mnemonic(ID, #s{wallet = #wallet{keys = Keys}}) -> - case lists:keyfind(ID, #key.id, Keys) of - #key{pair = #{secret := <>}} -> - Mnemonic = hz_key_master:encode(K), - {ok, Mnemonic}; - false -> - {error, bad_key} - end. - - -do_deploy(Build, - {PubKey, Nonce, TTL, GasPrice, Gas, Amount}, - InitArgs, - #s{wallet = #wallet{keys = Keys, chain_id = ChainID}}) -> - #key{pair = #{secret := SecKey}} = lists:keyfind(PubKey, #key.id, Keys), - case hz:contract_create_built(PubKey, - Nonce, Amount, TTL, Gas, GasPrice, - Build, InitArgs) of - {ok, CreateTX} -> do_deploy2(SecKey, CreateTX, ChainID); - Error -> gd_v_devman:trouble(Error) - end. - -do_deploy2(SecKey, CreateTX, ChainID) -> - SignedTX = hz:sign_tx(CreateTX, SecKey, ChainID), - tell(info, "SignedTX: ~p", [SignedTX]), - case hz:post_tx(SignedTX) of - {ok, Data = #{"tx_hash" := TXHash}} -> - ok = tell("Contract deploy TX succeded with: ~p", [TXHash]), - do_deploy3(Data); - {ok, WTF} -> - gd_v_devman:trouble({error, WTF}); - Error -> - gd_v_devman:trouble(Error) - end. - -do_deploy3(#{"tx_hash" := TXHash}) -> - case hz:tx_info(TXHash) of - {ok, #{"call_info" := #{"return_type" := "ok", "contract_id" := ConID}}} -> - gd_v_devman:open_contract(ConID); - {error, "Tx not mined"} -> - gd_v_devman:trouble({tx_hash, TXHash}); - {ok, Reason = #{"call_info" := #{"return_type" := "revert"}}} -> - gd_v_devman:trouble({error, Reason}); - Error -> - gd_v_devman:trouble(Error) - end. - - do_rename_key(ID, NewName, State = #s{wallet = W, wallets = Wallets, pass = Pass}) -> #wallet{name = Name, poas = POAs, keys = Keys} = W, RW = lists:keyfind(Name, #wr.name, Wallets), @@ -1105,26 +1012,6 @@ do_open_wallet(Path, Phrase, State = #s{timer = Timer}) -> end. -default_wallet(mainnet) -> - Node = #node{ip = "groot.mainnet.gajumaru.io"}, - Groot = #chain{id = <<"groot.mainnet">>, - nodes = [Node]}, - MainNet = #net{id = <<"mainnet">>, chains = [Groot]}, - #wallet{nets = [MainNet], endpoint = Node}; -default_wallet(testnet) -> - Node = #node{ip = "groot.testnet.gajumaru.io"}, - Groot = #chain{id = <<"groot.testnet">>, - nodes = [Node]}, - TestNet = #net{id = <<"testnet">>, chains = [Groot]}, - #wallet{nets = [TestNet], endpoint = Node}; -default_wallet(devnet) -> - % TODO: This accounts for the nature of devnets by defining *no* chains. - % The GUI/CON will need to manage this properly when encountered and prompt. - DevNet = #net{id = <<"devnet">>, - chains = []}, - #wallet{nets = [DevNet]}. - - do_password(none, none, State) -> State; do_password(none, New, State = #s{pass = none, @@ -1269,16 +1156,6 @@ maybe_clean(false, _) -> ok. -get_prefs(K, M, D) -> - P = maps:get(?MODULE, M, #{}), - maps:get(K, P, D). - -put_prefs(K, V, M) -> - P = maps:get(?MODULE, M, #{}), - NewP = maps:put(K, V, P), - maps:put(?MODULE, NewP, M). - - do_save(Module, Prefs, State = #s{prefs = Cached}) -> Updated = maps:put(Module, Prefs, Cached), ok = persist(Updated), @@ -1396,6 +1273,92 @@ cancel_timer(T) -> end. + +%%% Stateless Operations + +encrypt(Pass, Binary) -> + Flags = [{encrypt, true}, {padding, pkcs_padding}], + crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags). + + +decrypt(Pass, Binary) -> + Flags = [{encrypt, false}, {padding, pkcs_padding}], + crypto:crypto_one_time(aes_256_ecb, Pass, Binary, Flags). + + +pass(none) -> + none; +pass(Phrase) -> + crypto:hash(sha3_256, Phrase). + + +base64_decode(String) -> + try + {ok, base64:decode(String)} + catch + E:R -> {E, R} + end. + + +transform({sha3, 256}) -> + fun(D) -> crypto:hash(sha3_256, D) end; +transform({sha2, 256}) -> + fun(D) -> crypto:hash(sha256, D) end; +transform({x_or, 256}) -> + fun t_xor/1. + + +t_xor(Bin) -> t_xor(Bin, <<0:256>>). + +t_xor(<>, A) -> + t_xor(T, crypto:exor(H, A)); +t_xor(<<>>, A) -> + A; +t_xor(B, A) -> + H = <<0:(256 - bit_size(B)), B/binary>>, + crypto:exor(H, A). + + +do_mnemonic(ID, #s{wallet = #wallet{keys = Keys}}) -> + case lists:keyfind(ID, #key.id, Keys) of + #key{pair = #{secret := <>}} -> + Mnemonic = hz_key_master:encode(K), + {ok, Mnemonic}; + false -> + {error, bad_key} + end. + + +default_wallet(mainnet) -> + Node = #node{ip = "groot.mainnet.gajumaru.io"}, + Groot = #chain{id = <<"groot.mainnet">>, + nodes = [Node]}, + MainNet = #net{id = <<"mainnet">>, chains = [Groot]}, + #wallet{nets = [MainNet], endpoint = Node}; +default_wallet(testnet) -> + Node = #node{ip = "groot.testnet.gajumaru.io"}, + Groot = #chain{id = <<"groot.testnet">>, + nodes = [Node]}, + TestNet = #net{id = <<"testnet">>, chains = [Groot]}, + #wallet{nets = [TestNet], endpoint = Node}; +default_wallet(devnet) -> + % TODO: This accounts for the nature of devnets by defining *no* chains. + % The GUI/CON will need to manage this properly when encountered and prompt. + DevNet = #net{id = <<"devnet">>, + chains = []}, + #wallet{nets = [DevNet]}. + + +get_prefs(K, M, D) -> + P = maps:get(?MODULE, M, #{}), + maps:get(K, P, D). + +put_prefs(K, V, M) -> + P = maps:get(?MODULE, M, #{}), + NewP = maps:put(K, V, P), + maps:put(?MODULE, NewP, M). + + persist(Prefs) -> Path = prefs_path(), ok = filelib:ensure_dir(Path), diff --git a/src/gd_gui.erl b/src/gd_gui.erl index df258ea..d8e92a8 100644 --- a/src/gd_gui.erl +++ b/src/gd_gui.erl @@ -17,13 +17,9 @@ handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). -include("$zx_include/zx_logger.hrl"). -include("gd.hrl"). +-include("gdl.hrl"). --record(w, - {name = none :: atom(), - id = 0 :: integer(), - wx = none :: none | wx:wx_object()}). - -record(h, {win = none :: none | wx:wx_object(), sz = none :: none | wx:wx_object()}). @@ -337,6 +333,7 @@ handle_event(Event, State) -> handle_troubling(#s{frame = Frame}, Info) -> + ok = wxFrame:raise(Frame), zxw:show_message(Frame, Info). diff --git a/src/gd_lib.erl b/src/gd_lib.erl index 9aabc06..d288ca9 100644 --- a/src/gd_lib.erl +++ b/src/gd_lib.erl @@ -5,7 +5,15 @@ -module(gd_lib). -vsn("0.8.1"). --export([is_int/1]). +-export([is_int/1, + mono_text/2, mono_text/3, + button/2, button/3]). + +-include("$zx_include/zx_logger.hrl"). +-include("gd.hrl"). +-include("gdl.hrl"). + + -spec is_int(string()) -> boolean(). %% @doc @@ -14,3 +22,57 @@ is_int(S) -> lists:all(fun(C) -> $0 =< C andalso C =< $9 end, S). + + +-spec mono_text(Parent, Name) -> StaticText + when Parent :: wxWindow:wxWindow(), + Name :: term(), + StaticText :: #wx{}. +%% @doc +%% Creates a blank monospace font static text field. +%% @equiv mono_text(Parent, Name, ""). + +mono_text(Parent, Name) -> + mono_text(Parent, Name, ""). + + +-spec mono_text(Parent, Name, Value) -> StaticText + when Parent :: wxWindow:wxWindow(), + Name :: term(), + Value :: string(), + StaticText :: #w{}. +@doc +%% Creats a monospace font static text field with the given value. +%% This exists so that I don't have to remember the difference between how to create a styled +%% wxStaticText vs wxTextCtrl (which has to do it in a weird way because it has a much richer +%% notion of text styling). + +mono_text(Parent, Name, Value) -> + Text = wxStaticText:new(Parent, ?wxID_ANY, Value), + Font = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxFONT_STYLE_NORMAL, ?wxFONT_WEIGHT_NORMAL), + ok = + case wxStaticText:setFont(Text, Font) of + true -> ok; + false -> log(info, "wxStaticText ~p is already monospace.", [Text) + end, + #w{name = Name, id = wxStaticText:getId(Text), wx = Text}. + + +-spec button(Parent, Label) -> Button + when Parent :: wxWindow:wxWindow(), + Label :: string(), + Button :: #w{}. + +button(Parent, Label) -> + button(Parent, Label, Label). + + +-spec button(Parent, Name, Label) -> Button + when Parent :: wxWindow:wxWindow(), + Name :: term(), + Label :: string(), + Button :: #wx{}. + +button(Parent, Name, Label) -> + Button = wxButton:new(Parent, ?wxID_ANY, [{label, Label}]), + #w{name = Name, id = wxButton:getId(Button), wx = Button}. diff --git a/src/gd_v_call.erl b/src/gd_v_call.erl index a9f2060..f316bc4 100644 --- a/src/gd_v_call.erl +++ b/src/gd_v_call.erl @@ -1,4 +1,4 @@ --module(gd_v_devman). +-module(gd_v_call). -vsn("0.8.1"). -author("Craig Everett "). -copyright("QPQ AG "). @@ -7,34 +7,56 @@ -behavior(wx_object). %-behavior(gd_v). -include_lib("wx/include/wx.hrl"). --export([to_front/1]). +-export([to_front/1, result/2]). -export([start_link/1]). -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). -include("$zx_include/zx_logger.hrl"). -include("gd.hrl"). +-include("gdl.hrl"). -% Widgets --record(w, - {name = none :: atom() | {FunName :: binary(), call | dryr}, - id = 0 :: integer(), - wx = none :: none | wx:wx_object()}). - % State -record(s, - {wx = none :: none | wx:wx_object(), - frame = none :: none | wx:wx_object(), - lang = en :: en | jp, - j = none :: none | fun(), - args = [] :: [#wx{}], - ttl = #wx{} :: #wx{}, - gasprice = #wx{} :: #wx{}, - gas = #wx{} :: #wx{}, - amount = #wx{} :: #wx{}, - retry = #wx{} :: #wx{}, - copy = #wx{} :: #wx{}, - done = #wx{} :: #wx{}}). + {wx = none :: none | wx:wx_object(), + frame = none :: none | wx:wx_object(), + lang = en :: en | jp, + j = none :: none | fun(), + prefs = #{} :: map(), + con_id = <<"">> :: binary(), + fundef = none :: none | fun_def(), + build = none :: none | map(), + args = [] :: [#w{}], + ttl = #w{} :: #w{}, + gasprice = #w{} :: #w{}, + gas = #w{} :: #w{}, + amount = #w{} :: #w{}, + action = #w{} :: #w{}, + tx_hash = #w{} :: #w{}, + out = #w{} :: #w{}, + copy = #w{} :: #w{}}). + + +-type fun_name() :: string(). +-type fun_type() :: call | dryr | init. +-type fun_def() :: {fun_name(), fun_type()}. + + +%%% Interface + +-spec to_front(Win) -> ok + when Win :: wx:wx_object(). + +to_front(Win) -> + wx_object:cast(Win, to_front). + + +-spec result(Win, Outcome) -> ok + when Win :: wx:wx_object(), + Outcome :: term(). + +result(Win, Outcome) -> + wx_object:cast(Win, {result, Outcome}). @@ -44,38 +66,61 @@ start_link(Args) -> wx_object:start_link({local, ?MODULE}, ?MODULE, Args, []). -init({{FunName, FunType}, ConID, AACI}) -> - {aaci, ConName, FunSpecs, _} = AACI, - FunSpec = maps:get(FunName, FunSpecs), - CallType = - case FunType of - call -> J("Contract Call"); - dryr -> J("Dry Run") - end, - Arity = integer_to_list(length(element(1, FunSpec))), - Title = [CallType, ": ", ConName, ".", FunName, "/", Arity], +init({Prefs, {FunName, FunType}, ConID, Build, Selected, Keys}) -> Lang = maps:get(lang, Prefs, en), Trans = gd_jt:read_translations(?MODULE), J = gd_jt:j(Lang, Trans), + {aaci, ConName, FunSpecs, _} = maps:get(aaci, Build), + FunSpec = maps:get(FunName, FunSpecs), + {CallType, ActionLabel} = + case FunType of + call -> {J("Contract Call"), J("Submit Call")}; + dryr -> {J("Dry Run"), J("Submit Dry Run")}; + init -> {J("Deploy"), J("Deploy")} + end, + Arity = integer_to_list(length(element(1, FunSpec))), + Title = [CallType, ": ", ConName, ".", FunName, "/", Arity], Wx = wx:new(), Frame = wxFrame:new(Wx, ?wxID_ANY, Title), + MainSz = wxBoxSizer:new(?wxVERTICAL), + + KeySz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("Signature Key")}]), + KeyPicker = wxChoice:new(Frame, ?wxID_ANY, [{choices, Keys}]), + _ = wxStaticBoxSizer:add(KeySz, KeyPicker, zxw:flags(wide)), {ArgSz, Args} = call_arg_sizer(Frame, J, FunSpec), {ParamSz, TTL_T, GasPriceT, GasT, AmountT} = call_param_sizer(Frame, J), + Action = #w{wx = ActionBn} = gd_lib:button(Frame, ActionLabel), + + TX_Sz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("Transaction Info")}]), + TX_Hash = #w{wx = HashTxt} = gd_lib:mono_text(TX_Sz, tx_hash), + Line = wxStaticLine:new(TX_Sz, [{style, ?wxLI_HORIZONTAL}]), + Out = #w{wx = OutTxt} = gd_lib:mono_text(TX_Sz, out), + Copy = #w{wx = CopyBn} = gd_lib:button(Frame, J("Copy")), + + _ = wxStaticBoxSizer:add(TX_Sz, HashTxt, zxw:flags({wide, 5})), + _ = wxStaticBoxSizer:add(TX_Sz, Line, zxw:flags({wide, 5})), + _ = wxStaticBoxSizer:add(TX_Sz, OutTxt, zxw:flags({wide, 5})), + _ = wxStaticBoxSizer:add(TX_Sz, CopyBn, zxw:flags({wide, 5})), + + _ = wxSizer:add(MainSz, ArgSz, zxw:flags({wide, 5})), + _ = wxSizer:add(MainSz, KeySz, zxw:flags({wide, 5})), + _ = wxSizer:add(MainSz, ParamSz, zxw:flags({wide, 5})), + _ = wxSizer:add(MainSz, ActionBn, zxw:flags({wide, 5})), + _ = wxSizer:add(MainSz, TX_Sz, zxw:flags({wide, 5})), + _ = wxFrame:setSizer(Frame, MainSz), _ = wxSizer:layout(MainSz), ok = gd_v:safe_size(Frame, Prefs), ok = wxFrame:connect(Frame, close_window), ok = wxFrame:connect(Frame, command_button_clicked), -% TODO: Decide where to put the frame -- should probably be slightly randomized. -% ok = wxFrame:center(Frame), true = wxFrame:show(Frame), State = - #s{wx = Wx, frame = Frame, j = J, prefs = Prefs, - args = Args, - ttl = TTL_T, gasprice = GasPriceT, gas = GasT, amount = AmountT, - retry = RetryBn, copy = CopyBn, done = DoneBn}, + #s{wx = Wx, frame = Frame, j = J, prefs = Prefs, + con_id = ConID, build = Build, args = Args, + ttl = TTL_T, gasprice = GasPriceT, gas = GasT, amount = AmountT, + action = Action, copy = Copy}, {Frame, State}. @@ -155,6 +200,124 @@ call_param_sizer(Frame, J) -> {ParamSz, TTL_Tx, GasP_Tx, Gas_Tx, Amount_Tx}. +handle_call(Unexpected, From, State) -> + ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]), + {noreply, State}. + + +handle_cast(Unexpected, State) -> + ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), + {noreply, State}. + + +handle_info(Unexpected, State) -> + ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]), + {noreply, State}. + + +handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID}, + State = #s{action = #w{id = ID}}) -> + NewState = engage(State); + {noreply, NewState}; + +handle_event(Event, State) -> + ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]), + {noreply, State}. + + +code_change(_, State, _) -> + {ok, State}. + + +terminate(Reason, State) -> + ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), + wx:destroy(). + + +handle_troubling(State = #s{frame = Frame}, Info) -> + ok = wxFrame:raise(Frame), + ok = zxw:show_message(Frame, Info), + State. + + +engage(State = #s{j = J}) -> + case gd_con:chain_id() of + {ok, ChainID} -> engage(State, ChainID); + Error -> handle_troubling(State, Error) + end. + +engage(State = #s{fundef = {"init", init}, build = Build}, ChainID) -> + {CallerID, Nonce, TTL, GasPrice, Gas, Amount} = params(State), + Args = args(State), + case hz:contract_create_built(CallerID, + Nonce, Amount, TTL, Gas, GasPrice, + Build, InitArgs) of + {ok, CreateTX} -> deploy(State, ChainID, CallerID, CreateTX); + Error -> handle_troubling(State, Error) + end; +engage(State = #s{con_id = ConID, build = Build, fundef = {Name, Type}}, ChainID) -> + AACI = maps:get(aaci, AACI), + {PK, Nonce, TTL, GasP, Gas, Amount} = params(State), + Args = args(State), + case hz:contract_call(PK, Nonce, Gas, GasP, Amount, TTL, AACI, ConID, Name, Args) of + {ok, UnsignedTX} -> + case Type of + call -> do_call(State, ChainID, PK, UnsignedTX); + dryr -> do_dry_run(State, ConID, UnsignedTX) + end; + Error -> + handle_troubling(State, Error) + end. + + +% NEXT: Complete the signature and enter check_tx/2 + +deploy(State, ChainID, CallerID, CreateTX) -> + SignedTX = hz:sign_tx(CreateTX, SecKey, ChainID), + tell(info, "SignedTX: ~p", [SignedTX]), + case hz:post_tx(SignedTX) of + {ok, Data = #{"tx_hash" := TXHash}} -> + ok = tell("Contract deploy TX succeded with: ~p", [TXHash]), + do_deploy3(Data); + {ok, WTF} -> + gd_v_devman:trouble({error, WTF}); + Error -> + gd_v_devman:trouble(Error) + end. + +do_deploy3(#{"tx_hash" := TXHash}) -> + case hz:tx_info(TXHash) of + {ok, #{"call_info" := #{"return_type" := "ok", "contract_id" := ConID}}} -> + gd_v_devman:open_contract(ConID); + {error, "Tx not mined"} -> + gd_v_devman:trouble({tx_hash, TXHash}); + {ok, Reason = #{"call_info" := #{"return_type" := "revert"}}} -> + gd_v_devman:trouble({error, Reason}); + Error -> + gd_v_devman:trouble(Error) + end. + + +do_call(State, ChainID, CallerID, UnsignedTX) -> + case gd_con:sign_call(ChainID, CallerID, UnsignedTX) of + {ok, SignedTX} -> do_call(State, SignedTX); + Error -> handle_troubling(State, Error) + end. + +do_call(State, SignedTX) -> + case hz:post_tx(SignedTX) of + {ok, TX_Hash} -> + + +do_dry_run(State, ConID, TX) -> + case hz:dry_run(TX) of + {ok, Result} -> gd_v_devman:dryrun_result(ConID, Result); + Other -> gd_v_devmam:trouble({error, ConID, Other}) + end. + ok = gd_con:dry_run(ConID, TX), + State. + + textify({integer, _, _}) -> "int"; textify({boolean, _, _}) -> "bool"; textify({{bytes, [I]}, _, _}) -> io_lib:format("bytes(~w)", [I]); diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index eaa98be..4944691 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -14,14 +14,9 @@ handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). -include("$zx_include/zx_logger.hrl"). -include("gd.hrl"). +-include("gdl.hrl"). -% Widgets --record(w, - {name = none :: atom() | {FunName :: binary(), call | dryr}, - id = 0 :: integer(), - wx = none :: none | wx:wx_object()}). - % Contract functions in an ACI -record(f, {name = <<"">> :: binary(), @@ -316,108 +311,16 @@ style(#s{code = {_, Pages}}, Win, Event) -> tell("Received bogus style event.~nWin: ~p~nEvent: ~p", [Win, Event]) end. -clicked(State = #s{cons = {Consbook, Contracts}}, Name) -> - case wxNotebook:getSelection(Consbook) of - ?wxNOT_FOUND -> - ok = tell(warning, "Inconcievable! No notebook page is selected!"), - State; - Index -> - #c{id = ConID, build = #{aaci := AACI}} = lists:nth(Index + 1, Contracts), - - clicked2(State, Contract, Name) - end. - -clicked2(State, Contract, Name) -> - case gd_con:list_keys() of - {ok, 0, []} -> - handle_troubling(State, "No keys exist in the current wallet."); - {ok, Selected, Keys} -> - clicked3(State, Contract, Name, Selected, Keys); - error -> - handle_troubling(State, "No wallet is selected!") - end. - -clicked3(State = #s{frame = Frame, j = J}, Contract, FunDef, Selected, Keys) -> - #c{id = ConID, build = #{aaci := AACI}} = Contract, - ok = gd_con:make_call(FunDef, ConID, AACI), - State. - - - Dialog = wxDialog:new(Frame, ?wxID_ANY, Label), - Sizer = wxBoxSizer:new(?wxVERTICAL), - KeySz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Signature Key")}]), - KeyPicker = wxChoice:new(Dialog, ?wxID_ANY, [{choices, Keys}]), - _ = wxStaticBoxSizer:add(KeySz, KeyPicker, zxw:flags(wide)), - ok = wxChoice:setSelection(KeyPicker, Selected - 1), - {ArgSz, ArgControls, Dimensions} = call_arg_sizer(Dialog, J, FunSpec), - {ParamSz, TTL_Tx, GasP_Tx, Gas_Tx, Amount_Tx} = call_param_sizer(Dialog, J), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - Affirm = wxButton:new(Dialog, ?wxID_OK), - Cancel = wxButton:new(Dialog, ?wxID_CANCEL), - _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags({wide, 5})), - _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags({wide, 5})), - _ = wxSizer:add(Sizer, KeySz, zxw:flags({base, 5})), - _ = wxSizer:add(Sizer, ArgSz, zxw:flags({wide, 5})), - _ = wxSizer:add(Sizer, ParamSz, zxw:flags({base, 5})), - _ = wxSizer:add(Sizer, ButtSz, zxw:flags({base, 5})), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, Dimensions), - ok = wxDialog:center(Dialog), - Outcome = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - ID = wxChoice:getString(KeyPicker, wxChoice:getSelection(KeyPicker)), - PK = unicode:characters_to_binary(ID), - Controls = - [{{"TTL", fun gt_0/1}, TTL_Tx}, - {{"Gas Price", fun gt_0/1}, GasP_Tx}, - {{"Gas", fun gt_0/1}, Gas_Tx}, - {{"Amount", fun gte_0/1}, Amount_Tx}], - case call_params(Controls) of - {ok, [TTL, GasP, Gas, Amount]} -> - {Message, CArgs} = extract_args(ArgControls), - ok = gd_v_devman:write_console(ConID, Message), - {ok, Nonce} = hz:next_nonce(PK), - CallParams = {PK, Nonce, TTL, GasP, Gas, Amount}, - {ok, CallParams, CArgs}; - E -> - E - end; - ?wxID_CANCEL -> - cancel +clicked(State = #s{cons = {Consbook, Contracts}}, FunDef) -> + ok = + case wxNotebook:getSelection(Consbook) of + ?wxNOT_FOUND -> + tell(warning, "Inconcievable! No deployed contract page is selected!"); + Index -> + #c{id = ConID, build = Build} = lists:nth(Index + 1, Contracts), + gd_con:prompt_call(FunDef, ConID, Build) end, - ok = wxDialog:destroy(Dialog), - case Outcome of - {ok, Params, Args} -> clicked4(State, Contract, Name, Params, Args); - cancel -> State; - Error -> handle_troubling(State, Error) - end. - - -clicked4(State, - #c{id = ConID, build = #{aaci := AACI}}, - {Name, Type}, - {PK, Nonce, TTL, GasP, Gas, Amt}, - Args) -> - case hz:contract_call(PK, Nonce, Gas, GasP, Amt, TTL, AACI, ConID, Name, Args) of - {ok, UnsignedTX} -> - case Type of - call -> do_call(State, ConID, PK, UnsignedTX); - dryr -> do_dry_run(State, ConID, UnsignedTX) - end; - Error -> - handle_troubling(State, Error), - State - end. - -do_call(State, ConID, CallerID, UnsignedTX) -> - ok = gd_con:sign_call(ConID, CallerID, UnsignedTX), - State. - -do_dry_run(State, ConID, TX) -> - ok = gd_con:dry_run(ConID, TX), State. @@ -562,82 +465,11 @@ deploy(State = #s{code = {Codebook, Pages}}) -> end. deploy2(State, Source) -> - case compile(Source) of -% TODO: Make hz accept either the aaci or the aci, preferring the aaci if present - {ok, Build} -> - {aaci, ContractName, Funs, _} = maps:get(aaci, Build), - ok = tell(info, "Deploying Contract: ~p", [ContractName]), - InitSpec = maps:get("init", Funs), - deploy3(State, InitSpec, Build); - Other -> - ok = tell(info, "Compilation Failed!~n~tp", [Other]), - State - end. - -deploy3(State, InitSpec, Build) -> - case gd_con:list_keys() of - {ok, 0, []} -> - handle_troubling(State, "No keys exist in the current wallet."); - {ok, Selected, Keys} -> - deploy4(State, InitSpec, Build, Selected, Keys); - error -> - handle_troubling(State, "No wallet is selected!") - end. - -deploy4(State = #s{frame = Frame, j = J}, InitSpec, Build, Selected, Keys) -> - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Deploy Contract")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - KeySz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Signature Key")}]), - KeyPicker = wxChoice:new(Dialog, ?wxID_ANY, [{choices, Keys}]), - _ = wxStaticBoxSizer:add(KeySz, KeyPicker, zxw:flags(wide)), - ok = wxChoice:setSelection(KeyPicker, Selected - 1), - {ArgSz, ArgControls, Dimensions} = call_arg_sizer(Dialog, J, InitSpec), - {ParamSz, TTL_Tx, GasP_Tx, Gas_Tx, Amount_Tx} = call_param_sizer(Dialog, J), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - Affirm = wxButton:new(Dialog, ?wxID_OK), - Cancel = wxButton:new(Dialog, ?wxID_CANCEL), - _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags({wide, 5})), - _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags({wide, 5})), - _ = wxSizer:add(Sizer, KeySz, zxw:flags({base, 5})), - _ = wxSizer:add(Sizer, ArgSz, zxw:flags({wide, 5})), - _ = wxSizer:add(Sizer, ParamSz, zxw:flags({base, 5})), - _ = wxSizer:add(Sizer, ButtSz, zxw:flags({base, 5})), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, Dimensions), - ok = wxDialog:center(Dialog), - Outcome = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - ID = wxChoice:getString(KeyPicker, wxChoice:getSelection(KeyPicker)), - PK = unicode:characters_to_binary(ID), - Controls = - [{{"TTL", fun gt_0/1}, TTL_Tx}, - {{"Gas Price", fun gt_0/1}, GasP_Tx}, - {{"Gas", fun gt_0/1}, Gas_Tx}, - {{"Amount", fun gte_0/1}, Amount_Tx}], - case call_params(Controls) of - {ok, [TTL, GasP, Gas, Amount]} -> - {Message, CArgs} = extract_args(ArgControls), - ok = tell(Message), - {ok, Nonce} = hz:next_nonce(PK), - DeployParams = {PK, Nonce, TTL, GasP, Gas, Amount}, - {ok, DeployParams, CArgs}; - E -> - E - end; - ?wxID_CANCEL -> - cancel + ok = + case compile(Source) of + {ok, Build} -> gd_con:prompt_call({"init", init}, init, Build); + Other -> tell(info, "Compilation Failed!~n~tp", [Other]) end, - ok = wxDialog:destroy(Dialog), - case Outcome of - {ok, Params, Args} -> deploy5(State, Build, Params, Args); - cancel -> State; - Error -> handle_troubling(State, Error) - end. - -deploy5(State, Build, Params, Args) -> - ok = gd_con:deploy(Build, Params, Args), State. @@ -669,7 +501,8 @@ open(State = #s{frame = Frame, j = J}) -> 1 -> hash; ?wxNOT_FOUND -> none end; - ?wxID_CANCEL -> cancel + ?wxID_CANCEL -> + cancel end, ok = wxDialog:destroy(Dialog), case Choice of diff --git a/src/gd_v_netman.erl b/src/gd_v_netman.erl index deeec13..4ae4abf 100644 --- a/src/gd_v_netman.erl +++ b/src/gd_v_netman.erl @@ -14,13 +14,9 @@ handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). -include("$zx_include/zx_logger.hrl"). -include("gd.hrl"). +-include("gdl.hrl"). --record(w, - {name = none :: atom(), - id = 0 :: integer(), - wx = none :: none | wx:wx_object()}). - -record(s, {wx = none :: none | wx:wx_object(), frame = none :: none | wx:wx_object(), diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index 40c2d5e..c4e4f22 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -28,13 +28,9 @@ handle_call/3, handle_cast/2, handle_info/2, handle_event/2]). -include("$zx_include/zx_logger.hrl"). -include("gd.hrl"). +-include("gdl.hrl"). --record(w, - {name = none :: atom(), - id = 0 :: integer(), - wx = none :: none | wx:wx_object()}). - -record(s, {wx = none :: none | wx:wx_object(), frame = none :: none | wx:wx_object(),