Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ You will just need to add `:numscriptex` as a dependency on your `mix.exs`, and
```elixir
def deps do
[
{:numscriptex, "~> 0.2.1"}
{:numscriptex, "~> 0.2.2"}
]
end
```
Expand Down Expand Up @@ -149,27 +149,27 @@ Where result will be something like this:
```elixir
iex> %{
...> postings: [
...> %{
...> %Numscriptex.Posting{
...> amount: 100,
...> asset: "USD/2",
...> destination: "bar",
...> source: "foo"
...> }
...> ],
...> balances: [
...> %{
...> %Numscriptex.Balance{
...> account: "foo",
...> asset: "EUR/2",
...> final_balance: 300,
...> initial_balance: 300
...> },
...> %{
...> %Numscriptex.Balance{
...> account: "foo",
...> asset: "USD/2",
...> final_balance: 400,
...> initial_balance: 500
...> },
...> %{
...> %Numscriptex.Balance{
...> account: "bar",
...> asset: "USD/2",
...> final_balance: 100,
Expand Down
36 changes: 11 additions & 25 deletions lib/numscriptex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,31 @@ defmodule Numscriptex do
"""

alias Numscriptex.AssetsManager
alias Numscriptex.Balances
alias Numscriptex.Balance
alias Numscriptex.CheckLog
alias Numscriptex.Posting
alias Numscriptex.Utilities

require AssetsManager

AssetsManager.ensure_wasm_binary_is_valid()

@type check_log() :: CheckLog.t()

@type check_result() :: %{
required(:script) => binary(),
optional(:hints) => list(check_log()),
optional(:infos) => list(check_log()),
optional(:warnings) => list(check_log())
optional(:hints) => list(CheckLog.t()),
optional(:infos) => list(CheckLog.t()),
optional(:warnings) => list(CheckLog.t())
}

@type run_result() :: %{
required(:balances) => balances(),
required(:postings) => postings(),
required(:balances) => list(Balance.t()),
required(:postings) => list(Posting.t()),
required(:accountsMeta) => map(),
required(:txMeta) => map()
}

@type balances() :: %{
required(:initial_balance) => integer(),
required(:final_balance) => integer(),
required(:asset) => binary(),
required(:account) => binary()
}

@type postings() :: %{
required(:destination) => binary(),
required(:source) => binary(),
required(:asset) => binary(),
required(:amount) => integer()
}

@type errors() :: %{
required(:reason) => list(check_log()) | any(),
required(:reason) => list(CheckLog.t()) | any(),
optional(:details) => any()
}

Expand Down Expand Up @@ -140,19 +125,20 @@ defmodule Numscriptex do
result
|> Map.put_new(:accountsMeta, %{})
|> Map.put_new(:txMeta, %{})
|> Map.update!(:postings, &Posting.from_list/1)

{:ok, standardized_result}
end

defp standardize_run_result({:error, _reason} = errors), do: errors

defp maybe_put_final_balance({:ok, %{"postings" => postings} = result}, initial_balance) do
balances = Balances.put(initial_balance, postings)
balances = Balance.put(initial_balance, postings)

normalized_result =
result
|> Map.put("balances", balances)
|> Utilities.normalize_keys(:atom)
|> Map.put(:balances, balances)

{:ok, normalized_result}
end
Expand Down
102 changes: 63 additions & 39 deletions lib/numscriptex/balances.ex → lib/numscriptex/balance.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
defmodule Numscriptex.Balances do
defmodule Numscriptex.Balance do
@moduledoc """
`Numscriptex.Balances` is responsible for building the account's final balance
after running your [Numscript](https://docs.formance.com/numscript/), so you
can see the results of all transactions.
"""

@derive JSON.Encoder
defstruct account: nil,
asset: nil,
final_balance: nil,
initial_balance: nil

@typedoc """
Type that represents `Numscriptex.Balance` struct.

## Fields
* `:account` the account name
* `:asset` the asset were the transaction was made
* `:final_balance` balance after the transactions
* `:initial_balance` balance before the transactions
"""
@type t() :: %__MODULE__{
account: bitstring(),
asset: bitstring(),
final_balance: non_neg_integer(),
initial_balance: non_neg_integer()
}

@doc """
Receives the account assets (balance field from `%Numscriptex.Run{}`), and the
postings that are generated after running the numscript transaction.
Expand All @@ -29,25 +51,25 @@ defmodule Numscriptex.Balances do
...> }
...> ]
...>
...> Numscriptex.Balances.put(account_assets, postings)
...> Numscriptex.Balance.put(account_assets, postings)
[
%{
"account" => "foo",
"asset" => "EUR/2",
"final_balance" => 300,
"initial_balance" => 300
%Numscriptex.Balance{
account: "foo",
asset: "EUR/2",
final_balance: 300,
initial_balance: 300
},
%{
"account" => "foo",
"asset" => "USD/2",
"final_balance" => 400,
"initial_balance" => 500
%Numscriptex.Balance{
account: "foo",
asset: "USD/2",
final_balance: 400,
initial_balance: 500
},
%{
"account" => "bar",
"asset" => "USD/2",
"final_balance" => 100,
"initial_balance" => 0
%Numscriptex.Balance{
account: "bar",
asset: "USD/2",
final_balance: 100,
initial_balance: 0
}
]
```
Expand All @@ -60,6 +82,9 @@ defmodule Numscriptex.Balances do
|> handle_initial_balance(account_assets)
|> handle_final_balance(postings)
|> maybe_drop_balance()
|> then(fn balances ->
Enum.map(balances, &struct(__MODULE__, &1))
end)
end

defp build_balances(account_assets, postings) do
Expand All @@ -73,10 +98,10 @@ defmodule Numscriptex.Balances do
Enum.flat_map(account_assets, fn {account, assets} ->
Enum.map(assets, fn {asset, _amount} ->
%{
"account" => account,
"asset" => asset,
"initial_balance" => 0,
"final_balance" => 0
account: account,
asset: asset,
initial_balance: 0,
final_balance: 0
}
end)
end)
Expand All @@ -86,35 +111,35 @@ defmodule Numscriptex.Balances do
Enum.flat_map(postings, fn posting ->
[
%{
"account" => posting["source"],
"asset" => posting["asset"],
"initial_balance" => 0,
"final_balance" => 0
account: posting["source"],
asset: posting["asset"],
initial_balance: 0,
final_balance: 0
},
%{
"account" => posting["destination"],
"asset" => posting["asset"],
"initial_balance" => 0,
"final_balance" => 0
account: posting["destination"],
asset: posting["asset"],
initial_balance: 0,
final_balance: 0
}
]
end)
end

defp handle_initial_balance(balances, account_assets) do
Enum.map(balances, fn balance ->
account = balance["account"]
asset = balance["asset"]
account = balance.account
asset = balance.asset

initial_balance = account_assets[account][asset] || 0

%{balance | "initial_balance" => initial_balance}
%{balance | initial_balance: initial_balance}
end)
end

defp handle_final_balance(balances, postings) do
Enum.map(balances, fn balance ->
initial_balance = balance["initial_balance"]
initial_balance = balance.initial_balance

Enum.reduce(postings, {%{}, initial_balance}, fn posting, {_map, acc} ->
final_balance = calculate_final_balance(balance, posting, acc)
Expand All @@ -123,14 +148,14 @@ defmodule Numscriptex.Balances do
end)
end)
|> Enum.map(fn {balance, final_balance} ->
%{balance | "final_balance" => final_balance}
%{balance | final_balance: final_balance}
end)
end

defp calculate_final_balance(balance, posting, initial_balance) do
same_asset? = posting["asset"] == balance["asset"]
source? = posting["source"] == balance["account"]
destination? = posting["destination"] == balance["account"]
same_asset? = posting["asset"] == balance.asset
source? = posting["source"] == balance.account
destination? = posting["destination"] == balance.account

cond do
source? and destination? and same_asset? ->
Expand All @@ -149,8 +174,7 @@ defmodule Numscriptex.Balances do

defp maybe_drop_balance(balances) do
Enum.reject(balances, fn balance ->
balance["initial_balance"] == 0 and
balance["final_balance"] == 0
balance.initial_balance == 0 and balance.final_balance == 0
end)
end
end
1 change: 0 additions & 1 deletion lib/numscriptex/check_log.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ defmodule Numscriptex.CheckLog do
"""

@derive JSON.Encoder

defstruct character: nil,
level: nil,
line: nil,
Expand Down
37 changes: 37 additions & 0 deletions lib/numscriptex/posting.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
defmodule Numscriptex.Posting do
@moduledoc """
`Numscriptex.Postings` represents a financial transaction made with [Numscript](https://docs.formance.com/numscript/)
"""

@derive JSON.Encoder
defstruct amount: nil,
asset: nil,
destination: nil,
source: nil

@typedoc """
Type that represents `Numscriptex.Posting` struct.

## Fields
* `:source` account whose the money came from
* `:asset` the asset were the transaction was made
* `:destination` account whose the money will go to
* `:amount` amount of mone transferred (in integer)
"""
@type t() :: %__MODULE__{
amount: pos_integer(),
asset: bitstring(),
destination: bitstring(),
source: bitstring()
}

@spec from_list(map()) :: list(__MODULE__.t())
def from_list(postings) do
Enum.map(postings, &from_map/1)
end

@spec from_list(map()) :: __MODULE__.t()
def from_map(map) do
struct(%__MODULE__{}, map)
end
end
1 change: 0 additions & 1 deletion lib/numscriptex/run.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ defmodule Numscriptex.Run do
"""

@derive JSON.Encoder

defstruct variables: %{},
balances: %{},
metadata: %{}
Expand Down
Loading