From 95942177ec565f39a8c55c89a34a30eeca7773a3 Mon Sep 17 00:00:00 2001 From: Awea Date: Tue, 7 May 2024 20:48:34 +0700 Subject: [PATCH] refactor(rpc): simplify send_operation --- lib/forge_operation.ex | 34 ++++---- lib/rpc.ex | 78 ++++++------------- lib/transaction.ex | 52 ------------- test/rpc_test.exs | 173 +++++++++++++++++++++++------------------ test/tezex_test.exs | 49 ------------ 5 files changed, 135 insertions(+), 251 deletions(-) delete mode 100644 lib/transaction.ex delete mode 100644 test/tezex_test.exs diff --git a/lib/forge_operation.ex b/lib/forge_operation.ex index 4a7848a..42ce11d 100644 --- a/lib/forge_operation.ex +++ b/lib/forge_operation.ex @@ -59,28 +59,20 @@ defmodule Tezex.ForgeOperation do @spec operation(map()) :: nonempty_binary() def operation(content) do - encoders = %{ - "failing_noop" => &failing_noop/1, - "activate_account" => &activate_account/1, - "reveal" => &reveal/1, - "transaction" => &transaction/1, - "origination" => &origination/1, - "delegation" => &delegation/1, - "endorsement" => &endorsement/1, - "endorsement_with_slot" => &endorsement_with_slot/1, - "register_global_constant" => ®ister_global_constant/1, - "transfer_ticket" => &transfer_ticket/1, - "smart_rollup_add_messages" => &smart_rollup_add_messages/1, - "smart_rollup_execute_outbox_message" => &smart_rollup_execute_outbox_message/1 - } - - encoder = encoders[content["kind"]] - - if is_nil(encoder) do - raise "No encoder for #{content["kind"]}" + case content["kind"] do + "failing_noop" -> failing_noop(content) + "activate_account" -> activate_account(content) + "reveal" -> reveal(content) + "transaction" -> transaction(content) + "origination" -> origination(content) + "delegation" -> delegation(content) + "endorsement" -> endorsement(content) + "endorsement_with_slot" -> endorsement_with_slot(content) + "register_global_constant" -> register_global_constant(content) + "transfer_ticket" -> transfer_ticket(content) + "smart_rollup_add_messages" -> smart_rollup_add_messages(content) + "smart_rollup_execute_outbox_message" -> smart_rollup_execute_outbox_message(content) end - - encoder.(content) end @spec operation_group(map()) :: nonempty_binary() diff --git a/lib/rpc.ex b/lib/rpc.ex index ae33137..a4a12ef 100644 --- a/lib/rpc.ex +++ b/lib/rpc.ex @@ -1,9 +1,7 @@ defmodule Tezex.Rpc do - # alias Tezex.Crypto - # alias Tezex.Crypto.Base58Check - # alias Tezex.ForgeOperation + alias Tezex.Crypto + alias Tezex.ForgeOperation alias Tezex.Rpc - alias Tezex.Transaction @type t() :: %__MODULE__{ endpoint: binary(), @@ -69,61 +67,33 @@ defmodule Tezex.Rpc do # @genesis_block_time ~U[2018-06-30 10:07:32.000Z] - def build_contract_operation( - %Rpc{} = _rpc, - public_key_hash, - counter, - contract, - amount, - fee, - storage_limit, - gas_limit, - entrypoint, - parameters, - _encoded_private_key - ) do - parameters = - cond do - not is_nil(parameters) -> %{entrypoint: entrypoint || "default", value: parameters} - not is_nil(entrypoint) -> %{entrypoint: entrypoint, value: []} - true -> nil - end - - %Transaction{ - source: public_key_hash, - destination: contract, - amount: Integer.to_string(amount), - storage_limit: Integer.to_string(storage_limit), - gas_limit: Integer.to_string(gas_limit), - counter: Integer.to_string(counter), - fee: Integer.to_string(fee), - kind: "transaction", - parameters: parameters - } - end + def send_operation(%Rpc{} = rpc, contents, wallet_address, encoded_private_key, offset \\ 0) do + {:ok, block_head} = get_block_at_offset(rpc, offset) + branch = binary_part(block_head["hash"], 0, 51) - # def send_operation(%Rpc{} = rpc, operations, encoded_private_key, offset) do - # {:ok, block_head} = get_block_at_offset(rpc, offset) - # block_hash = binary_part(block_head["hash"], 0, 51) + counter = get_next_counter_for_account(rpc, wallet_address) - # forged_operation_group = - # ForgeOperation.operation_group(%{"branch" => block_hash, "contents" => operations}) + contents = + contents + |> Enum.with_index() + |> Enum.map(fn {c, i} -> + Map.merge(c, %{"counter" => Integer.to_string(counter + i), "source" => wallet_address}) + end) - # op_signature = - # Crypto.sign_message( - # encoded_private_key, - # @operation_group_watermark <> forged_operation_group - # ) + operation = %{ + "branch" => branch, + "contents" => contents + } - # signed_op_group = forged_operation_group <> op_signature + forged_operation = ForgeOperation.operation_group(operation) - # op_pair = %{bytes: signed_op_group, signature: op_signature} + signature = Crypto.sign_operation(encoded_private_key, forged_operation) + {:ok, decoded_signature} = Crypto.decode_signature(signature) - # # applied = preapply_operation(rpc, block_hash, block_head["protocol"], operations, op_pair) - # # injected_op = inject_operation(rpc, op_pair) + decoded_signature = Base.encode16(decoded_signature, case: :lower) - # # %{results: applied[0], operation_group_id: injected_op = inject_operation(rpc, op_pair)} - # end + inject_operation(rpc, forged_operation, decoded_signature) + end def get_counter_for_account(%Rpc{} = rpc, address) do with {:ok, n} <- get(rpc, "/blocks/head/context/contracts/#{address}/counter"), @@ -153,7 +123,7 @@ defmodule Tezex.Rpc do post(rpc, "/injection/operation", forged_operation <> signature) end - def get(%Rpc{} = rpc, path) do + defp get(%Rpc{} = rpc, path) do url = URI.parse(rpc.endpoint) |> URI.append_path("/chains/#{rpc.chain_id}") @@ -168,7 +138,7 @@ defmodule Tezex.Rpc do end end - def post(%Rpc{} = rpc, path, body) do + defp post(%Rpc{} = rpc, path, body) do url = URI.parse(rpc.endpoint) # 🚧 Try to reproduce the same API as the rest of this file but it doesn't make sense for the injection endpoint since it doesn't include the /chains/id diff --git a/lib/transaction.ex b/lib/transaction.ex deleted file mode 100644 index 2decf08..0000000 --- a/lib/transaction.ex +++ /dev/null @@ -1,52 +0,0 @@ -defmodule Tezex.Transaction do - alias Tezex.Transaction - - @type t() :: %__MODULE__{ - source: binary() | nil, - destination: binary(), - fee: pos_integer() | nil, - counter: integer() | nil, - gas_limit: pos_integer() | nil, - amount: pos_integer() | nil, - destination: binary(), - storage_limit: pos_integer() | nil, - parameters: map() | nil - } - - defstruct [ - :kind, - :destination, - :source, - fee: 0, - counter: 0, - gas_limit: 0, - amount: 0, - storage_limit: 0, - parameters: nil - ] - - defimpl Jason.Encoder do - def encode(%Transaction{parameters: nil} = transaction, opts) do - transaction - |> Map.from_struct() - |> Map.drop([:parameters]) - |> encode(opts) - end - - def encode(%Transaction{} = transaction, opts) do - transaction - |> Map.from_struct() - |> encode(opts) - end - - def encode(value, opts) do - value - |> Map.update!(:fee, &to_string/1) - |> Map.update!(:gas_limit, &to_string/1) - |> Map.update!(:amount, &to_string/1) - |> Map.update!(:storage_limit, &to_string/1) - |> Map.update!(:counter, &to_string/1) - |> Jason.Encode.map(opts) - end - end -end diff --git a/test/rpc_test.exs b/test/rpc_test.exs index 9cb8fa6..8d295d5 100644 --- a/test/rpc_test.exs +++ b/test/rpc_test.exs @@ -5,6 +5,9 @@ defmodule Tezex.RpcTest do doctest Tezex.Rpc @endpoint "https://ghostnet.tezos.marigold.dev/" + @ghostnet_1_address "tz1ZW1ZSN4ruXYc3nCon8EaTXp1t3tKWb9Ew" + @ghostnet_1_pkey "edsk33h91RysSBUiYyEWbeRwo41YeZrtMPTNsuZ9nzsYWwiV8CFyKi" + @ghostnet_2_address "tz1cMcDFLgFe2picQbo4DY1i6mZJiVhPCu5B" test "get_counter_for_account" do counter = @@ -16,84 +19,104 @@ defmodule Tezex.RpcTest do assert is_integer(counter) end - test "build_contract_operation" do - rpc = %Rpc{endpoint: @endpoint} - public_key_hash = "tz1b9kV41KV9N3sp69ycLdSoZ2Ak8jXwtNPv" - encoded_private_key = "edsk4TjJWEszkHKono7XMnepVqwi37FrpbVt1KCsifJeAGimxheShG" - counter = Rpc.get_next_counter_for_account(rpc, public_key_hash) + # test "build_contract_operation" do + # rpc = %Rpc{endpoint: @endpoint} + # public_key_hash = "tz1b9kV41KV9N3sp69ycLdSoZ2Ak8jXwtNPv" + # encoded_private_key = "edsk4TjJWEszkHKono7XMnepVqwi37FrpbVt1KCsifJeAGimxheShG" + # counter = Rpc.get_next_counter_for_account(rpc, public_key_hash) - contract = "KT1MFWsAXGUZ4gFkQnjByWjrrVtuQi4Tya8G" - entrypoint = "offer" - gas_limit = 1000 - storage_limit = 1000 - fee = 1000 - amount = 1 + # contract = "KT1MFWsAXGUZ4gFkQnjByWjrrVtuQi4Tya8G" + # entrypoint = "offer" + # gas_limit = 1000 + # storage_limit = 1000 + # fee = 1000 + # amount = 1 - price = 100 - fa_contract = "KT1JzqDsR2eMqhu76uRqAUEuTWj7bbwqa9wY" - token_id = "5" - royalty_address = public_key_hash - royalty_share = 1000 + # price = 100 + # fa_contract = "KT1JzqDsR2eMqhu76uRqAUEuTWj7bbwqa9wY" + # token_id = "5" + # royalty_address = public_key_hash + # royalty_share = 1000 - parameters = %{ - prim: "Pair", - args: [ - %{ - prim: "Pair", - args: [%{string: fa_contract}, %{prim: "Some", args: [%{int: token_id}]}] - }, - %{ - prim: "Pair", - args: [ - %{prim: "Right", args: [%{prim: "Right", args: [%{prim: "Unit"}]}]}, - %{ - prim: "Pair", - args: [ - %{int: Integer.to_string(price)}, - %{ - prim: "Pair", - args: [ - [ - %{ - prim: "Elt", - args: [ - %{string: royalty_address}, - %{int: Integer.to_string(royalty_share)} - ] - } - ], - %{ - prim: "Pair", - args: [ - %{prim: "None"}, - %{ - prim: "Pair", - args: [[], %{prim: "Pair", args: [%{prim: "None"}, %{prim: "None"}]}] - } - ] - } - ] - } - ] - } - ] - } - ] - } + # parameters = %{ + # prim: "Pair", + # args: [ + # %{ + # prim: "Pair", + # args: [%{string: fa_contract}, %{prim: "Some", args: [%{int: token_id}]}] + # }, + # %{ + # prim: "Pair", + # args: [ + # %{prim: "Right", args: [%{prim: "Right", args: [%{prim: "Unit"}]}]}, + # %{ + # prim: "Pair", + # args: [ + # %{int: Integer.to_string(price)}, + # %{ + # prim: "Pair", + # args: [ + # [ + # %{ + # prim: "Elt", + # args: [ + # %{string: royalty_address}, + # %{int: Integer.to_string(royalty_share)} + # ] + # } + # ], + # %{ + # prim: "Pair", + # args: [ + # %{prim: "None"}, + # %{ + # prim: "Pair", + # args: [[], %{prim: "Pair", args: [%{prim: "None"}, %{prim: "None"}]}] + # } + # ] + # } + # ] + # } + # ] + # } + # ] + # } + # ] + # } - tx = - Rpc.build_contract_operation( - rpc, - public_key_hash, - counter, - contract, - amount, - fee, - storage_limit, - gas_limit, - entrypoint, - parameters, - encoded_private_key - ) + # Rpc.build_contract_operation( + # rpc, + # public_key_hash, + # counter, + # contract, + # amount, + # fee, + # storage_limit, + # gas_limit, + # entrypoint, + # parameters, + # encoded_private_key + # ) + # end + + @tag :tezos + test "inject an operation" do + rpc = %Rpc{endpoint: @endpoint} + + contents = [ + %{ + "amount" => "100", + "destination" => @ghostnet_2_address, + "fee" => "349", + "gas_limit" => "186", + "kind" => "transaction", + "storage_limit" => "0" + } + ] + + assert {:ok, operation_id} = + Rpc.send_operation(rpc, contents, @ghostnet_1_address, @ghostnet_1_pkey) + + assert is_binary(operation_id) end end diff --git a/test/tezex_test.exs b/test/tezex_test.exs deleted file mode 100644 index a944ec4..0000000 --- a/test/tezex_test.exs +++ /dev/null @@ -1,49 +0,0 @@ -defmodule Tezex.TezexTest do - use ExUnit.Case, async: false - - alias Tezex.ForgeOperation - alias Tezex.Rpc - alias Tezex.Crypto - - doctest Tezex.Rpc - - @wallet_address "tz1ZW1ZSN4ruXYc3nCon8EaTXp1t3tKWb9Ew" - @wallet_private_key "edsk33h91RysSBUiYyEWbeRwo41YeZrtMPTNsuZ9nzsYWwiV8CFyKi" - - @endpoint "https://ghostnet.tezos.marigold.dev/" - - @tag :tezos - test "inject an operation" do - rpc = %Rpc{endpoint: @endpoint} - - {:ok, block_head} = Rpc.get_block_at_offset(rpc, 0) - branch = binary_part(block_head["hash"], 0, 51) - - counter = Rpc.get_next_counter_for_account(rpc, @wallet_address) - - operation = %{ - "branch" => branch, - "contents" => [ - %{ - "amount" => "100", - "counter" => Integer.to_string(counter), - "destination" => "tz1cMcDFLgFe2picQbo4DY1i6mZJiVhPCu5B", - "fee" => "349", - "gas_limit" => "186", - "kind" => "transaction", - "source" => @wallet_address, - "storage_limit" => "0" - } - ] - } - - forged_operation = ForgeOperation.operation_group(operation) - - signature = Crypto.sign_operation(@wallet_private_key, forged_operation) - {:ok, decoded_signature} = Crypto.decode_signature(signature) - - decoded_signature = Base.encode16(decoded_signature, case: :lower) - - assert {:ok, operation_id} = Rpc.inject_operation(rpc, forged_operation, decoded_signature) - end -end