Skip to content

Commit

Permalink
Merge pull request #286 from kommitters/v0.12
Browse files Browse the repository at this point in the history
Release v0.12.0
  • Loading branch information
EdwinGuayacan authored Apr 19, 2023
2 parents d8afcfa + 490a99e commit eb4243e
Show file tree
Hide file tree
Showing 53 changed files with 5,274 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ stellar_sdk-*.tar

# DS_Store
.DS_Store

.elixir_ls/
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## 0.12.0 (19.04.2023)
* CAP-0046 Add support for smart contracts (Soroban preview 7).

## 0.11.8 (16.01.2023)
* Update all dependencies.
* Block egress traffic in GitHub Actions.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The **Stellar SDK** is composed of two complementary components: **`TxBuild`** +
```elixir
def deps do
[
{:stellar_sdk, "~> 0.11.8"}
{:stellar_sdk, "~> 0.12.0"}
]
end
```
Expand Down
3 changes: 1 addition & 2 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use Mix.Config

config :stellar_sdk,
network: :test
config :stellar_sdk, network: :test
56 changes: 56 additions & 0 deletions lib/tx_build/address_with_nonce.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
defmodule Stellar.TxBuild.AddressWithNonce do
@moduledoc """
`AddressWithNonce` struct definition.
"""

import Stellar.TxBuild.Validations,
only: [
validate_pos_integer: 1
]

alias Stellar.TxBuild.SCAddress
alias StellarBase.XDR.{AddressWithNonce, UInt64}

@behaviour Stellar.TxBuild.XDR

defstruct [:address, :nonce]

@type validation :: {:ok, any()} | {:error, atom()}

@type t :: %__MODULE__{address: SCAddress.t(), nonce: non_neg_integer()}
@impl true
def new(args, opts \\ nil)

## change functions to keyword list
def new(args, _opts) when is_list(args) do
address = Keyword.get(args, :address)
nonce = Keyword.get(args, :nonce)

with {:ok, address} <- validate_address({:address, address}),
{:ok, nonce} <- validate_pos_integer({:nonce, nonce}) do
%__MODULE__{
address: address,
nonce: nonce
}
end
end

def new(_args, _opts), do: {:error, :invalid_address_with_nonce}

@impl true
def to_xdr(%__MODULE__{
address: address,
nonce: nonce
}) do
address = SCAddress.to_xdr(address)
nonce = UInt64.new(nonce)

AddressWithNonce.new(address, nonce)
end

def to_xdr(_error), do: {:error, :invalid_struct_address_with_nonce}

@spec validate_address(tuple()) :: validation()
defp validate_address({_field, %SCAddress{} = value}), do: {:ok, value}
defp validate_address({field, _value}), do: {:error, :"invalid_#{field}"}
end
84 changes: 84 additions & 0 deletions lib/tx_build/authorized_invocation.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
defmodule Stellar.TxBuild.AuthorizedInvocation do
@moduledoc """
`AuthorizedInvocation` struct definition.
"""
import Stellar.TxBuild.Validations,
only: [
validate_sc_vals: 1,
validate_contract_id: 1,
validate_string: 1,
is_struct?: 2
]

alias StellarBase.XDR.{AuthorizedInvocation, AuthorizedInvocationList, Hash, SCSymbol, SCVec}
alias Stellar.TxBuild.SCVal

@type error :: Keyword.t() | atom()
@type validation :: {:ok, any()} | {:error, error()}
@type authorized_invocation_list :: AuthorizedInvocationList.t()
@type t :: %__MODULE__{
contract_id: binary(),
function_name: String.t(),
args: list(SCVal.t()),
sub_invocations: list(t())
}

@behaviour Stellar.TxBuild.XDR

defstruct [:contract_id, :function_name, :args, :sub_invocations]

@impl true
def new(args, opts \\ nil)

def new(args, _opts) when is_list(args) do
contract_id = Keyword.get(args, :contract_id)
function_name = Keyword.get(args, :function_name)
sub_invocations = Keyword.get(args, :sub_invocations)
args = Keyword.get(args, :args)

with {:ok, contract_id} <- validate_contract_id({:contract_id, contract_id}),
{:ok, function_name} <- validate_string({:function_name, function_name}),
{:ok, args} <- validate_sc_vals({:args, args}),
{:ok, sub_invocations} <- validate_sub_invocations({:sub_invocations, sub_invocations}) do
%__MODULE__{
contract_id: contract_id,
function_name: function_name,
args: args,
sub_invocations: sub_invocations
}
end
end

def new(_args, _opts), do: {:error, :invalid_authorized_invocation}

@impl true
def to_xdr(%__MODULE__{
contract_id: contract_id,
function_name: function_name,
args: args,
sub_invocations: sub_invocations
}) do
contract_id =
contract_id
|> Base.decode16!(case: :lower)
|> Hash.new()

function_name = SCSymbol.new(function_name)
args = args |> Enum.map(&SCVal.to_xdr/1) |> SCVec.new()

sub_invocations = sub_invocations |> Enum.map(&to_xdr/1) |> AuthorizedInvocationList.new()

AuthorizedInvocation.new(contract_id, function_name, args, sub_invocations)
end

def to_xdr(_error), do: {:error, :invalid_struct_authorized_invocation}

@spec validate_sub_invocations(tuple :: tuple()) :: validation()
defp validate_sub_invocations({field, args}) when is_list(args) do
if Enum.all?(args, &is_struct?(&1, __MODULE__)),
do: {:ok, args},
else: {:error, :"invalid_#{field}"}
end

defp validate_sub_invocations({field, _args}), do: {:error, :"invalid_#{field}"}
end
149 changes: 149 additions & 0 deletions lib/tx_build/contract_auth.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
defmodule Stellar.TxBuild.ContractAuth do
@moduledoc """
`ContractAuth` struct definition.
"""

import Stellar.TxBuild.Validations,
only: [
validate_sc_vals: 1,
validate_optional_address_with_nonce: 1
]

alias Stellar.TxBuild.{
AuthorizedInvocation,
OptionalAddressWithNonce,
SCObject,
SCMapEntry,
HashIDPreimage
}

alias Stellar.{KeyPair, Network}
alias StellarBase.XDR.{ContractAuth, SCVec}
alias StellarBase.XDR.HashIDPreimage, as: HashIDPreimageXDR

alias Stellar.TxBuild.{
AddressWithNonce,
SCVal,
HashIDPreimageContractAuth
}

@type error :: Keyword.t() | atom()
@type validation :: {:ok, any()} | {:error, error()}

@type t :: %__MODULE__{
address_with_nonce: OptionalAddressWithNonce.t(),
authorized_invocation: AuthorizedInvocation.t(),
signature_args: list(SCVal.t())
}

@behaviour Stellar.TxBuild.XDR

defstruct [:address_with_nonce, :authorized_invocation, :signature_args]

@impl true
def new(args, opts \\ nil)

def new(args, _opts) when is_list(args) do
address_with_nonce = Keyword.get(args, :address_with_nonce)
authorized_invocation = Keyword.get(args, :authorized_invocation)
signature_args = Keyword.get(args, :signature_args, [])

with {:ok, opt_address_with_nonce} <-
validate_optional_address_with_nonce({:address_with_nonce, address_with_nonce}),
{:ok, authorized_invocation} <-
validate_authorized_invocation({:authorized_invocation, authorized_invocation}),
{:ok, signature_args} <-
validate_sc_vals({:signature_args, signature_args}) do
%__MODULE__{
address_with_nonce: opt_address_with_nonce,
authorized_invocation: authorized_invocation,
signature_args: signature_args
}
end
end

def new(_args, _opts), do: {:error, :invalid_contract_auth}

@impl true
def to_xdr(%__MODULE__{
address_with_nonce: address_with_nonce,
authorized_invocation: authorized_invocation,
signature_args: signature_args
}) do
address_with_nonce = OptionalAddressWithNonce.to_xdr(address_with_nonce)

authorized_invocation = AuthorizedInvocation.to_xdr(authorized_invocation)

signature_args =
[vec: signature_args]
|> SCObject.new()
|> SCVal.new()
|> SCVal.to_xdr()
|> (&SCVec.new([&1])).()

ContractAuth.new(address_with_nonce, authorized_invocation, signature_args)
end

def to_xdr(_struct), do: {:error, :invalid_struct_contract_auth}

@spec sign(contract_auth :: t(), secret_key :: binary) :: t()
def sign(
%__MODULE__{
address_with_nonce: %OptionalAddressWithNonce{
address_with_nonce: %AddressWithNonce{nonce: nonce}
},
authorized_invocation: authorized_invocation,
signature_args: signature_args
} = contract_auth,
secret_key
)
when is_binary(secret_key) do
{public_key, _secret_key} = KeyPair.from_secret_seed(secret_key)
raw_public_key = KeyPair.raw_public_key(public_key)
network_id = network_id_xdr()

signature =
[
network_id: network_id,
nonce: nonce,
invocation: authorized_invocation
]
|> HashIDPreimageContractAuth.new()
|> (&HashIDPreimage.new(contract_auth: &1)).()
|> HashIDPreimage.to_xdr()
|> HashIDPreimageXDR.encode_xdr!()
|> hash()
|> KeyPair.sign(secret_key)

public_key_map_entry =
SCMapEntry.new(
SCVal.new(symbol: "public_key"),
SCVal.new(object: SCObject.new(bytes: raw_public_key))
)

signature_map_entry =
SCMapEntry.new(
SCVal.new(symbol: "signature"),
SCVal.new(object: SCObject.new(bytes: signature))
)

signature_sc_val =
SCVal.new(object: SCObject.new(map: [public_key_map_entry, signature_map_entry]))

%{contract_auth | signature_args: signature_args ++ [signature_sc_val]}
end

def sign(_args, _val), do: {:error, :invalid_secret_key}

@spec network_id_xdr :: binary()
defp network_id_xdr, do: hash(Network.passphrase())

@spec hash(data :: binary()) :: binary()
defp hash(data), do: :crypto.hash(:sha256, data)

@spec validate_authorized_invocation(tuple :: tuple()) :: validation()
defp validate_authorized_invocation({_field, %AuthorizedInvocation{} = value}),
do: {:ok, value}

defp validate_authorized_invocation({field, _}), do: {:error, :"invalid_#{field}"}
end
59 changes: 59 additions & 0 deletions lib/tx_build/ed25519_contract_id.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule Stellar.TxBuild.Ed25519ContractID do
@moduledoc """
`Ed25519ContractID` struct definition.
"""

import Stellar.TxBuild.Validations,
only: [
validate_pos_integer: 1,
validate_string: 1
]

alias StellarBase.XDR.{Ed25519ContractID, Hash, UInt256}

@type t :: %__MODULE__{
network_id: binary(),
ed25519: non_neg_integer(),
salt: non_neg_integer()
}

@behaviour Stellar.TxBuild.XDR

defstruct [:network_id, :ed25519, :salt]

@impl true
def new(args, opts \\ nil)

def new(args, _opts) when is_list(args) do
network_id = Keyword.get(args, :network_id)
ed25519 = Keyword.get(args, :ed25519)
salt = Keyword.get(args, :salt)

with {:ok, network_id} <- validate_string({:network_id, network_id}),
{:ok, ed25519} <- validate_pos_integer({:ed25519, ed25519}),
{:ok, salt} <- validate_pos_integer({:salt, salt}) do
%__MODULE__{
network_id: network_id,
ed25519: ed25519,
salt: salt
}
end
end

def new(_args, _opts), do: {:error, :invalid_ed25519_contract_id}

@impl true
def to_xdr(%__MODULE__{
network_id: network_id,
ed25519: ed25519,
salt: salt
}) do
network_id = Hash.new(network_id)
ed25519 = UInt256.new(ed25519)
salt = UInt256.new(salt)

Ed25519ContractID.new(network_id, ed25519, salt)
end

def to_xdr(_error), do: {:error, :invalid_struct_ed25519_contract_id}
end
Loading

0 comments on commit eb4243e

Please sign in to comment.