add parse_tx_info and read_contract_getter
parse_tx_info takes the output of tx_info OR dry_run and strips it down to a cb_ encoded binary, and then passes that cb_ encoded binary to decode_bytearray, using the Format specified. read_contract_getter combines contract_call and dry_run, but automatically identifies the owner of the contract, and uses that as the caller, and gives the caller a huge amount of gajus for the purpose of the dry run, so that the call always succeeds. This operation should be available in the node itself, rather than requiring us to do this huge back and forth for something as simple as reading the contents of the blockchain, but at least we can abstract over this in the tooling, and save the user from having to think about these steps.
This commit is contained in:
+131
-34
@@ -46,6 +46,7 @@
|
|||||||
acc_pending_txs/1,
|
acc_pending_txs/1,
|
||||||
next_nonce/1,
|
next_nonce/1,
|
||||||
dry_run/1, dry_run/2, dry_run/3, % dry_run_map/1,
|
dry_run/1, dry_run/2, dry_run/3, % dry_run_map/1,
|
||||||
|
read_contract_getter/4, read_contract_getter/5,
|
||||||
tx/1, tx_info/1,
|
tx/1, tx_info/1,
|
||||||
post_tx/1,
|
post_tx/1,
|
||||||
contract/1, contract_code/1, contract_source/1,
|
contract/1, contract_code/1, contract_source/1,
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
contract_call/5,
|
contract_call/5,
|
||||||
contract_call/6,
|
contract_call/6,
|
||||||
contract_call/10,
|
contract_call/10,
|
||||||
|
parse_tx_info/2,
|
||||||
decode_bytearray/2,
|
decode_bytearray/2,
|
||||||
spend/5, spend/10,
|
spend/5, spend/10,
|
||||||
sign_tx/2, sign_tx/3,
|
sign_tx/2, sign_tx/3,
|
||||||
@@ -627,7 +629,8 @@ dry_run(TX) ->
|
|||||||
|
|
||||||
-spec dry_run(TX, Accounts) -> {ok, Result} | {error, Reason}
|
-spec dry_run(TX, Accounts) -> {ok, Result} | {error, Reason}
|
||||||
when TX :: binary() | string(),
|
when TX :: binary() | string(),
|
||||||
Accounts :: [pubkey()],
|
Accounts :: [Account],
|
||||||
|
Account :: {pubkey(), integer()} | #{string() => term()},
|
||||||
Result :: term(), % FIXME
|
Result :: term(), % FIXME
|
||||||
Reason :: term(). % FIXME
|
Reason :: term(). % FIXME
|
||||||
%% @doc
|
%% @doc
|
||||||
@@ -643,7 +646,8 @@ dry_run(TX, Accounts) ->
|
|||||||
|
|
||||||
-spec dry_run(TX, Accounts, KBHash) -> {ok, Result} | {error, Reason}
|
-spec dry_run(TX, Accounts, KBHash) -> {ok, Result} | {error, Reason}
|
||||||
when TX :: binary() | string(),
|
when TX :: binary() | string(),
|
||||||
Accounts :: [pubkey()],
|
Accounts :: [Account],
|
||||||
|
Account :: {pubkey(), integer()} | #{string() => term()},
|
||||||
KBHash :: binary() | string(),
|
KBHash :: binary() | string(),
|
||||||
Result :: term(), % FIXME
|
Result :: term(), % FIXME
|
||||||
Reason :: term(). % FIXME
|
Reason :: term(). % FIXME
|
||||||
@@ -652,21 +656,85 @@ dry_run(TX, Accounts) ->
|
|||||||
%% hash provided.
|
%% hash provided.
|
||||||
|
|
||||||
dry_run(TX, Accounts, KBHash) ->
|
dry_run(TX, Accounts, KBHash) ->
|
||||||
|
NAccounts = lists:map(fun normalize_account/1, Accounts),
|
||||||
KBB = to_binary(KBHash),
|
KBB = to_binary(KBHash),
|
||||||
TXB = to_binary(TX),
|
TXB = to_binary(TX),
|
||||||
DryData = #{top => KBB,
|
DryData = #{top => KBB,
|
||||||
accounts => Accounts,
|
accounts => NAccounts,
|
||||||
txs => [#{tx => TXB}],
|
txs => [#{tx => TXB}],
|
||||||
tx_events => true},
|
tx_events => true},
|
||||||
JSON = zj:binary_encode(DryData),
|
JSON = zj:binary_encode(DryData),
|
||||||
request("/v3/dry_run", JSON).
|
request("/v3/dry_run", JSON).
|
||||||
|
|
||||||
|
normalize_account({Pubkey, Amount}) ->
|
||||||
|
PubkeyBin = unicode:characters_to_binary(Pubkey),
|
||||||
|
#{"pub_key" => PubkeyBin, "amount" => Amount};
|
||||||
|
normalize_account(Val) ->
|
||||||
|
Val.
|
||||||
|
|
||||||
|
|
||||||
% TODO
|
% TODO
|
||||||
%dry_run_map(Map) ->
|
%dry_run_map(Map) ->
|
||||||
% JSON = zj:binary_encode(Map),
|
% JSON = zj:binary_encode(Map),
|
||||||
% request("/v3/dry_run", JSON).
|
% request("/v3/dry_run", JSON).
|
||||||
|
|
||||||
|
parse_tx_info({error, Reason}, _) ->
|
||||||
|
{error, Reason};
|
||||||
|
parse_tx_info({ok, Result}, Format) ->
|
||||||
|
parse_tx_info(Result, Format);
|
||||||
|
parse_tx_info(#{"call_info" := #{"contract_id" := Contract}}, deploy) ->
|
||||||
|
% TODO: What happens if a contract deploy goes wrong?
|
||||||
|
{ok, Contract};
|
||||||
|
parse_tx_info(#{"call_info" := #{"return_type" := Status,
|
||||||
|
"return_value" := Value}},
|
||||||
|
Format) ->
|
||||||
|
parse_tx_value(Status, Value, Format);
|
||||||
|
parse_tx_info(#{"reason" := Reason,
|
||||||
|
"parameter" := Parameter,
|
||||||
|
"info" := #{"error" := Reason2,
|
||||||
|
"path" := Path,
|
||||||
|
"data" := Data}},
|
||||||
|
_)->
|
||||||
|
% Overall dry run error. Informative, but annoyingly inconsistent with all
|
||||||
|
% other cases.
|
||||||
|
{error, {Reason, Reason2, [Parameter | Path], Data}};
|
||||||
|
parse_tx_info(#{"results" := Results}, Format) ->
|
||||||
|
% Dry run result, could be multiple results or one, and each could be a
|
||||||
|
% success or an error.
|
||||||
|
parse_tx_info(Results, Format);
|
||||||
|
parse_tx_info([Next, Then | Rest], Format) ->
|
||||||
|
case Next of
|
||||||
|
#{"call_obj" := #{"return_type" := "ok"}} ->
|
||||||
|
% Success. Assume this transaction was just setting up conditions
|
||||||
|
% for later transactions, and move on.
|
||||||
|
parse_tx_info([Then | Rest], Format);
|
||||||
|
_ ->
|
||||||
|
% Some error. Stop here and parse the error out.
|
||||||
|
parse_tx_info(Next, Format)
|
||||||
|
end;
|
||||||
|
parse_tx_info([Last], Format) ->
|
||||||
|
parse_tx_info(Last, Format);
|
||||||
|
parse_tx_info(#{"reason" := Message}, _) ->
|
||||||
|
% Dry run error for individual tx.
|
||||||
|
{error, Message};
|
||||||
|
parse_tx_info(#{"call_obj" := #{"return_type" := Status,
|
||||||
|
"return_value" := Value}},
|
||||||
|
Format) ->
|
||||||
|
% Dry run result. At this point we can parse it the same way we parse
|
||||||
|
% tx_info.
|
||||||
|
parse_tx_value(Status, Value, Format).
|
||||||
|
|
||||||
|
parse_tx_value("revert", Value, _) ->
|
||||||
|
Message = decode_bytearray(Value, fate),
|
||||||
|
{error, {abort, Message}};
|
||||||
|
parse_tx_value("error", Value, _) ->
|
||||||
|
% gmser takes binary inputs and gives binary outputs
|
||||||
|
EncodedBinary = list_to_binary(Value),
|
||||||
|
{contract_bytearray, Binary} = gmser_api_encoder:decode(EncodedBinary),
|
||||||
|
Message = binary_to_list(Binary),
|
||||||
|
{error, {contract_error, Message}};
|
||||||
|
parse_tx_value("ok", Value, Format) ->
|
||||||
|
decode_bytearray(Value, Format).
|
||||||
|
|
||||||
-spec decode_bytearray_fate(EncodedStr) -> {ok, Result} | {error, Reason}
|
-spec decode_bytearray_fate(EncodedStr) -> {ok, Result} | {error, Reason}
|
||||||
when EncodedStr :: binary() | string(),
|
when EncodedStr :: binary() | string(),
|
||||||
@@ -720,6 +788,32 @@ decode_bytearray2(FATE, sophia) -> hz_sophia:fate_to_list(FATE);
|
|||||||
decode_bytearray2(FATE, {sophia, Type}) -> hz_sophia:fate_to_list(Type, FATE);
|
decode_bytearray2(FATE, {sophia, Type}) -> hz_sophia:fate_to_list(Type, FATE);
|
||||||
decode_bytearray2(FATE, {erlang, Type}) -> hz_aaci:fate_to_erlang(Type, FATE).
|
decode_bytearray2(FATE, {erlang, Type}) -> hz_aaci:fate_to_erlang(Type, FATE).
|
||||||
|
|
||||||
|
read_contract_getter(AACI, ConID, Fun, Args) ->
|
||||||
|
case contract(ConID) of
|
||||||
|
{ok, {}} ->
|
||||||
|
CallerID = ConID,
|
||||||
|
read_contract_getter(CallerID, AACI, ConID, Fun, Args);
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
read_contract_getter(CallerID, AACI, ConID, Fun, Args) ->
|
||||||
|
case convert_args(AACI, Fun, Args) of
|
||||||
|
{ok, {ArgsFATE, ReturnFormat}} ->
|
||||||
|
read_contract_getter2(CallerID, ConID, Fun, ArgsFATE, ReturnFormat);
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
|
read_contract_getter2(CallerID, ConID, Fun, Args, ReturnFormat) ->
|
||||||
|
case contract_call(CallerID, {}, ConID, Fun, {fate, Args}) of
|
||||||
|
{ok, TX} ->
|
||||||
|
Result = dry_run(TX, [{CallerID, 1 bsl 80}]),
|
||||||
|
parse_tx_info(Result, ReturnFormat);
|
||||||
|
{error, Reason} ->
|
||||||
|
{error, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
to_binary(S) when is_binary(S) -> S;
|
to_binary(S) when is_binary(S) -> S;
|
||||||
to_binary(S) when is_list(S) -> list_to_binary(S).
|
to_binary(S) when is_list(S) -> list_to_binary(S).
|
||||||
|
|
||||||
@@ -1614,44 +1708,47 @@ min_gas() ->
|
|||||||
200_000.
|
200_000.
|
||||||
|
|
||||||
|
|
||||||
encode_call_data({aaci, _ContractName, FunDefs, _TypeDefs}, Fun, Args) ->
|
encode_call_data(AACI, Fun, Args) ->
|
||||||
case maps:find(Fun, FunDefs) of
|
case convert_args(AACI, Fun, Args) of
|
||||||
{ok, {ArgDef, _ResultDef}} -> encode_call_data2(ArgDef, Fun, Args);
|
{ok, {ArgsFATE, _}} ->
|
||||||
error -> {error, bad_fun_name}
|
gmb_fate_abi:create_calldata(Fun, ArgsFATE);
|
||||||
end;
|
{error, Reason} ->
|
||||||
encode_call_data({aaci, Label}, Fun, Args) ->
|
{error, Reason}
|
||||||
case hz_man:lookup_aaci(Label) of
|
|
||||||
{ok, AACI} -> encode_call_data(AACI, Fun, Args);
|
|
||||||
error -> {error, aaci_not_found}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
encode_call_data2(ArgDef, Fun, {sophia, Args}) ->
|
convert_args(_, _, {fate, Args}) ->
|
||||||
case convert(ArgDef, Args) of
|
{ok, {Args, fate}};
|
||||||
{ok, Converted} -> gmb_fate_abi:create_calldata(Fun, Converted);
|
convert_args(AACI, Fun, Args) ->
|
||||||
Errors -> Errors
|
case aaci_lookup_spec(AACI, Fun) of
|
||||||
end;
|
{ok, {ArgTypes, ReturnType}} ->
|
||||||
encode_call_data2(ArgDef, Fun, {erlang, Args}) ->
|
convert_args2(ArgTypes, Args, ReturnType);
|
||||||
case hz_aaci:erlang_args_to_fate(ArgDef, Args) of
|
{error, Reason} ->
|
||||||
{ok, Coerced} -> gmb_fate_abi:create_calldata(Fun, Coerced);
|
{error, Reason}
|
||||||
Errors -> Errors
|
end.
|
||||||
end;
|
|
||||||
encode_call_data2(_, Fun, {fate, Args}) ->
|
|
||||||
% TODO: This should probably be moved back closer to the initiating call.
|
|
||||||
% 2026-02-13: Craig
|
|
||||||
gmb_fate_abi:create_calldata(Fun, Args);
|
|
||||||
encode_call_data2(ArgDef, Fun, Args) ->
|
|
||||||
encode_call_data2(ArgDef, Fun, {sophia, Args}).
|
|
||||||
|
|
||||||
convert(Defs, Args) -> convert(Defs, Args, 1, [], []).
|
convert_args2(ArgTypes, {erlang, Args}, ReturnType) ->
|
||||||
|
case hz_aaci:erlang_args_to_fate(ArgTypes, Args) of
|
||||||
|
{ok, Converted} -> {ok, {Converted, {erlang, ReturnType}}};
|
||||||
|
{error, Reason} -> {error, Reason}
|
||||||
|
end;
|
||||||
|
convert_args2(ArgTypes, {sophia, Args}, ReturnType) ->
|
||||||
|
case sophia_args_to_fate(ArgTypes, Args) of
|
||||||
|
{ok, Converted} -> {ok, {Converted, {sophia, ReturnType}}};
|
||||||
|
{error, Reason} -> {error, Reason}
|
||||||
|
end;
|
||||||
|
convert_args2(ArgTypes, Args, ReturnType) ->
|
||||||
|
convert_args2(ArgTypes, {sophia, Args}, ReturnType).
|
||||||
|
|
||||||
convert([{Name, Def} | Defs], [Arg | Args], Nth, Terms, Errors) ->
|
sophia_args_to_fate(Defs, Args) -> sophia_args_to_fate(Defs, Args, 1, [], []).
|
||||||
|
|
||||||
|
sophia_args_to_fate([{Name, Def} | Defs], [Arg | Args], Nth, Terms, Errors) ->
|
||||||
case hz_sophia:parse_literal(Def, Arg) of
|
case hz_sophia:parse_literal(Def, Arg) of
|
||||||
{ok, Term} -> convert(Defs, Args, Nth + 1, [Term | Terms], Errors);
|
{ok, Term} -> sophia_args_to_fate(Defs, Args, Nth + 1, [Term | Terms], Errors);
|
||||||
{error, Reason} -> convert(Defs, Args, Nth + 1, Terms, [{Nth, Name, Reason} | Errors])
|
{error, Reason} -> sophia_args_to_fate(Defs, Args, Nth + 1, Terms, [{Nth, Name, Reason} | Errors])
|
||||||
end;
|
end;
|
||||||
convert([], [], _, Terms, []) ->
|
sophia_args_to_fate([], [], _, Terms, []) ->
|
||||||
{ok, lists:reverse(Terms)};
|
{ok, lists:reverse(Terms)};
|
||||||
convert([], [], _, _, Errors) ->
|
sophia_args_to_fate([], [], _, _, Errors) ->
|
||||||
{error, Errors}.
|
{error, Errors}.
|
||||||
|
|
||||||
-spec sign_tx(Unsigned, SecKey) -> Result
|
-spec sign_tx(Unsigned, SecKey) -> Result
|
||||||
|
|||||||
Reference in New Issue
Block a user