Skip to content

Commit

Permalink
Merge pull request #42 from kommitters/v0.5
Browse files Browse the repository at this point in the history
Release v0.5.0
  • Loading branch information
Odraxs authored May 9, 2023
2 parents d211676 + 37edd6b commit 5cb5742
Show file tree
Hide file tree
Showing 9 changed files with 707 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
81 changes: 80 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
import Config

config :stellar_sdk, network: :future
17 changes: 17 additions & 0 deletions lib/contract.ex
Original file line number Diff line number Diff line change
@@ -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
173 changes: 173 additions & 0 deletions lib/contract/invoke_host_function.ex
Original file line number Diff line number Diff line change
@@ -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
40 changes: 37 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
}
Loading

0 comments on commit 5cb5742

Please sign in to comment.