From df441184639ab4c6ac68b330dfebbf6c99f88936 Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Thu, 8 Jan 2026 15:59:46 +0900 Subject: [PATCH] WIP --- src/gd_v_devman.erl | 225 ++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 110 deletions(-) diff --git a/src/gd_v_devman.erl b/src/gd_v_devman.erl index 310458f..98abb7a 100644 --- a/src/gd_v_devman.erl +++ b/src/gd_v_devman.erl @@ -8,7 +8,7 @@ %-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]). @@ -87,6 +87,14 @@ open_contract(Address) -> wx_object:cast(?MODULE, {open_contract, 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 when ConID :: gajudesk:id(), CallInfo :: map(). @@ -220,6 +228,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}; @@ -327,7 +338,7 @@ clicked2(State, Contract, Name) -> clicked3(State = #s{frame = Frame, j = J}, Contract, Name = {FunName, FunType}, Selected, Keys) -> {FunName, FunType} = Name, - #c{build = #{aaci := AACI}} = Contract, + #c{id = ConID, build = #{aaci := AACI}} = Contract, {aaci, ConName, FunSpecs, _} = AACI, FunSpec = maps:get(FunName, FunSpecs), tell("FunName: ~tp", [FunName]), @@ -365,19 +376,17 @@ clicked3(State = #s{frame = Frame, j = J}, Contract, Name = {FunName, FunType}, ID = wxChoice:getString(KeyPicker, wxChoice:getSelection(KeyPicker)), PK = unicode:characters_to_binary(ID), Controls = - [{{"TTL", integer}, TTL_Tx}, - {{"Gas Price", integer}, GasP_Tx}, - {{"Gas", integer}, Gas_Tx}, - {{"Amount", integer}, Amount_Tx}], - % TODO: Unify args from the call params and fun args - % Try to give type feedback, probably based on whether Sophia or Erlang terms are chosen + [{{"TTL", fun gt_0/1}, TTL_Tx}, + {{"Gas Price", fun gt_0/1}, GasP_Tx}, + {{"Gas", fun gt_0/1}, Gas_Tx}, + {{"Amount", fun gte_0/1}, Amount_Tx}], case call_params(Controls) of {ok, [TTL, GasP, Gas, Amount]} -> + {Message, CArgs} = extract_args(ArgControls), + ok = gd_v_devman:write_console(ConID, Message), {ok, Nonce} = hz:next_nonce(PK), - case extract_args(ArgControls) of - {ok, Args} -> {ok, {PK, Nonce, TTL, GasP, Gas, Amount}, Args}; - E -> E - end; + CallParams = {PK, Nonce, TTL, GasP, Gas, Amount}, + {ok, CallParams, CArgs}; E -> E end; @@ -386,19 +395,29 @@ clicked3(State = #s{frame = Frame, j = J}, Contract, Name = {FunName, FunType}, end, ok = wxDialog:destroy(Dialog), case Outcome of - {ok, Params} -> clicked4(State, Contract, Name, Params); - cancel -> State; - Error -> handle_troubling(State, Error) + {ok, Params, Args} -> clicked4(State, Contract, Name, Params, Args); + cancel -> State; + Error -> handle_troubling(State, Error) end. -extract_args({Name, Control}, {OK, Errors}) -> - extract_args(Controls) -> extract_args(Controls, []). -extract_args([H | T], A) -> - case wxTextCtrl:getValue(H) of - "" -> { +extract_args([], Acc) -> + pack_args(Acc); +extract_args([{Name, Control} | T], Acc) -> + String = wxTextCtrl:getValue(Control), + extract_args(T, [{Name, String} | Acc]). + +pack_args(Args) -> + pack_args(Args, [], []). + +pack_args([], M, A) -> + Message = unicode:characters_to_list(["Call Args:\n", M]), + {Message, A}; +pack_args([{N, S} | T], M, A) -> + pack_args(T, [[N, ": ", S, "\n"] | M], [S | A]). + call_arg_sizer(Dialog, J, {CallArgs, ReturnType}) -> SpecSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Function Spec")}]), @@ -414,19 +433,24 @@ call_sizer(Dialog, J, []) -> _ = wxStaticBoxSizer:add(CallSz, Args, zxw:flags({wide, 5})), {CallSz, [], {500, 500}}; call_sizer(Dialog, J, CallArgs) -> + ScrollWin = wxScrolledWindow:new(Dialog), + ScrollSz = wxBoxSizer:new(?wxVERTICAL), + ok = wxScrolledWindow:setSizerAndFit(ScrollWin, ScrollSz), + ok = wxScrolledWindow:setScrollRate(ScrollWin, 5, 5), CallSz = wxStaticBoxSizer:new(?wxVERTICAL, Dialog, [{label, J("Call Args")}]), GridSz = wxFlexGridSizer:new(2, [{gap, {4, 4}}]), ok = wxFlexGridSizer:setFlexibleDirection(GridSz, ?wxHORIZONTAL), ok = wxFlexGridSizer:addGrowableCol(GridSz, 1), AddArg = - fun(Arg = {Name, Type}) -> - L = wxStaticText:new(Dialog, ?wxID_ANY, [Name, " : ", textify(Type)]), - C = wxTextCtrl:new(Dialog, ?wxID_ANY), + fun({Name, Type}) -> + L = wxStaticText:new(ScrollWin, ?wxID_ANY, [Name, " : ", textify(Type)]), + C = wxTextCtrl:new(ScrollWin, ?wxID_ANY), _ = wxFlexGridSizer:add(GridSz, L, zxw:flags({base, 5})), _ = wxFlexGridSizer:add(GridSz, C, zxw:flags({wide, 5})), - {Arg, C} + {Name, C} end, - _ = wxStaticBoxSizer:add(CallSz, GridSz, zxw:flags(wide)), + _ = wxStaticBoxSizer:add(ScrollSz, GridSz, zxw:flags(wide)), + _ = wxStaticBoxSizer:add(CallSz, ScrollWin, zxw:flags(wide)), Controls = lists:map(AddArg, CallArgs), {CallSz, Controls, {600, 700}}. @@ -479,29 +503,52 @@ call_param_sizer(Dialog, J) -> {ParamSz, TTL_Tx, GasP_Tx, Gas_Tx, Amount_Tx}. call_params(Controls) -> - call_params(Controls, []). + call_params(Controls, {[], []}). -call_params([], A) -> - {ok, lists:reverse(A)}; -call_params([{L, C} | T], A) -> +call_params([], {Acc, []}) -> + {ok, lists:reverse(Acc)}; +call_params([], {_, Errors}) -> + {error, lists:reverse(Errors)}; +call_params([{{Label, Validate}, Control} | T], {Acc, Errors}) -> + String = wxTextCtrl:getValue(Control), + case Validate(String) of + {ok, Value} -> call_params(T, {[Value | Acc], Errors}); + {error, Reason} -> call_params(T, {Acc, [{Label, Reason} | Errors]}) + end. + +gt_0(S) -> + C = "Must be an integer greater than 0", R = try - {ok, list_to_integer(wxTextCtrl:getValue(C))} + {ok, list_to_integer(S)} catch - error:badarg -> {error, {L, not_an_integer}} + error:badarg -> {error, {S, C}} end, case R of - {ok, N} when N >= 0 -> call_params(T, [N | A]); - {ok, N} when N < 0 -> {error, {L, neg_integer}}; - Error -> Error + {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. clicked4(State, - #c{id = ConID, build = #{aaci := AACI}, funs = {_, Funs}}, + #c{id = ConID, build = #{aaci := AACI}}, {Name, Type}, - {PK, Nonce, TTL, GasP, Gas, Amt}) -> - #f{args = ArgFields} = maps:get(Name, Funs), - Args = lists:map(fun get_arg/1, ArgFields), + {PK, Nonce, TTL, GasP, Gas, Amt}, + Args) -> case hz:contract_call(PK, Nonce, Gas, GasP, Amt, TTL, AACI, ConID, Name, Args) of {ok, UnsignedTX} -> case Type of @@ -522,28 +569,27 @@ do_dry_run(State, 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). @@ -687,55 +733,44 @@ deploy3(State, InitSpec, Build) -> 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}]), + 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(ScrollWin, J), + {ArgSz, ArgControls, Dimensions} = call_arg_sizer(Dialog, J, InitSpec), + {ParamSz, TTL_Tx, GasP_Tx, Gas_Tx, Amount_Tx} = call_param_sizer(Dialog, J), ButtSz = wxBoxSizer:new(?wxHORIZONTAL), Affirm = wxButton:new(Dialog, ?wxID_OK), Cancel = wxButton:new(Dialog, ?wxID_CANCEL), - _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags(wide)), - _ = 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}]), + _ = wxBoxSizer:add(ButtSz, Affirm, zxw:flags({wide, 5})), + _ = wxBoxSizer:add(ButtSz, Cancel, zxw:flags({wide, 5})), + _ = wxSizer:add(Sizer, KeySz, zxw:flags({base, 5})), + _ = wxSizer:add(Sizer, ArgSz, zxw:flags({wide, 5})), + _ = wxSizer:add(Sizer, ParamSz, zxw:flags({base, 5})), + _ = wxSizer:add(Sizer, ButtSz, zxw:flags({base, 5})), ok = wxDialog:setSizer(Dialog, Sizer), ok = wxBoxSizer:layout(Sizer), - ok = wxDialog:setSize(Dialog, {500, 500}), + ok = wxDialog:setSize(Dialog, Dimensions), ok = wxDialog:center(Dialog), Outcome = case wxDialog:showModal(Dialog) of ?wxID_OK -> ID = wxChoice:getString(KeyPicker, wxChoice:getSelection(KeyPicker)), PK = unicode:characters_to_binary(ID), - IArgs = lists:map(fun get_arg/1, ArgFields), Controls = - [{"TTL", TTL_Tx}, - {"Gas Price", GasP_Tx}, - {"Gas", Gas_Tx}, - {"Amount", Amount_Tx}], + [{{"TTL", fun gt_0/1}, TTL_Tx}, + {{"Gas Price", fun gt_0/1}, GasP_Tx}, + {{"Gas", fun gt_0/1}, Gas_Tx}, + {{"Amount", fun gte_0/1}, Amount_Tx}], case call_params(Controls) of {ok, [TTL, GasP, Gas, Amount]} -> + {Message, CArgs} = extract_args(ArgControls), + ok = tell(Message), {ok, Nonce} = hz:next_nonce(PK), DeployParams = {PK, Nonce, TTL, GasP, Gas, Amount}, - {ok, DeployParams, IArgs}; + {ok, DeployParams, CArgs}; E -> E end; @@ -755,19 +790,6 @@ deploy5(State, 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), @@ -1160,23 +1182,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, _}) ->