From b071467faaded874bed82fe06204bb103f6fe56f Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Wed, 6 May 2026 12:46:26 +0900 Subject: [PATCH] Make Contract Calls Great Again --- src/gd_con.erl | 44 +++++++++++++++++++++- src/gd_v_call.erl | 92 +++++++++++++++++++++++++++++++-------------- src/gd_v_devman.erl | 33 ++-------------- 3 files changed, 110 insertions(+), 59 deletions(-) diff --git a/src/gd_con.erl b/src/gd_con.erl index 1e74652..8745305 100644 --- a/src/gd_con.erl +++ b/src/gd_con.erl @@ -16,7 +16,7 @@ refresh/0, 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, + deploy/1, prompt_call/3, list_calls/0, open_contract/1, show_call/2, 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]). @@ -210,7 +210,7 @@ deploy(Build) -> when FunDef :: {FunName, FunType}, FunName :: string(), FunType :: call | dryr | init, - ConID :: none | binary(), + ConID :: none | string(), Build :: map(). % Fixme prompt_call(FunDef, ConID, Build) -> @@ -229,6 +229,28 @@ list_calls() -> gen_server:call(?MODULE, list_calls). +-spec open_contract(ConID) -> ok + when ConID :: string(). +%% @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) -> + gen_server:cast(?MODULE, {open_contract, ConID}). + + +-spec show_call(ConID, Info) -> ok + when ConID :: string(), + Info :: map(). +%% @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) -> + gen_server:cast(?MODULE, {show_call, ConID, Info}). + + -spec make_key(Type, Size, Name, Seed, Encoding, Transform) -> ok when Type :: {eddsa, ed25519}, Size :: 256, @@ -499,6 +521,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}, State) -> + NewState = do_open_contract(ConID, State), + {noreply, NewState}; +handle_cast({show_call, ConID, Info}, State) -> + NewState = do_show_call(ConID, Info, State), + {noreply, NewState}; handle_cast({add_node, New}, State) -> NewState = do_add_node(New, State), {noreply, NewState}; @@ -991,6 +1019,18 @@ do_drop_key(ID, State = #s{wallet = W, wallets = Wallets, pass = Pass}) -> State#s{wallet = NewWallet}. +do_open_contract(ConID, State) -> + NewState = do_show_ui(gd_v_devman, State), + ok = gd_v_devman:open_contract(ConID), + NewState. + + +do_show_call(ConID, Info, State) -> + NewState = do_show_ui(gd_v_devman, State), + 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 diff --git a/src/gd_v_call.erl b/src/gd_v_call.erl index 08e5a16..e131c74 100644 --- a/src/gd_v_call.erl +++ b/src/gd_v_call.erl @@ -25,6 +25,7 @@ prefs = #{} :: map(), con_id = <<"">> :: binary(), fundef = none :: none | fun_def(), + funret = none :: none | term(), % FIXME build = none :: none | map(), args = [] :: [#w{}], kp = #w{} :: #w{}, @@ -94,14 +95,14 @@ init({Prefs, FunDef = {FunName, FunIlk}, ConID, Build, Selected, Keys}) -> Trans = gd_jt:read_translations(?MODULE), J = gd_jt:j(Lang, Trans), {aaci, ConName, FunSpecs, _} = maps:get(aaci, Build), - FunSpec = maps:get(FunName, FunSpecs), + 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(element(1, FunSpec))), + Arity = integer_to_list(length(FunArgs)), Title = [CallTypeLabel, ": ", ConName, ".", FunName, "/", Arity], Wx = wx:new(), Frame = wxFrame:new(Wx, ?wxID_ANY, Title), @@ -127,19 +128,19 @@ init({Prefs, FunDef = {FunName, FunIlk}, ConID, Build, Selected, Keys}) -> 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({base, 5})), + _ = wxStaticBoxSizer:add(TX_Sz, HashT, zxw:flags({base, 5})), + _ = wxStaticBoxSizer:add(TX_Sz, InfoT, zxw:flags({wide, 5})), ArgSzArgs = case HasArgs of - true -> {wide, 5}; - false -> {base, 5} + true -> [{proportion, 2}, {flag, ?wxEXPAND bor ?wxALL}, {border, 5}]; + false -> zxw:flags({base, 5}) end, - _ = wxSizer:add(MainSz, ArgSz, zxw:flags(ArgSzArgs)), + _ = 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, 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}), @@ -148,9 +149,9 @@ init({Prefs, FunDef = {FunName, FunIlk}, ConID, Build, Selected, Keys}) -> ok = wxFrame:connect(Frame, command_button_clicked), true = wxFrame:show(Frame), State = - #s{wx = Wx, frame = Frame, j = J, prefs = Prefs, - fundef = FunDef, con_id = ConID, build = Build, - args = Args, kp = KP, params = Params, + #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}, @@ -306,7 +307,7 @@ code_change(_, State, _) -> retire(State = #s{frame = Frame}) -> ok = wxWindow:destroy(Frame), - {noreply, State}. + {stop, normal, State}. terminate(Reason, State) -> @@ -433,10 +434,44 @@ do_call2(State = #s{action = #w{wx = ActionB}}, SignedTX) -> do_dry_run(State = #s{action = #w{wx = ActionB}}, ConID, TX) -> _ = wxButton:disable(ActionB), case hz:dry_run(TX) of - {ok, Result} -> update_info(State#s{tx_info = Result}); + {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{frame = Frame, j = J, fundef = {_, init}, @@ -448,15 +483,22 @@ check_tx(State = #s{frame = Frame, 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), ok = wxWindow:destroy(Frame), - exit(0); + 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("Deploy")), + ok = wxButton:setLabel(ActionB, J("Check Depoyment Status")), _ = wxButton:enable(ActionB), - State#s{status = none} + State end; check_tx(State = #s{j = J, con_id = ConID, @@ -476,18 +518,18 @@ check_tx(State = #s{j = J, _ = wxButton:disable(ActionB), ok = wxTextCtrl:setValue(ReturnT, Value), ok = wxTextCtrl:setValue(InfoT, FormattedInfo), - ok = gd_con:show_call_result(ConID, Info), - update_info(State#s{status = included, tx_info = Info}); + ok = gd_con:show_call(ConID, Info), + State#s{status = included, tx_info = Info}; {ok, Reason = #{"call_info" := #{"return_type" := "revert"}}} -> _ = wxButton:enable(CopyB), _ = wxButton:disable(ActionB), FormattedInfo = io_lib:format("~tp", [Reason]), ok = wxTextCtrl:setValue(ReturnT, FormattedInfo), % FIXME ok = wxTextCtrl:setValue(InfoT, FormattedInfo), - ok = gd_con:show_call_result(ConID, Reason), + ok = gd_con:show_call(ConID, Reason), State#s{status = rejected, tx_info = Reason}; {error, "Tx not mined"} -> - ok = wxTextCtrl:setValue(InfoT, J("Transaction not yet mined.")), + ok = wxTextCtrl:setValue(InfoT, J("[Transaction not yet mined.]")), ok = wxButton:setLabel(ActionB, J("Check Transaction Status")), _ = wxButton:enable(ActionB), State; @@ -502,16 +544,10 @@ check_tx(State = #s{tx_data = TXData, State. -update_info(State = #s{tx_info = TXInfo, return = #w{wx = Return}}) -> - Formatted = io_lib:format("TXInfo: ~p~n", [TXInfo]), - ok = wxTextCtrl:setValue(Return, Formatted), - State. - - copy(#s{tx_info = none}) -> ok; -copy(#s{return = #w{wx = Return}}) -> - Output = wxTextCtrl:getValue(Return), +copy(#s{return = #w{wx = ReturnT}}) -> + Output = wxTextCtrl:getValue(ReturnT), gd_lib:copy_to_clipboard(Output). diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index 0b09b08..4bc5a5a 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -559,41 +559,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 + 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, Address); {ok, Address = "th_" ++ _} -> get_contract_from_tx(State, 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}}} ->