From 078de48344031e09c47ab5ccaa4fa13d49afc9d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rold=C3=A1n?= <62166813+Odraxs@users.noreply.github.com> Date: Tue, 9 May 2023 09:30:39 -0500 Subject: [PATCH 1/3] Invoke Contract Function (#39) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Invoke Contract Function * Add default `function_args` in contract * Add requested changes * Fix modules name and move contract to lib --------- Co-authored-by: CristhianRodriguezMolina Co-authored-by: Felipe Guzmán Sierra --- config/dev.exs | 2 + lib/contract.ex | 17 ++ lib/contract/invoke_host_function.ex | 173 +++++++++++++ mix.exs | 2 +- mix.lock | 2 +- test/contract/invoke_host_function_test.exs | 257 ++++++++++++++++++++ test/contract_test.exs | 136 +++++++++++ 7 files changed, 587 insertions(+), 2 deletions(-) create mode 100644 lib/contract.ex create mode 100644 lib/contract/invoke_host_function.ex create mode 100644 test/contract/invoke_host_function_test.exs create mode 100644 test/contract_test.exs diff --git a/config/dev.exs b/config/dev.exs index becde76..bc96fe2 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -1 +1,3 @@ import Config + +config :stellar_sdk, network: :future diff --git a/lib/contract.ex b/lib/contract.ex new file mode 100644 index 0000000..ec0f1ff --- /dev/null +++ b/lib/contract.ex @@ -0,0 +1,17 @@ +defmodule Soroban.Contract do + @moduledoc """ + Exposes the function to invoke Soroban smart contracts + """ + + alias Soroban.Contract.InvokeHostFunction + + defdelegate invoke( + contract_id, + source_secret_key, + function_name, + function_args \\ [], + auth_accounts \\ [] + ), + to: InvokeHostFunction, + as: :invoke +end diff --git a/lib/contract/invoke_host_function.ex b/lib/contract/invoke_host_function.ex new file mode 100644 index 0000000..04dcdbc --- /dev/null +++ b/lib/contract/invoke_host_function.ex @@ -0,0 +1,173 @@ +defmodule Soroban.Contract.InvokeHostFunction do + @moduledoc """ + `InvokeHostFunction` implementation to invoke authorized and not authorized contract functions. + """ + + alias Soroban.RPC + alias Soroban.RPC.{SendTransactionResponse, SimulateTransactionResponse} + alias Stellar.{Horizon.Accounts, TxBuild} + + alias Stellar.TxBuild.{ + Account, + ContractAuth, + HostFunction, + InvokeHostFunction, + SCVal, + SequenceNumber, + Signature + } + + @type account :: Account.t() + @type function_args :: list(struct()) + @type auth :: String.t() | nil + @type auth_account :: String.t() | nil + @type auth_accounts :: list(binary()) + @type invoke_host_function :: InvokeHostFunction.t() + @type function_name :: String.t() + @type contract_id :: binary() + @type source_secret_key :: binary() + @type simulate_response :: {:ok, SimulateTransactionResponse.t()} + @type send_response :: {:ok, SendTransactionResponse.t()} + @type signature :: Signature.t() + @type sequence_number :: SequenceNumber.t() + @type sc_val_list :: list(SCVal.t()) + + @spec invoke( + contract_id :: contract_id(), + source_secret_key :: source_secret_key(), + function_name :: function_name(), + function_args :: function_args(), + auth_accounts :: auth_accounts() + ) :: send_response() + def invoke( + contract_id, + source_secret_key, + function_name, + function_args, + auth_accounts \\ [] + ) do + with {public_key, _secret} = keypair <- Stellar.KeyPair.from_secret_seed(source_secret_key), + {:ok, seq_num} <- Accounts.fetch_next_sequence_number(public_key), + {:ok, function_args} <- convert_to_sc_val(function_args), + signature <- Signature.new(keypair), + source_account <- Account.new(public_key), + sequence_number <- SequenceNumber.new(seq_num), + auth_account <- Enum.at(auth_accounts, 0) do + invoke_host_function_op = create_host_function_op(contract_id, function_name, function_args) + + invoke_host_function_op + |> simulate(source_account, sequence_number) + |> send_transaction( + source_account, + sequence_number, + signature, + auth_account, + invoke_host_function_op + ) + end + end + + @spec simulate( + invoke_host_function_op :: invoke_host_function(), + source_account :: account(), + sequence_number :: sequence_number() + ) :: simulate_response() + defp simulate( + invoke_host_function_op, + source_account, + sequence_number + ) do + {:ok, envelop_xdr} = + source_account + |> TxBuild.new(sequence_number: sequence_number) + |> TxBuild.add_operation(invoke_host_function_op) + |> TxBuild.envelope() + + RPC.simulate_transaction(envelop_xdr) + end + + @spec send_transaction( + simulate_response :: simulate_response(), + source_account :: account(), + sequence_number :: sequence_number(), + signature :: signature(), + auth_account :: auth_account(), + invoke_host_function_op :: invoke_host_function() + ) :: send_response() | simulate_response() + defp send_transaction( + {:ok, %SimulateTransactionResponse{results: [%{footprint: footprint, auth: auth}]}}, + source_account, + sequence_number, + signature, + auth_account, + invoke_host_function_op + ) do + invoke_host_function_op = + set_invoke_host_function_params(invoke_host_function_op, footprint, auth, auth_account) + + {:ok, envelope_xdr} = + source_account + |> TxBuild.new(sequence_number: sequence_number) + |> TxBuild.add_operation(invoke_host_function_op) + |> TxBuild.sign(signature) + |> TxBuild.envelope() + + RPC.send_transaction(envelope_xdr) + end + + defp send_transaction( + {:ok, %SimulateTransactionResponse{}} = response, + _source_account, + _sequence_number, + _signature, + _auth_account, + _invoke_host_function_op + ), + do: response + + @spec create_host_function_op( + contract_id :: contract_id(), + function_name :: function_name(), + function_args :: function_args() + ) :: invoke_host_function() + defp create_host_function_op(contract_id, function_name, function_args) do + function = + HostFunction.new( + type: :invoke, + contract_id: contract_id, + function_name: function_name, + args: function_args + ) + + InvokeHostFunction.new(function: function) + end + + @spec set_invoke_host_function_params( + invoke_host_function :: invoke_host_function(), + footprint :: String.t(), + auth :: auth(), + auth_account :: auth_account() + ) :: invoke_host_function() | {:error, :required_auth} + defp set_invoke_host_function_params(invoke_host_function_op, footprint, [auth], nil) do + invoke_host_function_op + |> InvokeHostFunction.set_footprint(footprint) + |> InvokeHostFunction.set_contract_auth(auth) + end + + defp set_invoke_host_function_params(invoke_host_function_op, footprint, [auth], auth_account) do + authorization = ContractAuth.sign_xdr(auth, auth_account) + + invoke_host_function_op + |> InvokeHostFunction.set_footprint(footprint) + |> InvokeHostFunction.set_contract_auth(authorization) + end + + defp set_invoke_host_function_params(invoke_host_function_op, footprint, nil, _auth_account), + do: InvokeHostFunction.set_footprint(invoke_host_function_op, footprint) + + @spec convert_to_sc_val(function_args :: function_args()) :: {:ok, sc_val_list()} + defp convert_to_sc_val(function_args) do + sc_vals = Enum.map(function_args, fn %{__struct__: struct} = arg -> struct.to_sc_val(arg) end) + {:ok, sc_vals} + end +end diff --git a/mix.exs b/mix.exs index c1ef363..051c48a 100644 --- a/mix.exs +++ b/mix.exs @@ -44,7 +44,7 @@ defmodule Soroban.MixProject do {:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}, {:ex_doc, "~> 0.29", only: :dev, runtime: false}, {:excoveralls, "~> 0.15", only: :test}, - {:stellar_sdk, "~> 0.13.1"}, + {:stellar_sdk, "~> 0.14"}, {:hackney, "~> 1.18"} ] end diff --git a/mix.lock b/mix.lock index 0b30181..9bcc3f4 100644 --- a/mix.lock +++ b/mix.lock @@ -24,6 +24,6 @@ "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "stellar_base": {:hex, :stellar_base, "0.10.2", "a9b8384cf7e6ecc77568dc6329f48075e7ed2d387e3fcbb441e2c932453c1565", [:mix], [{:crc, "~> 0.10.0", [hex: :crc, repo: "hexpm", optional: false]}, {:elixir_xdr, "~> 0.3.0", [hex: :elixir_xdr, repo: "hexpm", optional: false]}], "hexpm", "be93529024dc9e4375350f6c63457dd86bd3d9b6e2126987c56714ff75802fc4"}, - "stellar_sdk": {:hex, :stellar_sdk, "0.13.1", "c0c3dc8e28a31287cf7d68efcdb933ca5dc3bb244ad7fc52d21abcd9eb7b7d68", [:mix], [{:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:stellar_base, "~> 0.10.2", [hex: :stellar_base, repo: "hexpm", optional: false]}], "hexpm", "f74f08c0fb6f23e2656610566468b40bdaed95154dd8f960f9c1732595de1dbe"}, + "stellar_sdk": {:hex, :stellar_sdk, "0.14.0", "2c2f27ab393e4e9c4233257103c4da419794f8b6a2f5d187931dfe64fdad356f", [:mix], [{:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:stellar_base, "~> 0.10.2", [hex: :stellar_base, repo: "hexpm", optional: false]}], "hexpm", "3e0c94558d206f905929735e70806c29ae1f61fc364f8c19cfa97af4932cf463"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/test/contract/invoke_host_function_test.exs b/test/contract/invoke_host_function_test.exs new file mode 100644 index 0000000..06497c7 --- /dev/null +++ b/test/contract/invoke_host_function_test.exs @@ -0,0 +1,257 @@ +defmodule Stellar.Horizon.Client.CannedAccountRequests do + @moduledoc false + + @base_url "https://horizon-testnet.stellar.org" + + def request( + :get, + @base_url <> "/accounts/GBNDWIM7DPYZJ2RLJ3IESXBIO4C2SVF6PWZXS3DLODJSBQWBMKY5U4M3", + _headers, + _body, + _opts + ) do + {:ok, 200, [], "{\"sequence\":\"1390916568875069\"}"} + end + + def request( + :get, + @base_url <> "/accounts/GDDZSR7Y6TIMSBM72WYVGUH6FB6P7MF6Y6DU7MCNAPFRXI5GCWGWWFRS", + _headers, + _body, + _opts + ) do + {:ok, 200, [], "{\"sequence\":\"1390916568875069\"}"} + end + + def request( + :get, + @base_url <> "/accounts/GASY52GNGVKEMXSGH7VSCZQKRWQMIQD77J53KHXEBAV2BODWH6FDDZ3F", + _headers, + _body, + _opts + ) do + {:ok, 200, [], "{\"sequence\":\"1390916568875069\"}"} + end +end + +defmodule Soroban.RPC.CannedInvokeHostFunctionClientImpl do + @moduledoc false + + @behaviour Soroban.RPC.Client.Spec + + @impl true + def request( + "simulateTransaction", + _url, + _headers, + %{ + transaction: + "AAAAAgAAAABaOyGfG/GU6itO0ElcKHcFqVS+fbN5bGtw0yDCwWKx2gAAAGQABPEIAAAAPgAAAAAAAAAAAAAAAQAAAAAAAAAYAAAAAAAAAAMAAAANAAAAIL5BOLMcxdDZ2RtTGT10MW0lRAZ5TsD4HT7UD03BuGpuAAAADwAAAA1mdW5jdGlvbl9uYW1lAAAAAAAADwAAAANBcmcAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + }, + _opts + ) do + send(self(), {:request, "RESPONSE"}) + + {:ok, + %{ + results: [ + %{ + auth: nil, + events: nil, + footprint: + "AAAAAgAAAAYU0EuZrCKggMgcYHtwMuiHqnrYwhksO17kfjwJ8h2l3QAAABQAAAAHCoKrtqgxTcxBJ+F9JX+3Gvlw3NtYGwCu8hzxUsbupwIAAAAA", + xdr: "AAAAEAAAAAEAAAACAAAADwAAAAVIZWxsbwAAAAAAAA8AAAAFd29ybGQAAAA=" + } + ], + cost: %{cpu_insns: "1048713", mem_bytes: "1201148"}, + latest_ledger: "475528" + }} + end + + def request( + "simulateTransaction", + _url, + _headers, + %{ + transaction: + "AAAAAgAAAADHmUf49NDJBZ/VsVNQ/ih8/7C+x4dPsE0DyxujphWNawAAAGQABPEIAAAAPgAAAAAAAAAAAAAAAQAAAAAAAAAYAAAAAAAAAAMAAAANAAAAIL5BOLMcxdDZ2RtTGT10MW0lRAZ5TsD4HT7UD03BuGpuAAAADwAAAA1mdW5jdGlvbl9uYW1lAAAAAAAADwAAAANBcmcAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + }, + _opts + ) do + send(self(), {:request, "RESPONSE"}) + + {:ok, + %{ + results: [ + %{ + auth: [ + "AAAAAQAAAAAAAAAAyU545WHCcUig2re/I2xMg5FaqNroaTV+AXQbahq8ftYAAAAAAAAABb5BOLMcxdDZ2RtTGT10MW0lRAZ5TsD4HT7UD03BuGpuAAAABHN3YXAAAAACAAAACgAAAAAAAABkAAAAAAAAAAAAAAAKAAAAAAAAEZQAAAAAAAAAAAAAAAAAAAAA" + ], + events: nil, + footprint: + "AAAAAgAAAAYU0EuZrCKggMgcYHtwMuiHqnrYwhksO17kfjwJ8h2l3QAAABQAAAAHCoKrtqgxTcxBJ+F9JX+3Gvlw3NtYGwCu8hzxUsbupwIAAAAA", + xdr: "AAAAEAAAAAEAAAACAAAADwAAAAVIZWxsbwAAAAAAAA8AAAAFd29ybGQAAAA=" + } + ], + cost: %{cpu_insns: "1048713", mem_bytes: "1201148"}, + latest_ledger: "475528" + }} + end + + def request( + "simulateTransaction", + _url, + _headers, + %{ + transaction: + "AAAAAgAAAAAljujNNVRGXkY/6yFmCo2gxEB/+nu1HuQIK6C4dj+KMQAAAGQABPEIAAAAPgAAAAAAAAAAAAAAAQAAAAAAAAAYAAAAAAAAAAMAAAANAAAAIL5BOLMcxdDZ2RtTGT10MW0lRAZ5TsD4HT7UD03BuGpuAAAADwAAAA1mdW5jdGlvbl9uYW1lAAAAAAAADwAAAANBcmcAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + }, + _opts + ) do + send(self(), {:request, "RESPONSE"}) + + {:ok, + %{ + error: "error", + cost: %{cpu_insns: "1048713", mem_bytes: "1201148"}, + latest_ledger: "475528" + }} + end + + @impl true + def request("sendTransaction", _url, _headers, _body, _opts) do + send(self(), {:request, "RESPONSE"}) + + {:ok, + %{ + status: "PENDING", + hash: "a4721e2a61e9a6b3f54030396e41c3e352101e6cd649b4453e89fb3e827744f4", + latest_ledger: "476420", + latest_ledger_close_time: "1683150612" + }} + end +end + +defmodule Soroban.Contract.InvokeHostFunctionTest do + use ExUnit.Case + + alias Soroban.Contract.InvokeHostFunction + alias Soroban.Types.Symbol + + alias Soroban.RPC.{ + CannedInvokeHostFunctionClientImpl, + SendTransactionResponse, + SimulateTransactionResponse + } + + alias Stellar.Horizon.Client.CannedAccountRequests + + setup do + Application.put_env(:stellar_sdk, :http_client, CannedAccountRequests) + Application.put_env(:soroban, :http_client_impl, CannedInvokeHostFunctionClientImpl) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client) + Application.delete_env(:soroban, :http_client_impl) + end) + + %{ + contract_id: "be4138b31cc5d0d9d91b53193d74316d254406794ec0f81d3ed40f4dc1b86a6e", + # GBNDWIM7DPYZJ2RLJ3IESXBIO4C2SVF6PWZXS3DLODJSBQWBMKY5U4M3 + source_secret: "SDRD4CSRGPWUIPRDS5O3CJBNJME5XVGWNI677MZDD4OD2ZL2R6K5IQ24", + # GDDZSR7Y6TIMSBM72WYVGUH6FB6P7MF6Y6DU7MCNAPFRXI5GCWGWWFRS + source_secret_with_auth: "SCFQVIK6JH2NNVWVZFQBA7FRKPYHIAGOMMGO32RZKTQ3QUTLU5DN67MG", + # GASY52GNGVKEMXSGH7VSCZQKRWQMIQD77J53KHXEBAV2BODWH6FDDZ3F + source_secret_with_error: "SDXKY6TSBNS7T2UJMHLIH4BWTP4EHR52HZTRNEKH33ML3ARJI2AKIPEC", + function_name: "function_name", + function_args: [Symbol.new("Arg")], + auth_accounts: ["SCAVFA3PI3MJLTQNMXOUNBSEUOSY66YMG3T2KCQKLQBENNVLVKNPV3EK"] + } + end + + test "invoke host function without authorization", %{ + contract_id: contract_id, + source_secret: source_secret, + function_name: function_name, + function_args: function_args + } do + {:ok, + %SendTransactionResponse{ + status: "PENDING", + hash: "a4721e2a61e9a6b3f54030396e41c3e352101e6cd649b4453e89fb3e827744f4", + latest_ledger: "476420", + latest_ledger_close_time: "1683150612", + error_result_xdr: nil + }} = + InvokeHostFunction.invoke( + contract_id, + source_secret, + function_name, + function_args + ) + end + + test "invoke host function with signed authorization", %{ + contract_id: contract_id, + source_secret_with_auth: source_secret_with_auth, + function_name: function_name, + function_args: function_args, + auth_accounts: auth_accounts + } do + {:ok, + %SendTransactionResponse{ + status: "PENDING", + hash: "a4721e2a61e9a6b3f54030396e41c3e352101e6cd649b4453e89fb3e827744f4", + latest_ledger: "476420", + latest_ledger_close_time: "1683150612", + error_result_xdr: nil + }} = + InvokeHostFunction.invoke( + contract_id, + source_secret_with_auth, + function_name, + function_args, + auth_accounts + ) + end + + test "invoke host function without signed authorization", %{ + contract_id: contract_id, + source_secret_with_auth: source_secret_with_auth, + function_name: function_name, + function_args: function_args + } do + {:ok, + %SendTransactionResponse{ + status: "PENDING", + hash: "a4721e2a61e9a6b3f54030396e41c3e352101e6cd649b4453e89fb3e827744f4", + latest_ledger: "476420", + latest_ledger_close_time: "1683150612", + error_result_xdr: nil + }} = + InvokeHostFunction.invoke( + contract_id, + source_secret_with_auth, + function_name, + function_args + ) + end + + test "invoke host function with simulate error", %{ + contract_id: contract_id, + source_secret_with_error: source_secret_with_error, + function_name: function_name, + function_args: function_args + } do + {:ok, + %SimulateTransactionResponse{ + error: "error" + }} = + InvokeHostFunction.invoke( + contract_id, + source_secret_with_error, + function_name, + function_args + ) + end +end diff --git a/test/contract_test.exs b/test/contract_test.exs new file mode 100644 index 0000000..a892e06 --- /dev/null +++ b/test/contract_test.exs @@ -0,0 +1,136 @@ +defmodule Stellar.Horizon.Client.CannedContractAccountRequests do + @moduledoc false + + @base_url "https://horizon-testnet.stellar.org" + + def request( + :get, + @base_url <> "/accounts/GBNDWIM7DPYZJ2RLJ3IESXBIO4C2SVF6PWZXS3DLODJSBQWBMKY5U4M3", + _headers, + _body, + _opts + ) do + {:ok, 200, [], "{\"sequence\":\"1390916568875069\"}"} + end +end + +defmodule Soroban.RPC.CannedContractClientImpl do + @moduledoc false + + @behaviour Soroban.RPC.Client.Spec + + @impl true + def request( + "simulateTransaction", + _url, + _headers, + _body, + _opts + ) do + send(self(), {:request, "RESPONSE"}) + + {:ok, + %{ + results: [ + %{ + auth: nil, + events: nil, + footprint: + "AAAAAgAAAAYU0EuZrCKggMgcYHtwMuiHqnrYwhksO17kfjwJ8h2l3QAAABQAAAAHCoKrtqgxTcxBJ+F9JX+3Gvlw3NtYGwCu8hzxUsbupwIAAAAA", + xdr: "AAAAEAAAAAEAAAACAAAADwAAAAVIZWxsbwAAAAAAAA8AAAAFd29ybGQAAAA=" + } + ], + cost: %{cpu_insns: "1048713", mem_bytes: "1201148"}, + latest_ledger: "475528" + }} + end + + @impl true + def request("sendTransaction", _url, _headers, _body, _opts) do + send(self(), {:request, "RESPONSE"}) + + {:ok, + %{ + status: "PENDING", + hash: "a4721e2a61e9a6b3f54030396e41c3e352101e6cd649b4453e89fb3e827744f4", + latest_ledger: "476420", + latest_ledger_close_time: "1683150612" + }} + end +end + +defmodule Soroban.ContractTest do + use ExUnit.Case + + alias Soroban.Contract + alias Soroban.Types.Symbol + + alias Soroban.RPC.{ + CannedContractClientImpl, + SendTransactionResponse + } + + alias Stellar.Horizon.Client.CannedContractAccountRequests + + setup do + Application.put_env(:stellar_sdk, :http_client, CannedContractAccountRequests) + Application.put_env(:soroban, :http_client_impl, CannedContractClientImpl) + + on_exit(fn -> + Application.delete_env(:stellar_sdk, :http_client) + Application.delete_env(:soroban, :http_client_impl) + end) + + %{ + contract_id: "be4138b31cc5d0d9d91b53193d74316d254406794ec0f81d3ed40f4dc1b86a6e", + # GBNDWIM7DPYZJ2RLJ3IESXBIO4C2SVF6PWZXS3DLODJSBQWBMKY5U4M3 + source_secret: "SDRD4CSRGPWUIPRDS5O3CJBNJME5XVGWNI677MZDD4OD2ZL2R6K5IQ24", + # GDDZSR7Y6TIMSBM72WYVGUH6FB6P7MF6Y6DU7MCNAPFRXI5GCWGWWFRS + function_name: "function_name", + function_args: [Symbol.new("Arg")], + auth_accounts: ["SCAVFA3PI3MJLTQNMXOUNBSEUOSY66YMG3T2KCQKLQBENNVLVKNPV3EK"] + } + end + + test "invoke/5", %{ + contract_id: contract_id, + source_secret: source_secret, + function_name: function_name + } do + {:ok, + %SendTransactionResponse{ + status: "PENDING", + hash: "a4721e2a61e9a6b3f54030396e41c3e352101e6cd649b4453e89fb3e827744f4", + latest_ledger: "476420", + latest_ledger_close_time: "1683150612", + error_result_xdr: nil + }} = + Contract.invoke( + contract_id, + source_secret, + function_name + ) + end + + test "invoke/5 aaa", %{ + contract_id: contract_id, + source_secret: source_secret, + function_name: function_name, + function_args: function_args + } do + {:ok, + %SendTransactionResponse{ + status: "PENDING", + hash: "a4721e2a61e9a6b3f54030396e41c3e352101e6cd649b4453e89fb3e827744f4", + latest_ledger: "476420", + latest_ledger_close_time: "1683150612", + error_result_xdr: nil + }} = + Contract.invoke( + contract_id, + source_secret, + function_name, + function_args + ) + end +end From c681dff6bebd205dd8e933be5306bad335a542c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rold=C3=A1n?= <62166813+Odraxs@users.noreply.github.com> Date: Tue, 9 May 2023 10:16:32 -0500 Subject: [PATCH 2/3] Update readme (#40) --- README.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/README.md b/README.md index 9f7eca2..e958d29 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,85 @@ Soroban.RPC.get_transaction(hash) ``` +## Invoke contracts functions + +### Invoke without required authorization + +```elixir +alias Soroban.Contract +alias Soroban.Types.Symbol + +Contract.invoke( + "be4138b31cc5d0d9d91b53193d74316d254406794ec0f81d3ed40f4dc1b86a6e", + "SCAVFA3PI3MJLTQNMXOUNBSEUOSY66YMG3T2KCQKLQBENNVLVKNPV3EK", + "hello", + [Symbol.new("world")] +) + +{:ok, + %Soroban.RPC.SendTransactionResponse{ + status: "PENDING", + hash: "f62cb9e20c6d297316f49dca2041be4bf1af6b069c784764e51ac008b313d716", + latest_ledger: "570194", + latest_ledger_close_time: "1683643419", + error_result_xdr: nil + }} +``` + +### Invoke with required authorization + +- When the invoker is the signer + + +```elixir +alias Soroban.Contract +alias Soroban.Types.{Address, UInt128} + +Soroban.Contract.invoke( + "be4138b31cc5d0d9d91b53193d74316d254406794ec0f81d3ed40f4dc1b86a6e", + "SCAVFA3PI3MJLTQNMXOUNBSEUOSY66YMG3T2KCQKLQBENNVLVKNPV3EK", + "inc", + [Address.new("GDEU46HFMHBHCSFA3K336I3MJSBZCWVI3LUGSNL6AF2BW2Q2XR7NNAPM"), UInt128.new(2)] +) + +{:ok, + %Soroban.RPC.SendTransactionResponse{ + status: "PENDING", + hash: "e888193b4fed9b3ca6ad2beca3c1ed5bef3e0099e558756de85d03511cbaa00b", + latest_ledger: "570253", + latest_ledger_close_time: "1683643728", + error_result_xdr: nil + }} +``` + +- When the invokers is not the signer + +```elixir +alias Soroban.Contract +alias Soroban.Types.{Address, Int128} + +Contract.invoke( + "be4138b31cc5d0d9d91b53193d74316d254406794ec0f81d3ed40f4dc1b86a6e", + "SDRD4CSRGPWUIPRDS5O3CJBNJME5XVGWNI677MZDD4OD2ZL2R6K5IQ24", + "swap", + [ + Address.new("GDEU46HFMHBHCSFA3K336I3MJSBZCWVI3LUGSNL6AF2BW2Q2XR7NNAPM"), + Int128.new(100), + Int128.new(4500) + ], + ["SCAVFA3PI3MJLTQNMXOUNBSEUOSY66YMG3T2KCQKLQBENNVLVKNPV3EK"] +) + +{:ok, + %Soroban.RPC.SendTransactionResponse{ + status: "PENDING", + hash: "da263f59a8f8b29f415e7e26758cad6e8d88caec875112641b88757ce8e01873", + latest_ledger: "570349", + latest_ledger_close_time: "1683644240", + error_result_xdr: nil + }} +``` + ## Development - Install an Elixir version `v1.14` or lower. From 37edd6b02545c867a83f645f63c821a4a3ae86c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rold=C3=A1n?= <62166813+Odraxs@users.noreply.github.com> Date: Tue, 9 May 2023 10:49:29 -0500 Subject: [PATCH 3/3] Prepare release v0.5.0 (#41) --- CHANGELOG.md | 4 ++++ README.md | 2 +- mix.exs | 38 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14f9db6..70d8841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.4.0 (09.05.2023) + +- [Invoke Contract Function](https://github.com/kommitters/soroban.ex/issues/23) + ## 0.4.0 (04.05.2023) - [Soroban RPC: Simulate, Send & Get transaction](https://github.com/kommitters/soroban.ex/issues/16) diff --git a/README.md b/README.md index e958d29..3a53adb 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Add `soroban` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:soroban, "~> 0.4.0"} + {:soroban, "~> 0.5.0"} ] end ``` diff --git a/mix.exs b/mix.exs index 051c48a..06bf67e 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule Soroban.MixProject do use Mix.Project - @version "0.4.0" + @version "0.5.0" @github_url "https://github.com/kommitters/soroban.ex" def project do @@ -81,7 +81,41 @@ defmodule Soroban.MixProject do end defp groups_for_modules do - [] + [ + Contract: [ + Soroban.Contract, + Soroban.Contract.InvokeHostFunction + ], + RPC: [ + Soroban.RPC, + Soroban.RPC.Request, + Soroban.RPC.GetTransaction, + Soroban.RPC.GetTransactionResponse, + Soroban.RPC.SendTransaction, + Soroban.RPC.SendTransactionResponse, + Soroban.RPC.SimulateTransaction, + Soroban.RPC.SimulateTransactionResponse, + Soroban.RPC.Error, + Soroban.RPC.HTTPError + ], + Types: [ + Soroban.Types.Address, + Soroban.Types.Bool, + Soroban.Types.Bytes, + Soroban.Types.Duration, + Soroban.Types.Int32, + Soroban.Types.Int64, + Soroban.Types.Int128, + Soroban.Types.Int256, + Soroban.Types.String, + Soroban.Types.Symbol, + Soroban.Types.TimePoint, + Soroban.Types.UInt32, + Soroban.Types.UInt64, + Soroban.Types.UInt128, + Soroban.Types.UInt256 + ] + ] end defp extras() do