This commit is contained in:
2026-04-06 10:59:45 +09:00
parent 713650f88b
commit 9fa365e83f
8 changed files with 439 additions and 424 deletions
+199 -36
View File
@@ -1,4 +1,4 @@
-module(gd_v_devman).
-module(gd_v_call).
-vsn("0.8.1").
-author("Craig Everett <craigeverett@qpq.swiss>").
-copyright("QPQ AG <info@qpq.swiss>").
@@ -7,34 +7,56 @@
-behavior(wx_object).
%-behavior(gd_v).
-include_lib("wx/include/wx.hrl").
-export([to_front/1]).
-export([to_front/1, result/2]).
-export([start_link/1]).
-export([init/1, terminate/2, code_change/3,
handle_call/3, handle_cast/2, handle_info/2, handle_event/2]).
-include("$zx_include/zx_logger.hrl").
-include("gd.hrl").
-include("gdl.hrl").
% Widgets
-record(w,
{name = none :: atom() | {FunName :: binary(), call | dryr},
id = 0 :: integer(),
wx = none :: none | wx:wx_object()}).
% State
-record(s,
{wx = none :: none | wx:wx_object(),
frame = none :: none | wx:wx_object(),
lang = en :: en | jp,
j = none :: none | fun(),
args = [] :: [#wx{}],
ttl = #wx{} :: #wx{},
gasprice = #wx{} :: #wx{},
gas = #wx{} :: #wx{},
amount = #wx{} :: #wx{},
retry = #wx{} :: #wx{},
copy = #wx{} :: #wx{},
done = #wx{} :: #wx{}}).
{wx = none :: none | wx:wx_object(),
frame = none :: none | wx:wx_object(),
lang = en :: en | jp,
j = none :: none | fun(),
prefs = #{} :: map(),
con_id = <<"">> :: binary(),
fundef = none :: none | fun_def(),
build = none :: none | map(),
args = [] :: [#w{}],
ttl = #w{} :: #w{},
gasprice = #w{} :: #w{},
gas = #w{} :: #w{},
amount = #w{} :: #w{},
action = #w{} :: #w{},
tx_hash = #w{} :: #w{},
out = #w{} :: #w{},
copy = #w{} :: #w{}}).
-type fun_name() :: string().
-type fun_type() :: call | dryr | init.
-type fun_def() :: {fun_name(), fun_type()}.
%%% Interface
-spec to_front(Win) -> ok
when Win :: wx:wx_object().
to_front(Win) ->
wx_object:cast(Win, to_front).
-spec result(Win, Outcome) -> ok
when Win :: wx:wx_object(),
Outcome :: term().
result(Win, Outcome) ->
wx_object:cast(Win, {result, Outcome}).
@@ -44,38 +66,61 @@ start_link(Args) ->
wx_object:start_link({local, ?MODULE}, ?MODULE, Args, []).
init({{FunName, FunType}, ConID, AACI}) ->
{aaci, ConName, FunSpecs, _} = AACI,
FunSpec = maps:get(FunName, FunSpecs),
CallType =
case FunType of
call -> J("Contract Call");
dryr -> J("Dry Run")
end,
Arity = integer_to_list(length(element(1, FunSpec))),
Title = [CallType, ": ", ConName, ".", FunName, "/", Arity],
init({Prefs, {FunName, FunType}, ConID, Build, Selected, Keys}) ->
Lang = maps:get(lang, Prefs, en),
Trans = gd_jt:read_translations(?MODULE),
J = gd_jt:j(Lang, Trans),
{aaci, ConName, FunSpecs, _} = maps:get(aaci, Build),
FunSpec = maps:get(FunName, FunSpecs),
{CallType, ActionLabel} =
case FunType of
call -> {J("Contract Call"), J("Submit Call")};
dryr -> {J("Dry Run"), J("Submit Dry Run")};
init -> {J("Deploy"), J("Deploy")}
end,
Arity = integer_to_list(length(element(1, FunSpec))),
Title = [CallType, ": ", ConName, ".", FunName, "/", Arity],
Wx = wx:new(),
Frame = wxFrame:new(Wx, ?wxID_ANY, Title),
MainSz = wxBoxSizer:new(?wxVERTICAL),
KeySz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("Signature Key")}]),
KeyPicker = wxChoice:new(Frame, ?wxID_ANY, [{choices, Keys}]),
_ = wxStaticBoxSizer:add(KeySz, KeyPicker, zxw:flags(wide)),
{ArgSz, Args} = call_arg_sizer(Frame, J, FunSpec),
{ParamSz, TTL_T, GasPriceT, GasT, AmountT} = call_param_sizer(Frame, J),
Action = #w{wx = ActionBn} = gd_lib:button(Frame, ActionLabel),
TX_Sz = wxStaticBoxSizer:new(?wxVERTICAL, Frame, [{label, J("Transaction Info")}]),
TX_Hash = #w{wx = HashTxt} = gd_lib:mono_text(TX_Sz, tx_hash),
Line = wxStaticLine:new(TX_Sz, [{style, ?wxLI_HORIZONTAL}]),
Out = #w{wx = OutTxt} = gd_lib:mono_text(TX_Sz, out),
Copy = #w{wx = CopyBn} = gd_lib:button(Frame, J("Copy")),
_ = wxStaticBoxSizer:add(TX_Sz, HashTxt, zxw:flags({wide, 5})),
_ = wxStaticBoxSizer:add(TX_Sz, Line, zxw:flags({wide, 5})),
_ = wxStaticBoxSizer:add(TX_Sz, OutTxt, zxw:flags({wide, 5})),
_ = wxStaticBoxSizer:add(TX_Sz, CopyBn, zxw:flags({wide, 5})),
_ = wxSizer:add(MainSz, ArgSz, zxw:flags({wide, 5})),
_ = wxSizer:add(MainSz, KeySz, zxw:flags({wide, 5})),
_ = wxSizer:add(MainSz, ParamSz, zxw:flags({wide, 5})),
_ = wxSizer:add(MainSz, ActionBn, zxw:flags({wide, 5})),
_ = wxSizer:add(MainSz, TX_Sz, zxw:flags({wide, 5})),
_ = wxFrame:setSizer(Frame, MainSz),
_ = wxSizer:layout(MainSz),
ok = gd_v:safe_size(Frame, Prefs),
ok = wxFrame:connect(Frame, close_window),
ok = wxFrame:connect(Frame, command_button_clicked),
% TODO: Decide where to put the frame -- should probably be slightly randomized.
% ok = wxFrame:center(Frame),
true = wxFrame:show(Frame),
State =
#s{wx = Wx, frame = Frame, j = J, prefs = Prefs,
args = Args,
ttl = TTL_T, gasprice = GasPriceT, gas = GasT, amount = AmountT,
retry = RetryBn, copy = CopyBn, done = DoneBn},
#s{wx = Wx, frame = Frame, j = J, prefs = Prefs,
con_id = ConID, build = Build, args = Args,
ttl = TTL_T, gasprice = GasPriceT, gas = GasT, amount = AmountT,
action = Action, copy = Copy},
{Frame, State}.
@@ -155,6 +200,124 @@ call_param_sizer(Frame, J) ->
{ParamSz, TTL_Tx, GasP_Tx, Gas_Tx, Amount_Tx}.
handle_call(Unexpected, From, State) ->
ok = log(warning, "Unexpected call from ~tp: ~tp~n", [From, Unexpected]),
{noreply, State}.
handle_cast(Unexpected, State) ->
ok = log(warning, "Unexpected cast: ~tp~n", [Unexpected]),
{noreply, State}.
handle_info(Unexpected, State) ->
ok = log(warning, "Unexpected info: ~tp~n", [Unexpected]),
{noreply, State}.
handle_event(#wx{event = #wxCommand{type = command_button_clicked}, id = ID},
State = #s{action = #w{id = ID}}) ->
NewState = engage(State);
{noreply, NewState};
handle_event(Event, State) ->
ok = tell(info, "Unexpected event ~tp State: ~tp~n", [Event, State]),
{noreply, State}.
code_change(_, State, _) ->
{ok, State}.
terminate(Reason, State) ->
ok = log(info, "Reason: ~tp, State: ~tp", [Reason, State]),
wx:destroy().
handle_troubling(State = #s{frame = Frame}, Info) ->
ok = wxFrame:raise(Frame),
ok = zxw:show_message(Frame, Info),
State.
engage(State = #s{j = J}) ->
case gd_con:chain_id() of
{ok, ChainID} -> engage(State, ChainID);
Error -> handle_troubling(State, Error)
end.
engage(State = #s{fundef = {"init", init}, build = Build}, ChainID) ->
{CallerID, Nonce, TTL, GasPrice, Gas, Amount} = params(State),
Args = args(State),
case hz:contract_create_built(CallerID,
Nonce, Amount, TTL, Gas, GasPrice,
Build, InitArgs) of
{ok, CreateTX} -> deploy(State, ChainID, CallerID, CreateTX);
Error -> handle_troubling(State, Error)
end;
engage(State = #s{con_id = ConID, build = Build, fundef = {Name, Type}}, ChainID) ->
AACI = maps:get(aaci, AACI),
{PK, Nonce, TTL, GasP, Gas, Amount} = params(State),
Args = args(State),
case hz:contract_call(PK, Nonce, Gas, GasP, Amount, TTL, AACI, ConID, Name, Args) of
{ok, UnsignedTX} ->
case Type of
call -> do_call(State, ChainID, PK, UnsignedTX);
dryr -> do_dry_run(State, ConID, UnsignedTX)
end;
Error ->
handle_troubling(State, Error)
end.
% NEXT: Complete the signature and enter check_tx/2
deploy(State, ChainID, CallerID, CreateTX) ->
SignedTX = hz:sign_tx(CreateTX, SecKey, ChainID),
tell(info, "SignedTX: ~p", [SignedTX]),
case hz:post_tx(SignedTX) of
{ok, Data = #{"tx_hash" := TXHash}} ->
ok = tell("Contract deploy TX succeded with: ~p", [TXHash]),
do_deploy3(Data);
{ok, WTF} ->
gd_v_devman:trouble({error, WTF});
Error ->
gd_v_devman:trouble(Error)
end.
do_deploy3(#{"tx_hash" := TXHash}) ->
case hz:tx_info(TXHash) of
{ok, #{"call_info" := #{"return_type" := "ok", "contract_id" := ConID}}} ->
gd_v_devman:open_contract(ConID);
{error, "Tx not mined"} ->
gd_v_devman:trouble({tx_hash, TXHash});
{ok, Reason = #{"call_info" := #{"return_type" := "revert"}}} ->
gd_v_devman:trouble({error, Reason});
Error ->
gd_v_devman:trouble(Error)
end.
do_call(State, ChainID, CallerID, UnsignedTX) ->
case gd_con:sign_call(ChainID, CallerID, UnsignedTX) of
{ok, SignedTX} -> do_call(State, SignedTX);
Error -> handle_troubling(State, Error)
end.
do_call(State, SignedTX) ->
case hz:post_tx(SignedTX) of
{ok, TX_Hash} ->
do_dry_run(State, ConID, TX) ->
case hz:dry_run(TX) of
{ok, Result} -> gd_v_devman:dryrun_result(ConID, Result);
Other -> gd_v_devmam:trouble({error, ConID, Other})
end.
ok = gd_con:dry_run(ConID, TX),
State.
textify({integer, _, _}) -> "int";
textify({boolean, _, _}) -> "bool";
textify({{bytes, [I]}, _, _}) -> io_lib:format("bytes(~w)", [I]);