diff --git a/ebin/gajudesk.app b/ebin/gajudesk.app index 662b0c2..293be9a 100644 --- a/ebin/gajudesk.app +++ b/ebin/gajudesk.app @@ -3,7 +3,7 @@ {registered,[]}, {included_applications,[]}, {applications,[stdlib,kernel,sasl,ssl]}, - {vsn,"0.8.1"}, + {vsn,"0.9.0"}, {modules,[gajudesk,gd_con,gd_grids,gd_gui,gd_jt,gd_lib, gd_m_spend,gd_m_wallet_importer,gd_sophia_editor, gd_sup,gd_v,gd_v_devman,gd_v_netman,gd_v_wallman]}, diff --git a/include/gdl.hrl b/include/gdl.hrl new file mode 100644 index 0000000..45d951d --- /dev/null +++ b/include/gdl.hrl @@ -0,0 +1,5 @@ +% Widgets +-record(w, + {name = none :: string() | atom() | {FunName :: binary(), call | dryr}, + id = 0 :: integer(), + wx = none :: none | wx:wx_object()}). diff --git a/src/gajudesk.erl b/src/gajudesk.erl index 28d0fcf..f2eb4a1 100644 --- a/src/gajudesk.erl +++ b/src/gajudesk.erl @@ -3,7 +3,7 @@ %%% @end -module(gajudesk). --vsn("0.8.1"). +-vsn("0.9.0"). -behavior(application). -author("Craig Everett "). -copyright("QPQ AG "). diff --git a/src/gd_con.erl b/src/gd_con.erl index 88b9e71..f655c57 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -3,7 +3,7 @@ %%% @end -module(gd_con). --vsn("0.8.1"). +-vsn("0.9.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). @@ -14,9 +14,10 @@ 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, + nonce/1, spend/1, chain_id/0, grids/1, + sign_mess/1, sign_binary/1, sign_tx/1, sign_call/3, + deploy/1, prompt_call/3, list_calls/0, + open_contract/1, open_contract/2, show_call/2, show_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]). @@ -26,7 +27,6 @@ -export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]). -include("$zx_include/zx_logger.hrl"). - -include("gd.hrl"). @@ -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:call(?MODULE, chain_id). -spec grids(string()) -> ok. @@ -188,38 +188,92 @@ 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}). +sign_call(ChainID, PubKey, TX) -> + gen_server:call(?MODULE, {sign_call, ChainID, PubKey, TX}). --spec dry_run(ConID, TX) -> ok - when ConID :: gajudesk:id(), - TX :: binary(). +-spec deploy(Build) -> ok + when Build :: map(). -dry_run(ConID, TX) -> - gen_server:cast(?MODULE, {dry_run, ConID, TX}). +deploy(Build) -> + gen_server:cast(?MODULE, {deploy, Build}). --spec deploy(Build, Params, InitArgs) -> Result - when Build :: map(), - Params :: {PK :: gajudesk:id(), - Nonce :: non_neg_integer(), - TTL :: pos_integer(), - GasP :: pos_integer(), - Gas :: pos_integer(), - Amount :: pos_integer()}, - InitArgs :: [Arg :: string()], - Result :: {ok, TX_Hash :: gajudesk:id()} - | {error, Reason}, - Reason :: term(). % FIXME +-spec prompt_call(FunDef, ConID, Build) -> ok + when FunDef :: {FunName, FunType}, + FunName :: string(), + FunType :: call | dryr | init, + ConID :: none | string(), + Build :: map(). % Fixme -deploy(Build, Params, InitArgs) -> - gen_server:cast(?MODULE, {deploy, Build, Params, InitArgs}). +prompt_call(FunDef, ConID, Build) -> + gen_server:cast(?MODULE, {prompt_call, FunDef, ConID, Build}). + + +-spec list_calls() -> Calls + when Calls :: [{Name, Process}], + Name :: term(), + Process :: wx:wx_object() | pid(). + +%% @doc +%% List any active contract call tasks. + +list_calls() -> + gen_server:call(?MODULE, list_calls). + + +-spec open_contract(ConID) -> ok + when ConID :: string(). +%% @doc +%% @equiv open_contract(ConID, true). + +open_contract(ConID) -> + open_contract(ConID, true). + + +-spec open_contract(ConID, DevmanToFront) -> ok + when ConID :: string(), + DevmanToFront :: boolean(). +%% @doc +%% Ask the controller to tell the devman interface to open a deployed contract. +%% The controller will start the devman if it isn't already on. + +open_contract(ConID, DevmanToFront) when is_binary(ConID) -> + gen_server:cast(?MODULE, {open_contract, ConID, DevmanToFront}); +open_contract(ConID, DevmanToFront) when is_list(ConID) -> + open_contract(list_to_binary(ConID), DevmanToFront). + + +-spec show_call(ConID, Info) -> ok + when ConID :: string(), + Info :: map(). +%% @doc +%% @equiv show_call(ConID, Info, true). + +show_call(ConID, Info) -> + show_call(ConID, Info, true). + + +-spec show_call(ConID, Info, DevmanToFront) -> ok + when ConID :: string(), + Info :: map(), + DevmanToFront :: boolean(). +%% @doc +%% Ask the controller to tell the devman interface to dislpay the result of a call +%% to the indicated contract. Opens the contract in question if it isn't alread open. +%% Starts the devman if it isn't already running. + +show_call(ConID, Info, DevmanToFront) when is_binary(ConID) -> + gen_server:cast(?MODULE, {show_call, ConID, Info, DevmanToFront}); +show_call(ConID, Info, DevmanToFront) when is_list(ConID) -> + show_call(list_to_binary(ConID), Info, DevmanToFront). -spec make_key(Type, Size, Name, Seed, Encoding, Transform) -> ok @@ -401,6 +455,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, State}; +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 +516,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 +528,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), +handle_cast({deploy, Build}, State) -> + ok = do_deploy(Build, State), {noreply, State}; +handle_cast({prompt_call, FunDef, ConID, Build}, State) -> + 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}; @@ -492,6 +546,12 @@ handle_cast({rename_key, ID, NewName}, State) -> handle_cast({drop_key, ID}, State) -> NewState = do_drop_key(ID, State), {noreply, NewState}; +handle_cast({open_contract, ConID, ToFront}, State) -> + NewState = do_open_contract(ConID, ToFront, State), + {noreply, NewState}; +handle_cast({show_call, ConID, Info, ToFront}, State) -> + NewState = do_show_call(ConID, Info, ToFront, State), + {noreply, NewState}; handle_cast({add_node, New}, State) -> NewState = do_add_node(New, State), {noreply, NewState}; @@ -559,11 +619,29 @@ terminate(Reason, _) -> %%% GUI doers +-spec do_show_ui(Name, State) -> NewState + when Name :: ui_name() | term(), + State :: state(), + NewState :: state(). -do_show_ui(Name, State = #s{tasks = Tasks, prefs = Prefs}) -> +do_show_ui(Name, State) -> + do_show_ui(Name, true, State). + + +-spec do_show_ui(Name, ToFront, State) -> NewState + when Name :: ui_name() | term(), + ToFront :: boolean(), + State :: state(), + NewState :: state(). + +do_show_ui(Name, ToFront, State = #s{tasks = Tasks, prefs = Prefs}) -> case lists:keyfind(Name, #ui.name, Tasks) of #ui{wx = Win} -> - ok = Name:to_front(Win), + ok = + case ToFront of + true -> Name:to_front(Win); + false -> ok + end, State; false -> TaskPrefs = maps:get(Name, Prefs, #{}), @@ -588,9 +666,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) -> @@ -632,23 +712,26 @@ do_refresh(State = #s{wallet = #wallet{endpoint = Node}}) -> do_refresh2(ChainID, State = #s{wallet = W = #wallet{poas = POAs}}) -> - CheckBalance = check_balance(ChainID), + CheckBalance = check_balance(ChainID, "gaju"), NewPOAs = lists:map(CheckBalance, POAs), ok = gd_gui:show(NewPOAs), NewW = W#wallet{chain_id = ChainID, poas = NewPOAs}, State#s{wallet = NewW}. -check_balance(ChainID) -> - fun(This = #poa{id = ID, balances = [#balance{coin = "gaju", total = OldBalance}]}) -> +check_balance(ChainID, Coin) -> + fun(This = #poa{id = ID, balances = Balances}) -> + #balance{dist = Dist} = lists:keyfind(Coin, #balance.coin, Balances), + Old = proplists:get_value(ChainID, Dist, 0), Pucks = case hz:acc(ID) of - {ok, #{"balance" := P}} -> P; - {error, "Account not found"} -> 0; - {error, timeout} -> OldBalance + {ok, #{"balance" := Old}} -> Old; + {ok, #{"balance" := New}} -> New; + {error, "Account not found"} -> 0; + {error, timeout} -> Old end, - Dist = [{ChainID, Pucks}], - Gaju = #balance{coin = "gaju", total = Pucks, dist = Dist}, + NewDist = [{ChainID, Pucks}], + Gaju = #balance{coin = "gaju", total = Pucks, dist = NewDist}, This#poa{balances = [Gaju]} end. @@ -799,41 +882,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. @@ -880,23 +935,31 @@ 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). +do_deploy(Build, State) -> + do_prompt_call({"init", init}, none, Build, State). -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, #{}), + Args = {CallPrefs, FunDef, ConID, Build, Selected, KeyIDs}, + Win = gd_v_call:start_link(Args), + 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) -> @@ -947,33 +1010,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} -> @@ -1002,54 +1038,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), @@ -1074,6 +1062,19 @@ do_drop_key(ID, State = #s{wallet = W, wallets = Wallets, pass = Pass}) -> State#s{wallet = NewWallet}. +do_open_contract(ConID, ToFront, State) -> + NewState = do_show_ui(gd_v_devman, ToFront, State), + ok = gd_v_devman:open_contract(ConID), + NewState. + + +do_show_call(ConID, Info, ToFront, State) -> + NewState = do_show_ui(gd_v_devman, ToFront, State), + ok = gd_v_devman:open_contract(ConID), + ok = gd_v_devman:call_result(ConID, Info), + NewState. + + do_open_wallet(Path, Phrase, State = #s{timer = Timer}) -> Pass = pass(Phrase), case read(Path, Pass) of @@ -1102,26 +1103,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, @@ -1266,16 +1247,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), @@ -1291,13 +1262,17 @@ do_close_wallet(State = #s{wallet = Current, wallets = Wallets, pass = Pass}) -> State#s{selected = 0, pass = none, wallet = none}. -save_wallet(#wr{path = Path, pass = false}, none, Wallet) -> +save_wallet(#wr{path = Path, pass = false}, none, Wallet = #wallet{name = Name}) -> ok = filelib:ensure_dir(Path), - file:write_file(Path, term_to_binary(Wallet)); -save_wallet(#wr{path = Path, pass = true}, Pass, Wallet) -> + ok = log(info, "Saving plain wallet ~ts file to disk...", [Name]), + ok = file:write_file(Path, term_to_binary(Wallet)), + log(info, "Wallet ~ts file written.", [Name]); +save_wallet(#wr{path = Path, pass = true}, Pass, Wallet = #wallet{name = Name}) -> ok = filelib:ensure_dir(Path), Cipher = encrypt(Pass, term_to_binary(Wallet)), - file:write_file(Path, Cipher). + ok = log(info, "Saving cipher wallet ~ts file to disk.", [Name]), + ok = file:write_file(Path, Cipher), + log(info, "Wallet ~ts file written.", [Name]). read(Path, none) -> @@ -1349,20 +1324,25 @@ do_tic(MS, State = #s{timer = {T, _}}) -> T = erlang:send_after(MS, self(), tic), State#s{timer = {T, MS}}. +handle_tic(State = #s{wallet = #wallet{poas = []}, timer = {T, MS}}) -> + ok = cancel_timer(T), + NewT = erlang:send_after(MS, self(), tic), + State#s{timer = {NewT, MS}}; handle_tic(State = #s{wallet = This = #wallet{poas = POAs, endpoint = Node}, timer = {T, MS}, selected = Selected}) when Selected > 0 -> - + % TODO: The closure below is kind of silly. The internal hz:acc/1 account will + % Expand to become hz:acc/2, accepting a chain ID later. NewState = case ensure_hz_set(Node) of {ok, ChainID} -> POA = #poa{id = ID} = lists:nth(Selected, POAs), - CheckBalance = check_balance(ChainID), + CheckBalance = check_balance(ChainID, "gaju"), NewPOA = CheckBalance(POA), NewPOAs = lists:keystore(ID, #poa.id, POAs, NewPOA), ok = gd_gui:show(NewPOAs), - State#s{wallet = This#wallet{poas = POAs}}; + State#s{wallet = This#wallet{poas = NewPOAs}}; Error -> ok = log(info, "Balance update on tic failed with: ~p", [Error]), State @@ -1384,9 +1364,96 @@ 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), + ok = log(info, "Writing prefs to disk."), zx_lib:write_terms(Path, proplists:from_map(Prefs)). diff --git a/src/gd_grids.erl b/src/gd_grids.erl index 75d6091..ee4de93 100644 --- a/src/gd_grids.erl +++ b/src/gd_grids.erl @@ -37,7 +37,7 @@ %%% @end -module(gd_grids). --vsn("0.8.1"). +-vsn("0.9.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). diff --git a/src/gd_gui.erl b/src/gd_gui.erl index df258ea..82d909a 100644 --- a/src/gd_gui.erl +++ b/src/gd_gui.erl @@ -3,7 +3,7 @@ %%% @end -module(gd_gui). --vsn("0.8.1"). +-vsn("0.9.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). @@ -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()}). @@ -302,7 +298,7 @@ handle_event(#wx{event = #wxCommand{type = command_button_clicked}, #w{name = mnemonic} -> show_mnemonic(State); #w{name = rename} -> rename_key(State); #w{name = drop_key} -> drop_key(State); - #w{name = copy} -> copy(State); + #w{name = copy} -> copy_pk(State); #w{name = www} -> www(State); #w{name = send} -> spend(State); #w{name = grids} -> grids_dialogue(State); @@ -337,6 +333,7 @@ handle_event(Event, State) -> handle_troubling(#s{frame = Frame}, Info) -> + ok = wxFrame:raise(Frame), zxw:show_message(Frame, Info). @@ -591,7 +588,7 @@ show_mnemonic(Selected, State = #s{frame = Frame, j = J, accounts = Accounts}) - ok = case wxDialog:showModal(Dialog) of ?wxID_CANCEL -> ok; - ?wxID_OK -> copy_to_clipboard(Mnemonic) + ?wxID_OK -> gd_lib:copy_to_clipboard(Mnemonic) end, ok = wxDialog:destroy(Dialog), State. @@ -659,28 +656,11 @@ drop_key(Selected, State = #s{frame = Frame, j = J, accounts = Accounts, prefs = State#s{prefs = NewPrefs}. -copy(State = #s{id = {_, #w{wx = ID_T}}}) -> +copy_pk(State = #s{id = {_, #w{wx = ID_T}}}) -> String = wxStaticText:getLabel(ID_T), - ok = copy_to_clipboard(String), + ok = gd_lib:copy_to_clipboard(String), State. -copy_to_clipboard(String) -> - CB = wxClipboard:get(), - case wxClipboard:open(CB) of - true -> - Text = wxTextDataObject:new([{text, String}]), - case wxClipboard:setData(CB, Text) of - true -> - R = wxClipboard:flush(CB), - log(info, "String copied to system clipboard. Flushed: ~p", [R]); - false -> - log(info, "Failed to copy to clipboard") - end, - ok = wxClipboard:close(CB); - false -> - log(info, "Failed to acquire the clipboard.") - end. - www(State = #s{id = {_, #w{wx = ID_T}}, j = J}) -> case wxStaticText:getLabel(ID_T) of diff --git a/src/gd_jt.erl b/src/gd_jt.erl index b94583d..303fa68 100644 --- a/src/gd_jt.erl +++ b/src/gd_jt.erl @@ -15,7 +15,7 @@ %%% translation library is retained). -module(gd_jt). --vsn("0.8.1"). +-vsn("0.9.0"). -export([read_translations/1, j/2, oneshot_j/2]). diff --git a/src/gd_lib.erl b/src/gd_lib.erl index 9aabc06..d7af461 100644 --- a/src/gd_lib.erl +++ b/src/gd_lib.erl @@ -3,9 +3,19 @@ %%% @end -module(gd_lib). --vsn("0.8.1"). +-vsn("0.9.0"). + +-include_lib("wx/include/wx.hrl"). +-export([is_int/1, + mono_text/2, mono_text/3, mono_text/4, + button/2, button/3, + copy_to_clipboard/1]). + +-include("$zx_include/zx_logger.hrl"). +-include("gd.hrl"). +-include("gdl.hrl"). + --export([is_int/1]). -spec is_int(string()) -> boolean(). %% @doc @@ -14,3 +24,81 @@ 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) -> + mono_text(Parent, Name, Value, []). + +mono_text(Parent, Name, Value, Options) -> + Text = wxTextCtrl:new(Parent, ?wxID_ANY, [{value, Value} | Options]), + Font = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL), + ok = + case wxTextCtrl:setFont(Text, Font) of + true -> ok; + false -> tell(info, "wxStaticText ~p is already monospace.", [Text]) + end, + #w{name = Name, id = wxTextCtrl: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}. + + +-spec copy_to_clipboard(String) -> ok + when String :: unicode:charlist(). + +copy_to_clipboard(String) -> + CB = wxClipboard:get(), + case wxClipboard:open(CB) of + true -> + Text = wxTextDataObject:new([{text, String}]), + case wxClipboard:setData(CB, Text) of + true -> + R = wxClipboard:flush(CB), + log(info, "String copied to system clipboard. Flushed: ~p", [R]); + false -> + log(info, "Failed to copy to clipboard") + end, + ok = wxClipboard:close(CB); + false -> + log(info, "Failed to acquire the clipboard.") + end. diff --git a/src/gd_m_spend.erl b/src/gd_m_spend.erl index 47d147e..fb31f22 100644 --- a/src/gd_m_spend.erl +++ b/src/gd_m_spend.erl @@ -3,7 +3,7 @@ %%% @end -module(gd_m_spend). --vsn("0.8.1"). +-vsn("0.9.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/gd_m_wallet_importer.erl b/src/gd_m_wallet_importer.erl index 3510f4e..4b88cb6 100644 --- a/src/gd_m_wallet_importer.erl +++ b/src/gd_m_wallet_importer.erl @@ -5,7 +5,7 @@ %%% @end -module(gd_m_wallet_importer). --vsn("0.8.1"). +-vsn("0.9.0"). -author("Craig Everett "). -copyright("Craig Everett "). -license("GPL-3.0-or-later"). diff --git a/src/gd_sophia_editor.erl b/src/gd_sophia_editor.erl index ee9a7a3..1c6c8f3 100644 --- a/src/gd_sophia_editor.erl +++ b/src/gd_sophia_editor.erl @@ -1,5 +1,5 @@ -module(gd_sophia_editor). --vsn("0.8.1"). +-vsn("0.9.0"). -export([new/1, update/1, update/2, get_text/1, set_text/2]). @@ -26,6 +26,7 @@ -define(H, 255). % High -define(M, 192). % Medium -define(L, 128). % Low +-define(D, 64). % Deep-Low -define(X, 32). % X-Low -define(Z, 0). % Zilch @@ -46,8 +47,9 @@ -define(brown, {?L, ?L, ?Z}). -define(magenta, {?L, ?Z, ?L}). -define(cyan, {?Z, ?L, ?L}). --define(not_black, {?X, ?X, ?X}). -define(grey, {?L, ?L, ?L}). +-define(dark_grey, {?D, ?D, ?D}). +-define(not_black, {?X, ?X, ?X}). -define(white, {?M, ?M, ?M}). styles() -> @@ -67,6 +69,9 @@ palette(light) -> ?STRING => ?red, ?NUMBER => ?magenta, ?OPERATOR => ?brown, + sel_back => ?grey, + line_num => ?brown, + cursor => ?black, bg => ?high_white}; palette(dark) -> #{?DEFAULT => ?white, @@ -76,6 +81,9 @@ palette(dark) -> ?STRING => ?light_red, ?NUMBER => ?light_magenta, ?OPERATOR => ?yellow, + sel_back => ?dark_grey, + line_num => ?yellow, + cursor => ?white, bg => ?not_black}. color_mode() -> @@ -97,6 +105,8 @@ new(Parent) -> [{face, "Monospace"}]), SetMonospace = fun(Style) -> wxStyledTextCtrl:styleSetFont(STC, Style, Mono) end, ok = lists:foreach(SetMonospace, styles()), + ok = wxStyledTextCtrl:setMarginType(STC, 0, ?wxSTC_MARGIN_NUMBER), + ok = wxStyledTextCtrl:setMarginWidth(STC, 0, 40), ok = wxStyledTextCtrl:styleSetFont(STC, ?wxSTC_STYLE_DEFAULT, Mono), ok = set_colors(STC), STC. @@ -112,13 +122,17 @@ set_text(STC, Text) -> set_colors(STC) -> ok = wxStyledTextCtrl:styleClearAll(STC), - Palette = #{bg := BGC} = palette(color_mode()), + Palette = palette(color_mode()), + #{bg := BGC, cursor := CC, line_num := LNC, sel_back := SFB} = Palette, Colorize = fun(Style) -> Color = maps:get(Style, Palette), ok = wxStyledTextCtrl:styleSetForeground(STC, Style, Color), ok = wxStyledTextCtrl:styleSetBackground(STC, Style, BGC) end, + ok = wxStyledTextCtrl:setCaretForeground(STC, CC), + ok = wxStyledTextCtrl:setSelBackground(STC, true, SFB), + ok = wxStyledTextCtrl:styleSetForeground(STC, ?wxSTC_STYLE_LINENUMBER, LNC), ok = wxStyledTextCtrl:styleSetBackground(STC, ?wxSTC_STYLE_DEFAULT, BGC), lists:foreach(Colorize, styles()). diff --git a/src/gd_sup.erl b/src/gd_sup.erl index 0ca4d64..5bac2bd 100644 --- a/src/gd_sup.erl +++ b/src/gd_sup.erl @@ -12,7 +12,7 @@ %%% @end -module(gd_sup). --vsn("0.8.1"). +-vsn("0.9.0"). -behaviour(supervisor). -author("Craig Everett "). -copyright("QPQ AG "). diff --git a/src/gd_v.erl b/src/gd_v.erl index 1e50284..b714b66 100644 --- a/src/gd_v.erl +++ b/src/gd_v.erl @@ -1,5 +1,5 @@ -module(gd_v). --vsn("0.8.1"). +-vsn("0.9.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). diff --git a/src/gd_v_call.erl b/src/gd_v_call.erl new file mode 100644 index 0000000..7f3f084 --- /dev/null +++ b/src/gd_v_call.erl @@ -0,0 +1,591 @@ +-module(gd_v_call). +-vsn("0.9.0"). +-author("Craig Everett "). +-copyright("QPQ AG "). +-license("GPL-3.0-or-later"). + +-behavior(wx_object). +%-behavior(gd_v). +-include_lib("wx/include/wx.hrl"). +-export([to_front/1, tx_hash/1, tx_data/1, tx_info/1]). +-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"). + + +% State +-record(s, + {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(), + funret = none :: none | term(), % FIXME + build = none :: none | map(), + args = [] :: [#w{}], + kp = #w{} :: #w{}, + params = [] :: [param()], + return = #w{} :: #w{}, % wxTextCtrl, single-line + copy = #w{} :: #w{}, + status = none :: status(), + action = #w{} :: #w{}, + tx_data = none :: none | map(), + tx_info = none :: none | map(), + hash = #w{} :: #w{}, % wxTextCtrl, single-line + info = #w{} :: #w{}}). % wxTextCtrl, multi-line + + +-type fun_name() :: string(). +-type fun_ilk() :: call | dryr | init. +-type fun_def() :: {fun_name(), fun_ilk()}. +-type param() :: {Label :: string(), Check :: fun(), #w{}}. +-type status() :: none + | submitted + | rejected + | included. + + +%%% Interface + +-spec to_front(Win) -> ok + when Win :: wx:wx_object(). + +to_front(Win) -> + wx_object:cast(Win, to_front). + + +-spec tx_hash(Win) -> Result + when Win :: pid() | wx:wx_object(), + Result :: none | string(). + +tx_hash(Win) -> + wx_object:call(Win, tx_hash). + + +-spec tx_data(Win) -> Result + when Win :: pid() | wx:wx_object(), + Result :: none | map(). + +tx_data(Win) -> + wx_object:call(Win, tx_data). + + +-spec tx_info(Win) -> Result + when Win :: pid() | wx:wx_object(), + Result :: none | map(). + +tx_info(Win) -> + wx_object:call(Win, tx_info). + + + +%%% Startup Functions + +start_link(Args) -> + wx_object:start_link(?MODULE, Args, []). + + +init({Prefs, FunDef = {FunName, FunIlk}, 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 = {FunArgs, FunReturn} = maps:get(FunName, FunSpecs), + {CallTypeLabel, ActionLabel} = + case FunIlk 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(FunArgs)), + Title = [CallTypeLabel, ": ", 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")}]), + KeyBox = wxStaticBoxSizer:getStaticBox(KeySz), + KeyPicker = wxChoice:new(KeyBox, ?wxID_ANY, [{choices, Keys}]), + KP = #w{name = key_picker, id = wxChoice:getId(KeyPicker), wx = KeyPicker}, + ZeroBasedSelected = Selected - 1, + ok = wxChoice:setSelection(KeyPicker, ZeroBasedSelected), + _ = wxStaticBoxSizer:add(KeySz, KeyPicker, zxw:flags(wide)), + + {ArgSz, Args, Return, Copy, HasArgs} = call_arg_sizer(Frame, J, FunIlk, FunSpec), + {ParamSz, Params} = 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_Sz_Box = wxStaticBoxSizer:getStaticBox(TX_Sz), + Single = [{style, ?wxTE_READONLY}], + Multi = [{style, ?wxTE_MULTILINE bor ?wxTE_READONLY}], + TX_Hash = #w{wx = HashT} = gd_lib:mono_text(TX_Sz_Box, tx_hash, "", Single), + TX_Info = #w{wx = InfoT} = gd_lib:mono_text(TX_Sz_Box, tx_info, "", Multi), + + _ = wxStaticBoxSizer:add(TX_Sz, HashT, zxw:flags({base, 5})), + _ = wxStaticBoxSizer:add(TX_Sz, InfoT, zxw:flags({wide, 5})), + + ArgSzArgs = + case HasArgs of + true -> [{proportion, 2}, {flag, ?wxEXPAND bor ?wxALL}, {border, 5}]; + false -> zxw:flags({base, 5}) + end, + _ = wxSizer:add(MainSz, ArgSz, ArgSzArgs), + _ = wxSizer:add(MainSz, KeySz, zxw:flags({base, 5})), + _ = wxSizer:add(MainSz, ParamSz, zxw:flags({base, 5})), + _ = wxSizer:add(MainSz, ActionBn, zxw:flags({base, 5})), + _ = wxSizer:add(MainSz, TX_Sz, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), + + _ = wxFrame:setSizer(Frame, MainSz), + _ = wxFrame:setSize(Frame, {900, 900}), + _ = wxSizer:layout(MainSz), + ok = wxFrame:connect(Frame, close_window), + ok = wxFrame:connect(Frame, command_button_clicked), + true = wxFrame:show(Frame), + State = + #s{wx = Wx, frame = Frame, j = J, prefs = Prefs, + fundef = FunDef, funret = FunReturn, con_id = ConID, build = Build, + args = Args, kp = KP, params = Params, + return = Return, copy = Copy, + action = Action, status = none, + hash = TX_Hash, info = TX_Info}, + {Frame, State}. + + +call_arg_sizer(Frame, J, FunIlk, {CallArgs, ReturnType}) -> + SpecSz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("Function Spec")}]), + SpecBox = wxStaticBoxSizer:getStaticBox(SpecSz), + {CallSz, CallControls, HasArgs} = call_sizer(SpecBox, J, CallArgs), + {ReturnSz, Return, Copy} = return_sizer(SpecBox, J, FunIlk, ReturnType), + _ = wxStaticBoxSizer:add(SpecSz, CallSz, zxw:flags({wide, 5})), + _ = wxStaticBoxSizer:add(SpecSz, ReturnSz, zxw:flags({base, 5})), + {SpecSz, CallControls, Return, Copy, HasArgs}. + +call_sizer(Parent, J, []) -> + CallSz = wxStaticBoxSizer:new(?wxVERTICAL, Parent, [{label, J("Call Args")}]), + CallBox = wxStaticBoxSizer:getStaticBox(CallSz), + Args = wxStaticText:new(CallBox, ?wxID_ANY, ["[", J("No Args"), "]"]), + _ = wxStaticBoxSizer:add(CallSz, Args, zxw:flags({wide, 5})), + {CallSz, [], false}; +call_sizer(Parent, J, CallArgs) -> + CallSz = wxStaticBoxSizer:new(?wxVERTICAL, Parent, [{label, J("Call Args")}]), + CallBox = wxStaticBoxSizer:getStaticBox(CallSz), + ScrollWin = wxScrolledWindow:new(CallBox), + ScrollSz = wxBoxSizer:new(?wxVERTICAL), + ok = wxScrolledWindow:setSizerAndFit(ScrollWin, ScrollSz), + ok = wxScrolledWindow:setScrollRate(ScrollWin, 5, 5), + GridSz = wxFlexGridSizer:new(2, [{gap, {4, 4}}]), + ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), + ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), + _ = wxStaticBoxSizer:add(ScrollSz, GridSz, zxw:flags(wide)), + _ = wxStaticBoxSizer:add(CallSz, ScrollWin, zxw:flags(wide)), + AddArg = + fun({Name, Type}) -> + L = wxStaticText:new(ScrollWin, ?wxID_ANY, [Name, " : ", textify(Type)]), + C = #w{wx = T} = gd_lib:mono_text(ScrollWin, Name), + _ = wxFlexGridSizer:add(GridSz, L, zxw:flags({base, 5})), + _ = wxFlexGridSizer:add(GridSz, T, zxw:flags({wide, 5})), + C + end, + Controls = lists:map(AddArg, CallArgs), + {CallSz, Controls, true}. + +return_sizer(Parent, J, FunIlk, ReturnType) -> + IlkLabel = + case FunIlk =:= init of + false -> textify(ReturnType); + true -> J("Contract Address") + end, + ReturnSz = wxStaticBoxSizer:new(?wxVERTICAL, Parent, [{label, J("Return Type")}]), + ReturnSzBox = wxStaticBoxSizer:getStaticBox(ReturnSz), + ReturnLabel = wxStaticText:new(ReturnSzBox, ?wxID_ANY, IlkLabel), + Single = [{style, ?wxTE_READONLY}], + Return = #w{wx = ReturnTx} = gd_lib:mono_text(ReturnSzBox, return, "", Single), + Copy = #w{wx = CopyB} = gd_lib:button(ReturnSzBox, J("Copy")), + _ = wxButton:disable(CopyB), + _ = wxStaticBoxSizer:add(ReturnSz, ReturnLabel, zxw:flags({wide, 5})), + _ = wxStaticBoxSizer:add(ReturnSz, ReturnTx, zxw:flags({wide, 5})), + _ = wxStaticBoxSizer:add(ReturnSz, CopyB, zxw:flags({wide, 5})), + {ReturnSz, Return, Copy}. + + +call_param_sizer(Frame, J) -> + {ok, Height} = hz:top_height(), + DefTTL = Height + 10000, + DefGasP = hz:min_gas_price(), + DefGas = 5000000, + DefAmount = 0, + ParamSz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("TX Parameters")}]), + ParamBox = wxStaticBoxSizer:getStaticBox(ParamSz), + GridSz = wxFlexGridSizer:new(2, 4, 4), + ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), + ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), + Amount_L = wxStaticText:new(ParamBox, ?wxID_ANY, J("Amount")), + Amount_T = wxTextCtrl:new(ParamBox, ?wxID_ANY), + ok = wxTextCtrl:setValue(Amount_T, integer_to_list(DefAmount)), + Gas_L = wxStaticText:new(ParamBox, ?wxID_ANY, J("Gas")), + Gas_T = wxTextCtrl:new(ParamBox, ?wxID_ANY), + ok = wxTextCtrl:setValue(Gas_T, integer_to_list(DefGas)), + GasP_L = wxStaticText:new(ParamBox, ?wxID_ANY, J("Gas Price")), + GasP_T = wxTextCtrl:new(ParamBox, ?wxID_ANY), + ok = wxTextCtrl:setValue(GasP_T, integer_to_list(DefGasP)), + TTL_L = wxStaticText:new(ParamBox, ?wxID_ANY, "TTL"), + TTL_T = wxTextCtrl:new(ParamBox, ?wxID_ANY), + ok = wxTextCtrl:setValue(TTL_T, integer_to_list(DefTTL)), + _ = wxFlexGridSizer:add(GridSz, Amount_L, zxw:flags({base, 5})), + _ = wxFlexGridSizer:add(GridSz, Amount_T, zxw:flags({wide, 5})), + _ = wxFlexGridSizer:add(GridSz, Gas_L, zxw:flags({base, 5})), + _ = wxFlexGridSizer:add(GridSz, Gas_T, zxw:flags({wide, 5})), + _ = wxFlexGridSizer:add(GridSz, GasP_L, zxw:flags({base, 5})), + _ = wxFlexGridSizer:add(GridSz, GasP_T, zxw:flags({wide, 5})), + _ = wxFlexGridSizer:add(GridSz, TTL_L, zxw:flags({base, 5})), + _ = wxFlexGridSizer:add(GridSz, TTL_T, zxw:flags({wide, 5})), + _ = wxSizer:add(ParamSz, GridSz, zxw:flags(wide)), + Params = + [{J("TX Amount"), fun gte_0/1, Amount_T} , + {J("Gas") , fun gt_0/1, Gas_T}, + {J("Gas Price"), fun gt_0/1, GasP_T}, + { "TTL", fun gte_0/1, TTL_T}], + {ParamSz, Params}. + + + +%%% Spine + +handle_call(tx_hash, _, State) -> + TXHash = do_tx_hash(State), + {reply, TXHash, State}; +handle_call(tx_data, _, State = #s{tx_data = TXData}) -> + {reply, TXData, State}; +handle_call(tx_info, _, State = #s{tx_info = TXInfo}) -> + {reply, TXInfo, State}; +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(retire, State) -> + ok = retire(State), + {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}, status = none}) -> + NewState = prep_call(State), + {noreply, NewState}; +handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID}, + State = #s{action = #w{id = ID}}) -> + NewState = check_tx(State), + {noreply, NewState}; +handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID}, + State = #s{copy = #w{id = ID}}) -> + ok = copy(State), + {noreply, State}; +handle_event(#wx{event = #wxClose{}}, State) -> + ok = retire(State), + {noreply, State}; +handle_event(Event, State) -> + ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]), + {noreply, State}. + + +code_change(_, State, _) -> + {ok, State}. + + +retire(#s{frame = Frame}) -> + wxWindow:destroy(Frame). + + +terminate(wx_deleted, _) -> + wx:destroy(); +terminate(Reason, State) -> + ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), + wx:destroy(). + + + +%%% Handlers + +handle_troubling(State = #s{frame = Frame}, Info) -> + ok = wxFrame:raise(Frame), + ok = zxw:show_message(Frame, Info), + State. + + +do_tx_hash(#s{tx_data = #{"tx_hash" := TXHash}}) -> + TXHash; +do_tx_hash(#s{tx_data = none}) -> + none. + + +prep_call(State) -> + case gd_con:chain_id() of + {ok, ChainID} -> prep_call2(State, ChainID); + Error -> handle_troubling(State, Error) + end. + +prep_call2(State, ChainID) -> + case params(State) of + {ok, Params} -> prep_call3(State, ChainID, Params); + Error -> handle_troubling(State, Error) + end. + +prep_call3(State, ChainID, Params) -> + case args(State) of + {ok, Args} -> prep_call4(State, ChainID, Params, {sophia, Args}); + Error -> handle_troubling(State, Error) + end. + +prep_call4(State = #s{fundef = {"init", init}, build = Build}, ChainID, Params, Args) -> + {CallerID, Nonce, Gas, GP, Amount, TTL} = Params, + case hz:contract_create_built(CallerID, Nonce, Gas, GP, Amount, TTL, Build, Args) of + {ok, CreateTX} -> deploy(State, ChainID, CallerID, CreateTX); + Error -> handle_troubling(State, Error) + end; +prep_call4(State = #s{fundef = {Name, Ilk}, con_id = ConID, build = Build}, ChainID, Params, Args) -> + {CallerID, Nonce, Gas, GP, Amount, TTL} = Params, + AACI = maps:get(aaci, Build), + case hz:contract_call(CallerID, Nonce, Gas, GP, Amount, TTL, AACI, ConID, Name, Args) of + {ok, UnsignedTX} -> + case Ilk of + call -> do_call(State, ChainID, CallerID, UnsignedTX); + dryr -> do_dry_run(State, ConID, UnsignedTX) + end; + Error -> + handle_troubling(State, Error) + end. + +params(State = #s{kp = #w{wx = KeyPicker}}) -> + ID = wxChoice:getString(KeyPicker, wxChoice:getSelection(KeyPicker)), + PK = unicode:characters_to_binary(ID), + case hz:next_nonce(PK) of + {ok, Nonce} -> params2(State, PK, Nonce); + Error -> handle_troubling(State, Error) + end. + +params2(#s{params = Params}, PK, Nonce) -> + case lists:foldl(fun extract/2, {ok, []}, Params) of + {ok, [TTL, GP, Gas, Amount]} -> {ok, {PK, Nonce, Gas, GP, Amount, TTL}}; + Error -> Error + end. + +extract({Name, Check, Widget}, {Status, Out}) -> + case Check(wxTextCtrl:getValue(Widget)) of + {ok, Value} -> {Status, [Value | Out]}; + Error -> {error, [{Name, Error}, Out]} + end. + + +% TODO: Put some basic checking in here, like for blank strings, at least. +% It should be possible to perform type/parse checks over this since we have +% access to the AACI. But not today. +args(#s{args = ArgFields}) -> + Values = [wxTextCtrl:getValue(W) || #w{wx = W} <- ArgFields], + {ok, Values}. + + +deploy(State, ChainID, CallerID, CreateTX) -> + case gd_con:sign_call(ChainID, CallerID, CreateTX) of + {ok, SignedTX} -> deploy2(State, SignedTX); + Error -> handle_troubling(State, Error) + end. + +deploy2(State = #s{j = J, hash = #w{wx = HashT}, action = #w{wx = ActionB}}, SignedTX) -> + case hz:post_tx(SignedTX) of + {ok, Data = #{"tx_hash" := TXHash}} -> + _ = wxButton:disable(ActionB), + ok = wxButton:setLabel(ActionB, J("Check Transaction Status")), + ok = log(info, "Submitted transaction ~s", [TXHash]), + ok = wxTextCtrl:setValue(HashT, unicode:characters_to_list(TXHash)), + check_tx(State#s{tx_data = Data, status = submitted}); + {ok, #{"reason" := Reason}} -> + handle_troubling(State, {error, Reason}); + Error -> + handle_troubling(State, Error) + end. + + +do_call(State, ChainID, CallerID, UnsignedTX) -> + case gd_con:sign_call(ChainID, CallerID, UnsignedTX) of + {ok, SignedTX} -> do_call2(State, SignedTX); + Error -> handle_troubling(State, Error) + end. + +do_call2(State = #s{action = #w{wx = ActionB}}, SignedTX) -> + _ = wxButton:disable(ActionB), + case hz:post_tx(SignedTX) of + {ok, #{"reason" := Reason}} -> handle_troubling(State#s{status = rejected}, Reason); + {ok, Data} -> check_tx(State#s{tx_data = Data, status = submitted}); + Error -> handle_troubling(State, Error) + end. + + +do_dry_run(State = #s{action = #w{wx = ActionB}}, ConID, TX) -> + _ = wxButton:disable(ActionB), + case hz:dry_run(TX) of + {ok, Result} -> dry_run2(State#s{tx_info = Result}); + Other -> handle_troubling(State, {error, ConID, Other}) + end. + + +dry_run2(State = #s{funret = ReturnType, + return = #w{wx = ReturnT}, + copy = #w{wx = CopyB}, + tx_data = TXData, + tx_info = TXInfo, + hash = #w{wx = HashT}, + info = #w{wx = InfoT}}) -> + ReturnV = + case TXInfo of + #{"results" := + [#{"call_obj" := + #{"return_type" := "revert", + "return_value" := ReturnCB}, + "result" := "ok","type" := "contract_call"}]} -> + io_lib:format("Revert: ~ts", [hz:decode_bytearray(ReturnCB, sophia)]); + #{"results" := + [#{"call_obj" := + #{"return_type" := "ok", + "return_value" := ReturnCB}, + "result" := "ok","type" := "contract_call"}]} -> + hz:decode_bytearray(ReturnCB, {sophia, ReturnType}); + Other -> + io_lib:format("???: ~tp", [Other]) + end, + _ = wxButton:enable(CopyB), + FormattedHash = io_lib:format("~tp", [TXData]), + FormattedInfo = io_lib:format("~tp", [TXInfo]), + ok = wxTextCtrl:setValue(ReturnT, ReturnV), + ok = wxTextCtrl:setValue(HashT, FormattedHash), + ok = wxTextCtrl:setValue(InfoT, FormattedInfo), + State. + + +check_tx(State = #s{j = J, + fundef = {_, init}, + tx_data = #{"tx_hash" := TXHash}, + tx_info = none, + status = submitted, + action = #w{wx = ActionB}, + info = #w{wx = InfoT}}) -> + case hz:tx_info(TXHash) of + {ok, Info = #{"call_info" := #{"return_type" := "ok", "contract_id" := ConID}}} -> + ok = tell(info, "Contract deployed: ~p", [Info]), + _ = wxButton:disable(ActionB), + ok = gd_con:open_contract(ConID), + self() ! retire, + State; + {error, "Tx not mined"} -> + ok = wxTextCtrl:setValue(InfoT, J("[Transaction not yet mined.]")), + ok = wxButton:setLabel(ActionB, J("Check Deployment Status")), + _ = wxButton:enable(ActionB), + State; + Other -> + FormattedJunk = io_lib:format("~tp", [Other]), + ok = wxTextCtrl:setValue(InfoT, FormattedJunk), + ok = wxButton:setLabel(ActionB, J("Check Depoyment Status")), + _ = wxButton:enable(ActionB), + State + end; +check_tx(State = #s{j = J, + funret = ReturnType, + tx_data = #{"tx_hash" := TXHash}, + tx_info = none, + status = submitted, + return = #w{wx = ReturnT}, + copy = #w{wx = CopyB}, + action = #w{wx = ActionB}, + info = #w{wx = InfoT}}) -> + case hz:tx_info(TXHash) of + {ok, Info = #{"call_info" := #{"return_type" := "ok", + "return_value" := ReturnCB}}} -> + FormattedInfo = io_lib:format("~tp", [Info]), + _ = wxButton:enable(CopyB), + _ = wxButton:disable(ActionB), + ReturnV = hz:decode_bytearray(ReturnCB, {sophia, ReturnType}), + ok = wxTextCtrl:setValue(ReturnT, ReturnV), + ok = wxTextCtrl:setValue(InfoT, FormattedInfo), + State#s{status = included, tx_info = Info}; + {ok, Reason = #{"call_info" := #{"return_type" := "revert", "return_value" := ReturnCB}}} -> + _ = wxButton:enable(CopyB), + _ = wxButton:disable(ActionB), + ReturnV = io_lib:format("Revert: ~ts", [hz:decode_bytearray(ReturnCB, sophia)]), + ok = wxTextCtrl:setValue(ReturnT, ReturnV), + FormattedInfo = io_lib:format("~tp", [Reason]), + ok = wxTextCtrl:setValue(InfoT, FormattedInfo), + State#s{status = rejected, tx_info = Reason}; + {error, "Tx not mined"} -> + ok = wxTextCtrl:setValue(InfoT, J("[Transaction not yet mined.]")), + ok = wxButton:setLabel(ActionB, J("Check Transaction Status")), + _ = wxButton:enable(ActionB), + State; + Error -> + handle_troubling(State, Error) + end; +check_tx(State = #s{tx_data = TXData, + action = #w{wx = ActionB}}) -> + _ = wxButton:disable(ActionB), + tell(info, "TXData: ~p", [TXData]), + ok = tell("Nothing to check"), + State. + + +copy(#s{tx_info = none}) -> + ok; +copy(#s{return = #w{wx = ReturnT}}) -> + Output = wxTextCtrl:getValue(ReturnT), + gd_lib:copy_to_clipboard(Output). + + + +textify({integer, _, _}) -> "int"; +textify({boolean, _, _}) -> "bool"; +textify({{bytes, [I]}, _, _}) -> io_lib:format("bytes(~w)", [I]); +textify({{bytes, any}, _, _}) -> "bytes()"; +textify({T, _, _}) when is_atom(T) -> atom_to_list(T); +textify({T, _, _}) when is_list(T) -> T; +textify({T, _, _}) -> io_lib:format("~tp", [T]). + + +gt_0(S) -> + C = "Must be an integer greater than 0", + R = + try + {ok, list_to_integer(S)} + catch + error:badarg -> {error, {S, C}} + end, + case R of + {ok, N} when N > 0 -> {ok, N}; + {ok, N} when N =< 0 -> {error, {S, C}}; + Error -> Error + end. + +gte_0(S) -> + C = "Must be a non-negative integer.", + R = + try + {ok, list_to_integer(S)} + catch + error:badarg -> {error, {S, C}} + end, + case R of + {ok, N} when N >= 0 -> {ok, N}; + {ok, N} when N < 0 -> {error, {S, C}}; + Error -> Error + end. diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index 9dfd43c..b7a7405 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -1,5 +1,5 @@ -module(gd_v_devman). --vsn("0.8.1"). +-vsn("0.9.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). @@ -8,20 +8,15 @@ %-behavior(gd_v). -include_lib("wx/include/wx.hrl"). -export([to_front/1]). --export([set_manifest/1, open_contract/1, call_result/2, dryrun_result/2, trouble/1]). +-export([set_manifest/1, open_contract/1, write_console/2, call_result/2, dryrun_result/2, trouble/1]). -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()}). - % Contract functions in an ACI -record(f, {name = <<"">> :: binary(), @@ -81,10 +76,20 @@ set_manifest(Entries) -> -spec open_contract(Address) -> ok - when Address :: string(). + when Address :: binary() | string(). -open_contract(Address) -> - wx_object:cast(?MODULE, {open_contract, Address}). +open_contract(Address) when is_binary(Address) -> + wx_object:cast(?MODULE, {open_contract, Address}); +open_contract(Address) when is_list(Address) -> + open_contract(list_to_binary(Address)). + + +-spec write_console(ConID, Message) -> ok + when ConID :: gajudesk:id(), + Message :: unicode:chardata(). + +write_console(ConID, Message) -> + wx_object:cast(?MODULE, {write_console, ConID, Message}). -spec call_result(ConID, CallInfo) -> ok @@ -119,7 +124,7 @@ start_link(Args) -> init({Prefs, Manifest}) -> - Lang = maps:get(lang, Prefs, en_us), + Lang = maps:get(lang, Prefs, en), Trans = gd_jt:read_translations(?MODULE), J = gd_jt:j(Lang, Trans), Wx = wx:new(), @@ -220,6 +225,9 @@ handle_cast(to_front, State = #s{frame = Frame}) -> handle_cast({open_contract, Address}, State) -> NewState = load2(State, Address), {noreply, NewState}; +handle_cast({write_console, ConID, Message}, State) -> + ok = do_write_console(State, ConID, Message), + {noreply, State}; handle_cast({call_result, ConID, CallInfo}, State) -> ok = do_call_result(State, ConID, CallInfo), {noreply, State}; @@ -227,8 +235,8 @@ handle_cast({dryrun_result, ConID, CallInfo}, State) -> ok = do_dryrun_result(State, ConID, CallInfo), {noreply, State}; handle_cast({trouble, Info}, State) -> - ok = handle_troubling(State, Info), - {noreply, State}; + NewState = handle_troubling(State, Info), + {noreply, NewState}; handle_cast(Unexpected, State) -> ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]), {noreply, State}. @@ -281,14 +289,17 @@ handle_event(Event, State) -> {noreply, State}. -handle_troubling(#s{frame = Frame}, Info) -> - zxw:show_message(Frame, Info). +handle_troubling(State = #s{frame = Frame}, Info) -> + ok = zxw:show_message(Frame, Info), + State. code_change(_, State, _) -> {ok, State}. +terminate(wx_deleted, _) -> + wx:destroy(); terminate(Reason, State) -> ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]), wx:destroy(). @@ -305,176 +316,40 @@ 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 -> - Contract = 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, Name, Selected, Keys) -> - Label = - case element(2, Name) of - call -> "Contract Call"; - dryr -> "Dry Run" +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, - Dialog = wxDialog:new(Frame, ?wxID_ANY, J(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), - {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)), - _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), - _ = wxSizer:add(Sizer, KeySz, zxw:flags(wide)), - _ = wxSizer:add(Sizer, ParamSz, zxw:flags(wide)), - _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 300}), - 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", TTL_Tx}, - {"Gas Price", GasP_Tx}, - {"Gas", Gas_Tx}, - {"Amount", Amount_Tx}], - case call_params(Controls) of - {ok, [TTL, GasP, Gas, Amount]} -> - {ok, Nonce} = hz:next_nonce(PK), - {ok, {PK, Nonce, TTL, GasP, Gas, Amount}}; - E -> - E - end; - ?wxID_CANCEL -> - cancel - end, - ok = wxDialog:destroy(Dialog), - case Outcome of - {ok, Params} -> clicked4(State, Contract, Name, Params); - cancel -> State; - Error -> handle_troubling(State, Error) - end. - -call_param_sizer(Dialog, J) -> - {ok, Height} = hz:top_height(), - DefTTL = Height + 10000, - DefGasP = hz:min_gas_price(), - DefGas = 5000000, - DefAmount = 0, - ParamSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("TX Parameters")}]), - GridSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), - TTL_L = wxStaticText:new(Dialog, ?wxID_ANY, "TTL"), - TTL_Tx = wxTextCtrl:new(Dialog, ?wxID_ANY), - ok = wxTextCtrl:setValue(TTL_Tx, integer_to_list(DefTTL)), - GasP_L = wxStaticText:new(Dialog, ?wxID_ANY, J("Gas Price")), - GasP_Tx = wxTextCtrl:new(Dialog, ?wxID_ANY), - ok = wxTextCtrl:setValue(GasP_Tx, integer_to_list(DefGasP)), - Gas_L = wxStaticText:new(Dialog, ?wxID_ANY, J("Gas")), - Gas_Tx = wxTextCtrl:new(Dialog, ?wxID_ANY), - ok = wxTextCtrl:setValue(Gas_Tx, integer_to_list(DefGas)), - Amount_L = wxStaticText:new(Dialog, ?wxID_ANY, J("TX Amount")), - Amount_Tx = wxTextCtrl:new(Dialog, ?wxID_ANY), - ok = wxTextCtrl:setValue(Amount_Tx, integer_to_list(DefAmount)), - _ = wxFlexGridSizer:add(GridSz, TTL_L, zxw:flags(base)), - _ = wxFlexGridSizer:add(GridSz, TTL_Tx, zxw:flags(wide)), - _ = wxFlexGridSizer:add(GridSz, GasP_L, zxw:flags(base)), - _ = wxFlexGridSizer:add(GridSz, GasP_Tx, zxw:flags(wide)), - _ = wxFlexGridSizer:add(GridSz, Gas_L, zxw:flags(base)), - _ = wxFlexGridSizer:add(GridSz, Gas_Tx, zxw:flags(wide)), - _ = wxFlexGridSizer:add(GridSz, Amount_L, zxw:flags(base)), - _ = wxFlexGridSizer:add(GridSz, Amount_Tx, zxw:flags(wide)), - _ = wxSizer:add(ParamSz, GridSz, zxw:flags(wide)), - {ParamSz, TTL_Tx, GasP_Tx, Gas_Tx, Amount_Tx}. - -call_params(Controls) -> - call_params(Controls, []). - -call_params([], A) -> - {ok, lists:reverse(A)}; -call_params([{L, C} | T], A) -> - O = - try - {ok, list_to_integer(wxTextCtrl:getValue(C))} - catch - error:badarg -> {error, {L, not_an_integer}} - end, - case O of - {ok, N} -> call_params(T, [N | A]); - Error -> Error - end. - -clicked4(State, - #c{id = ConID, build = #{aaci := AACI}, funs = {_, Funs}}, - {Name, Type}, - {PK, Nonce, TTL, GasP, Gas, Amt}) -> - #f{args = ArgFields} = maps:get(Name, Funs), - Args = lists:map(fun get_arg/1, ArgFields), - 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. -do_call_result(#s{tabs = TopBook, cons = {Consbook, Contracts}}, ConID, CallInfo) -> +do_write_console(#s{tabs = TopBook, cons = {Consbook, Contracts}}, ConID, Message) -> case lookup_contract(ConID, Contracts) of {#c{cons = Console}, ZeroIndex} -> _ = wxNotebook:changeSelection(TopBook, 1), _ = wxNotebook:changeSelection(Consbook, ZeroIndex), - Out = io_lib:format("Call Result:~n~p~n~n", [CallInfo]), + Out = [Message, "\n\n"], wxTextCtrl:appendText(Console, Out); error -> - tell(info, "Received result for ~p:~n~p ", [ConID, CallInfo]) + tell(info, "Received result for ~p:~n~p ", [ConID, Message]) end. -do_dryrun_result(#s{tabs = TopBook, cons = {Consbook, Contracts}}, ConID, CallInfo) -> - case lookup_contract(ConID, Contracts) of - {#c{cons = Console}, ZeroIndex} -> - _ = wxNotebook:changeSelection(TopBook, 1), - _ = wxNotebook:changeSelection(Consbook, ZeroIndex), - Out = io_lib:format("Call Result:~n~p~n~n", [CallInfo]), - wxTextCtrl:appendText(Console, Out); - error -> - tell(info, "Received result for ~p:~n~p ", [ConID, CallInfo]) - end. +do_call_result(State, ConID, CallInfo) -> + Message = io_lib:format("Call Result:~n~p", [CallInfo]), + do_write_console(State, ConID, Message). + + +do_dryrun_result(State, ConID, CallInfo) -> + Message = io_lib:format("Call Result:~n~p", [CallInfo]), + do_write_console(State, ConID, Message). + lookup_contract(ConID, Contracts) -> lookup_contract(ConID, Contracts, 0). @@ -504,20 +379,16 @@ add_code_page2(State = #s{j = J}, {file, File}) -> add_code_page(State, {file, File}, Code); Error -> Message = io_lib:format(J("Opening ~p failed with: ~p"), [File, Error]), - ok = handle_troubling(State, Message), - State + handle_troubling(State, Message) end; {error, Reason} -> Message = io_lib:format(J("Opening ~p failed with: ~p"), [File, Reason]), - ok = handle_troubling(State, Message), - State + handle_troubling(State, Message) end; add_code_page2(State, {hash, Address}) -> open_hash2(State, Address). add_code_page(State = #s{tabs = TopBook, code = {Codebook, Pages}}, Location, Code) -> - Color = wxSystemSettings:getColour(?wxSYS_COLOUR_WINDOW), - tell("Color: ~p", [Color]), Window = wxWindow:new(Codebook, ?wxID_ANY), PageSz = wxBoxSizer:new(?wxHORIZONTAL), @@ -595,110 +466,14 @@ 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) -> - InitArgs = element(1, InitSpec), - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Deploy Contract")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - ScrollWin = wxScrolledWindow:new(Dialog), - ScrollSz = wxBoxSizer:new(?wxVERTICAL), - ok = wxScrolledWindow:setSizerAndFit(ScrollWin, ScrollSz), - ok = wxScrolledWindow:setScrollRate(ScrollWin, 5, 5), - FunName = unicode:characters_to_list(["init/", integer_to_list(length(InitArgs))]), - FunSz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, FunName}]), - KeySz = wxStaticBoxSizer:new(?wxVERTICAL, ScrollWin, [{label, J("Signature Key")}]), - KeyPicker = wxChoice:new(ScrollWin, ?wxID_ANY, [{choices, Keys}]), - _ = wxStaticBoxSizer:add(KeySz, KeyPicker, zxw:flags(wide)), - ok = wxChoice:setSelection(KeyPicker, Selected - 1), - {ParamSz, TTL_Tx, GasP_Tx, Gas_Tx, Amount_Tx} = call_param_sizer(ScrollWin, J), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - Affirm = wxButton:new(Dialog, ?wxID_OK), - Cancel = wxButton:new(Dialog, ?wxID_CANCEL), - _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), - _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), - GridSz = wxFlexGridSizer:new(2, 4, 4), - ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), - ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), - ArgFields = make_arg_fields(ScrollWin, GridSz, InitArgs), - _ = wxStaticBoxSizer:add(FunSz, GridSz, zxw:flags(wide)), - _ = wxStaticBoxSizer:add(ScrollSz, FunSz, [{proportion, 1}, {flag, ?wxEXPAND}]), - _ = wxStaticBoxSizer:add(ScrollSz, KeySz, [{proportion, 0}, {flag, ?wxEXPAND}]), - _ = wxStaticBoxSizer:add(ScrollSz, ParamSz, [{proportion, 0}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(Sizer, ScrollWin, [{proportion, 5}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(Sizer, ButtSz, [{proportion, 1}, {flag, ?wxEXPAND}]), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 500}), - 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), - IArgs = lists:map(fun get_arg/1, ArgFields), - Controls = - [{"TTL", TTL_Tx}, - {"Gas Price", GasP_Tx}, - {"Gas", Gas_Tx}, - {"Amount", Amount_Tx}], - case call_params(Controls) of - {ok, [TTL, GasP, Gas, Amount]} -> - {ok, Nonce} = hz:next_nonce(PK), - DeployParams = {PK, Nonce, TTL, GasP, Gas, Amount}, - {ok, DeployParams, IArgs}; - 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) -> - tell(info, "Build: ~p", [Build]), - ok = gd_con:deploy(Build, Params, Args), State. -make_arg_fields(ScrollWin, GridSz, Args) -> - MakeArgField = - fun({AN, T}) -> - tell(info, "~p: Arg: ~p, Type: ~p", [?LINE, AN, T]), - ANT = wxStaticText:new(ScrollWin, ?wxID_ANY, AN), - TCT = wxTextCtrl:new(ScrollWin, ?wxID_ANY), - _ = wxFlexGridSizer:add(GridSz, ANT, [{proportion, 0}, {flag, ?wxEXPAND}]), - _ = wxFlexGridSizer:add(GridSz, TCT, [{proportion, 0}, {flag, ?wxEXPAND}]), - {T, TCT} - end, - lists:map(MakeArgField, Args). - - open(State = #s{frame = Frame, j = J}) -> Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Open Contract Source")), Sizer = wxBoxSizer:new(?wxVERTICAL), @@ -727,7 +502,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 @@ -787,41 +563,16 @@ open_file(State = #s{frame = Frame, j = J, prefs = Prefs}) -> open_hash(State = #s{frame = Frame, j = J}) -> - Dialog = wxDialog:new(Frame, ?wxID_ANY, J("Retrieve Contract Source")), - Sizer = wxBoxSizer:new(?wxVERTICAL), - AddressSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Address Hash")}]), - AddressTx = wxTextCtrl:new(Dialog, ?wxID_ANY), - _ = wxSizer:add(AddressSz, AddressTx, zxw:flags(wide)), - ButtSz = wxBoxSizer:new(?wxHORIZONTAL), - Affirm = wxButton:new(Dialog, ?wxID_OK), - Cancel = wxButton:new(Dialog, ?wxID_CANCEL), - _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), - _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags(wide)), - _ = wxSizer:add(Sizer, AddressSz, zxw:flags(wide)), - _ = wxSizer:add(Sizer, ButtSz, zxw:flags(wide)), - ok = wxDialog:setSizer(Dialog, Sizer), - ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 200}), - ok = wxDialog:center(Dialog), - ok = wxTextCtrl:setFocus(AddressTx), - Choice = - case wxDialog:showModal(Dialog) of - ?wxID_OK -> - case wxTextCtrl:getValue(AddressTx) of - "" -> cancel; - A -> {ok, A} - end; - ?wxID_CANCEL -> - cancel - end, - ok = wxDialog:destroy(Dialog), - case Choice of - {ok, Address = "ct_" ++ _} -> open_hash2(State, Address); - {ok, Address = "th_" ++ _} -> get_contract_from_tx(State, Address); + Title = J("Retrieve Contract Source"), + Label = J("Address Hash"), + case zxw_modal_text:show(Frame, Title, [{label, Label}]) of + {ok, Address = "ct_" ++ _} -> open_hash2(State, list_to_binary(Address)); + {ok, Address = "th_" ++ _} -> get_contract_from_tx(State, list_to_binary(Address)); {ok, Turd} -> handle_troubling(State, {bad_address, Turd}); cancel -> State end. + get_contract_from_tx(State, Address) -> case hz:tx_info(Address) of {ok, #{"call_info" := #{"contract_id" := Contract}}} -> @@ -834,19 +585,20 @@ get_contract_from_tx(State, Address) -> open_hash2(State, Address) -> case hz:contract_source(Address) of + {project, [{Name, Source}]} -> + ok = tell("Retrieved ~p from ~p", [Name, Address]), + open_hash3(State, Address, Source); {ok, Source} -> + ok = tell("Retrieved uncompressed source of ~p", [Address]), open_hash3(State, Address, Source); Error -> - ok = handle_troubling(State, Error), - State + handle_troubling(State, Error) end. open_hash3(State, Address, Source) -> % TODO: Compile on load and verify the deployed hash for validity. case compile(Source) of - {ok, Build} -> - {aaci, _, FunDefs, _} = maps:get(aaci, Build), - ok = tell(info, "Compilation Succeeded!~n~tp~n~n~tp", [Build, FunDefs]), + {ok, _Build} -> add_code_page(State, {hash, Address}, Source); Other -> ok = tell(info, "Compilation Failed!~n~tp", [Other]), @@ -854,30 +606,25 @@ open_hash3(State, Address, Source) -> end. -% TODO: Break this down -- tons of things in here recur. -save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) -> +save(State = #s{prefs = Prefs, code = {Codebook, Pages}}) -> case wxNotebook:getSelection(Codebook) of ?wxNOT_FOUND -> State; Index -> case lists:nth(Index + 1, Pages) of #p{path = {file, Path}, code = Widget} -> - Source = wxTextCtrl:getValue(Widget), + Source = gd_sophia_editor:get_text(Widget), case filelib:ensure_dir(Path) of ok -> case file:write_file(Path, Source) of - ok -> - State; - Error -> - ok = handle_troubling(State, Error), - State + ok -> State; + Error -> handle_troubling(State, Error) end; Error -> - ok = handle_troubling(State, Error), - State + handle_troubling(State, Error) end; Page = #p{path = {hash, Hash}, code = Widget} -> - DefaultDir = + DefDir = case maps:find(dir, Prefs) of {ok, PrefDir} -> PrefDir; @@ -887,117 +634,76 @@ save(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) D -> filename:basename(D) end end, - Options = - [{message, J("Save Location")}, - {defaultDir, DefaultDir}, - {defaultFile, unicode:characters_to_list([Hash, ".aes"])}, - {wildCard, "*.aes"}, - {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}], - Dialog = wxFileDialog:new(Frame, Options), - NewState = - case wxFileDialog:showModal(Dialog) of - ?wxID_OK -> - Dir = wxFileDialog:getDirectory(Dialog), - case wxFileDialog:getFilename(Dialog) of - "" -> - State; - Name -> - File = - case filename:extension(Name) of - ".aes" -> Name; - _ -> Name ++ ".aes" - end, - Path = filename:join(Dir, File), - Source = gd_sophia_editor:get_text(Widget), - case filelib:ensure_dir(Path) of - ok -> - case file:write_file(Path, Source) of - ok -> - true = wxNotebook:setPageText(Codebook, Index, File), - NewPrefs = maps:put(dir, Dir, Prefs), - NewPage = Page#p{path = {file, Path}}, - NewPages = store_nth(Index + 1, NewPage, Pages), - NewCode = {Codebook, NewPages}, - State#s{prefs = NewPrefs, code = NewCode}; - Error -> - ok = handle_troubling(State, Error), - State - end; - Error -> - ok = handle_troubling(State, Error), - State - end - end; - ?wxID_CANCEL -> - State - end, - ok = wxFileDialog:destroy(Dialog), - NewState + DefName = unicode:characters_to_list([Hash, ".aes"]), + save_dialog(State, DefDir, DefName, Widget, Index, Page) end end. -% TODO: Break this down -- tons of things in here recur. -rename(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}) -> +rename(State = #s{code = {Codebook, Pages}}) -> case wxNotebook:getSelection(Codebook) of ?wxNOT_FOUND -> State; Index -> case lists:nth(Index + 1, Pages) of Page = #p{path = {file, Path}, code = Widget} -> - DefaultDir = filename:dirname(Path), - Options = - [{message, J("Save Location")}, - {defaultDir, DefaultDir}, - {defaultFile, filename:basename(Path)}, - {wildCard, "*.aes"}, - {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}], - Dialog = wxFileDialog:new(Frame, Options), - NewState = - case wxFileDialog:showModal(Dialog) of - ?wxID_OK -> - Dir = wxFileDialog:getDirectory(Dialog), - case wxFileDialog:getFilename(Dialog) of - "" -> - State; - Name -> - File = - case filename:extension(Name) of - ".aes" -> Name; - _ -> Name ++ ".aes" - end, - NewPath = filename:join(Dir, File), - Source = gd_sophia_editor:get_text(Widget), - case filelib:ensure_dir(NewPath) of - ok -> - case file:write_file(NewPath, Source) of - ok -> - true = wxNotebook:setPageText(Codebook, Index, File), - NewPrefs = maps:put(dir, Dir, Prefs), - NewPage = Page#p{path = {file, NewPath}}, - NewPages = store_nth(Index + 1, NewPage, Pages), - NewCode = {Codebook, NewPages}, - State#s{prefs = NewPrefs, code = NewCode}; - Error -> - ok = handle_troubling(State, Error), - State - end; - Error -> - ok = handle_troubling(State, Error), - State - end - end; - ?wxID_CANCEL -> - State - end, - ok = wxFileDialog:destroy(Dialog), - NewState; + DefDir = filename:dirname(Path), + DefName = filename:basename(Path), + save_dialog(State, DefDir, DefName, Widget, Index, Page); #p{path = {hash, _}} -> save(State) end end. +save_dialog(State = #s{frame = Frame, j = J, prefs = Prefs, code = {Codebook, Pages}}, + DefDir, DefName, Widget, Index, Page) -> + Options = + [{message, J("Save Location")}, + {defaultDir, DefDir}, + {defaultFile, DefName}, + {wildCard, "*.aes"}, + {style, ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT}], + Dialog = wxFileDialog:new(Frame, Options), + NewState = + case wxFileDialog:showModal(Dialog) of + ?wxID_OK -> + Dir = wxFileDialog:getDirectory(Dialog), + case wxFileDialog:getFilename(Dialog) of + "" -> + State; + Name -> + File = + case filename:extension(Name) of + ".aes" -> Name; + _ -> Name ++ ".aes" + end, + NewPath = filename:join(Dir, File), + Source = gd_sophia_editor:get_text(Widget), + case filelib:ensure_dir(NewPath) of + ok -> + case file:write_file(NewPath, Source) of + ok -> + true = wxNotebook:setPageText(Codebook, Index, File), + NewPrefs = maps:put(dir, Dir, Prefs), + NewPage = Page#p{path = {file, NewPath}}, + NewPages = store_nth(Index + 1, NewPage, Pages), + NewCode = {Codebook, NewPages}, + State#s{prefs = NewPrefs, code = NewCode}; + Error -> + handle_troubling(State, Error) + end; + Error -> + handle_troubling(State, Error) + end + end; + ?wxID_CANCEL -> + State + end, + ok = wxFileDialog:destroy(Dialog), + NewState. + + close_source(State = #s{code = {Codebook, Pages}}) -> case wxNotebook:getSelection(Codebook) of ?wxNOT_FOUND -> @@ -1033,16 +739,27 @@ load_from_tx(State, Address) -> handle_troubling(State, Error) end. -load2(State, Address) -> +load2(State = #s{cons = {_, Pages}}, Address) when is_binary(Address) -> + case lists:keyfind(Address, #c.id, Pages) of + false -> load3(State, Address); + #c{} -> State + end; +load2(State, Address) when is_list(Address) -> + load2(State, list_to_binary(Address)). + +load3(State, Address) -> case hz:contract_source(Address) of + {project, [{Name, Source}]} -> + ok = tell("Retrieved ~p from ~p", [Name, Address]), + load4(State, Address, Source); {ok, Source} -> - load3(State, Address, Source); + ok = tell("Retrieved uncompressed source of ~p", [Address]), + load4(State, Address, Source); Error -> - ok = handle_troubling(State, Error), - State + handle_troubling(State, Error) end. -load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j = J}, +load4(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j = J}, Address, Source) -> Window = wxWindow:new(Consbook, ?wxID_ANY), @@ -1060,17 +777,16 @@ load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j ConsSz = wxStaticBoxSizer:new(?wxVERTICAL, Window, [{label, J("Console")}]), ConsTxStyle = {style, ?wxTE_MULTILINE bor ?wxTE_READONLY}, ConsTx = wxTextCtrl:new(Window, ?wxID_ANY, [ConsTxStyle]), - _ = wxSizer:add(ConsSz, ConsTx, zxw:flags(wide)), - _ = wxSizer:add(ProgSz, CodeSz, [{proportion, 3}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(ProgSz, ScrollWin, [{proportion, 1}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(PageSz, ProgSz, [{proportion, 3}, {flag, ?wxEXPAND}]), - _ = wxSizer:add(PageSz, ConsSz, [{proportion, 1}, {flag, ?wxEXPAND}]), + _ = wxSizer:add(ConsSz, ConsTx, zxw:flags({wide, 5})), + _ = wxSizer:add(ProgSz, CodeSz, [{proportion, 3}, {flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), + _ = wxSizer:add(ProgSz, ScrollWin, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), + _ = wxSizer:add(PageSz, ProgSz, [{proportion, 3}, {flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), + _ = wxSizer:add(PageSz, ConsSz, [{proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}, {border, 5}]), {Out, IFaces, Build, NewButtons} = case compile(Source) of {ok, Output} -> {aaci, _, Funs, _} = maps:get(aaci, Output), Callable = maps:remove("init", Funs), - tell(info, "Callable: ~p", [Callable]), {NB, IFs} = fun_interfaces(ScrollWin, FunSz, Buttons, Callable, J), O = io_lib:format("Compilation Succeeded!~n~tp~n~nDone!~n", [Output]), {O, IFs, Output, NB}; @@ -1091,23 +807,6 @@ load3(State = #s{tabs = TopBook, cons = {Consbook, Pages}, buttons = Buttons, j State#s{cons = {Consbook, NewPages}, buttons = NewButtons}. -get_arg({list, AFs, _}) -> - lists:map(fun get_arg/1, AFs); -get_arg({record, AFs}) -> - get_record(AFs); -get_arg({tuple, AFs}) -> - list_to_tuple(lists:map(fun get_arg/1, AFs)); -get_arg({map, [Key, Value]}) -> - #{get_arg(Key) => get_arg(Value)}; -get_arg({variant, AFs, [VariantOne | _]}) -> - Elems = lists:map(fun get_arg/1, AFs), - list_to_tuple([VariantOne | Elems]); -get_arg({_, TextCtrl}) -> - wxTextCtrl:getValue(TextCtrl). - -get_record([{L, A} | T]) -> - [{L, get_arg(A)} | get_record(T)]. - fun_interfaces(ScrollWin, FunSz, Buttons, Funs, J) -> MakeIface = fun(Name, {Args, _}) -> @@ -1116,15 +815,16 @@ fun_interfaces(ScrollWin, FunSz, Buttons, Funs, J) -> FLabel = wxStaticText:new(ScrollWin, ?wxID_ANY, FunName), CallBn = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Call")}]), DryRBn = wxButton:new(ScrollWin, ?wxID_ANY, [{label, J("Dry Run")}]), - _ = wxBoxSizer:add(FS, FLabel, [{proportion, 1}, {flag, ?wxEXPAND}]), - _ = wxBoxSizer:add(FS, CallBn, [{proportion, 0}, {flag, ?wxEXPAND}]), - _ = wxBoxSizer:add(FS, DryRBn, [{proportion, 0}, {flag, ?wxEXPAND}]), + _ = wxBoxSizer:add(FS, FLabel, zxw:flags(wide)), + _ = wxBoxSizer:add(FS, CallBn, zxw:flags(base)), + _ = wxBoxSizer:add(FS, DryRBn, zxw:flags(base)), CallButton = #w{name = {Name, call}, id = wxButton:getId(CallBn), wx = CallBn}, DryRButton = #w{name = {Name, dryr}, id = wxButton:getId(DryRBn), wx = DryRBn}, - _ = wxSizer:add(FunSz, FS, zxw:flags(base)), + _ = wxSizer:add(FunSz, FS, zxw:flags({base, 5})), #f{name = Name, call = CallButton, dryrun = DryRButton, args = Args} end, - IFaces = maps:map(MakeIface, Funs), + Iterator = maps:iterator(Funs, ordered), + IFaces = maps:map(MakeIface, Iterator), NewButtons = maps:fold(fun map_iface_buttons/3, Buttons, IFaces), {NewButtons, IFaces}. @@ -1153,7 +853,8 @@ close_instance(State = #s{cons = {Consbook, Pages}, buttons = Buttons}) -> State; Index -> {#c{funs = {_, IFaces}}, NewPages} = take_nth(Index + 1, Pages), - IDs = list_iface_buttons(IFaces), + log(info, "IFaces: ~tp", [IFaces]), + IDs = list_iface_buttons(maps:values(IFaces)), NewButtons = maps:without(IDs, Buttons), true = wxNotebook:deletePage(Consbook, Index), State#s{cons = {Consbook, NewPages}, buttons = NewButtons} @@ -1174,7 +875,7 @@ compile(Source) -> case so_compiler:from_string(Source, Options) of {ok, Build} -> ACI = maps:get(aci, Build), - AACI = hz:prepare_aaci(ACI), + AACI = hz_aaci:prepare(ACI), Complete = maps:put(aaci, AACI, Build), {ok, Complete}; Other -> diff --git a/src/gd_v_netman.erl b/src/gd_v_netman.erl index 2c62efe..0657f81 100644 --- a/src/gd_v_netman.erl +++ b/src/gd_v_netman.erl @@ -1,5 +1,5 @@ -module(gd_v_netman). --vsn("0.8.1"). +-vsn("0.9.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). @@ -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(), @@ -60,7 +56,7 @@ start_link(Args) -> init({Prefs, Manifest}) -> - Lang = maps:get(lang, Prefs, en_us), + Lang = maps:get(lang, Prefs, en), Trans = gd_jt:read_translations(?MODULE), J = gd_jt:j(Lang, Trans), Wx = wx:new(), diff --git a/src/gd_v_wallman.erl b/src/gd_v_wallman.erl index 408f67e..abc325e 100644 --- a/src/gd_v_wallman.erl +++ b/src/gd_v_wallman.erl @@ -13,7 +13,7 @@ -module(gd_v_wallman). --vsn("0.8.1"). +-vsn("0.9.0"). -author("Craig Everett "). -copyright("QPQ AG "). -license("GPL-3.0-or-later"). @@ -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(), @@ -90,7 +86,7 @@ start_link(Args) -> init({Prefs, Manifest}) -> - Lang = maps:get(lang, Prefs, en_us), + Lang = maps:get(lang, Prefs, en), Trans = gd_jt:read_translations(?MODULE), J = gd_jt:j(Lang, Trans), Wx = wx:new(), diff --git a/zomp.meta b/zomp.meta index 8dd6812..df284e2 100644 --- a/zomp.meta +++ b/zomp.meta @@ -4,8 +4,8 @@ {prefix,"gd"}. {author,"Craig Everett"}. {desc,"A desktop client for the Gajumaru network of blockchain networks"}. -{package_id,{"otpr","gajudesk",{0,8,1}}}. -{deps,[{"otpr","hakuzaru",{0,8,2}}, +{package_id,{"otpr","gajudesk",{0,9,0}}}. +{deps,[{"otpr","hakuzaru",{0,9,0}}, {"otpr","zxwidgets",{1,1,0}}, {"otpr","eblake2",{1,0,1}}, {"otpr","base58",{0,1,1}},