Skip to content

Commit

Permalink
auto use collateral if not provided
Browse files Browse the repository at this point in the history
  • Loading branch information
piyushthapa committed Feb 21, 2025
1 parent 0b4eaab commit a30880a
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 41 deletions.
1 change: 1 addition & 0 deletions lib/sutra/cardano/address.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ defmodule Sutra.Cardano.Address do
@type address_type :: :shelley | :reward | :byron
@type stake_credential :: Credential.t() | Pointer.t() | nil
@type network :: :mainnet | :testnet
@type bech_32() :: binary()

typedstruct module: Credential do
@moduledoc """
Expand Down
7 changes: 7 additions & 0 deletions lib/sutra/cardano/asset.ex
Original file line number Diff line number Diff line change
Expand Up @@ -469,4 +469,11 @@ defmodule Sutra.Cardano.Asset do
Map.put(assets, "lovelace", lovelace)
end
end

def from_seperator(assets, seperator \\ ".") when is_map(assets) do
Enum.reduce(assets, %{}, fn {k, v}, acc ->
[policy_id, asset_name] = String.split(k, seperator)
add(acc, policy_id, asset_name, v)
end)
end
end
2 changes: 1 addition & 1 deletion lib/sutra/cardano/transaction/tx_body.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ defmodule Sutra.Cardano.Transaction.TxBody do
# -- (15)
field(:network_id, :string)
# -- (16)
field(:collateral_return, :integer)
field(:collateral_return, Output.t())
# -- (17)
field(:total_collateral, :integer)
# -- (18)
Expand Down
9 changes: 3 additions & 6 deletions lib/sutra/cardano/transaction/tx_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ defmodule Sutra.Cardano.Transaction.TxBuilder do
native: %{},
plutus_v1: %{},
plutus_v2: %{},
plutus_v3: %{}
plutus_v3: %{},
in_ref_script: %{}
}

@type t() :: %__MODULE__{
Expand Down Expand Up @@ -256,7 +257,6 @@ defmodule Sutra.Cardano.Transaction.TxBuilder do
# FIXME: Maybe wallet_utxos is not needed in config and can be fetched from here
with {:ok, %Transaction{} = tx} <-
Internal.finalize_tx(final_builder, final_cfg.wallet_utxos, collateral_ref) do
IO.inspect(tx)
tx
end
end
Expand Down Expand Up @@ -289,10 +289,7 @@ defmodule Sutra.Cardano.Transaction.TxBuilder do
end

def submit_tx(%Transaction{} = signed_tx, provider) do
cbor = signed_tx |> Transaction.to_cbor() |> CBOR.encode()
IO.inspect(Base.encode16(cbor))

provider.__struct__.submit(provider, cbor)
provider.submit_tx(signed_tx)
end

def get_change_address([%Address{} = addr | _], nil), do: addr
Expand Down
140 changes: 118 additions & 22 deletions lib/sutra/cardano/transaction/tx_builder/internal.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.Internal do
Internal Helper function for Transaction builder
"""

alias Credo.CLI.Command.Categories.Output
alias Credo.CLI.Command.Categories.Output
alias Sutra.Blake2b
alias Sutra.Cardano.Address
alias Sutra.Cardano.Asset
Expand All @@ -12,7 +14,6 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.Internal do
alias Sutra.CoinSelection
alias Sutra.CoinSelection.LargestFirst
alias Sutra.Common.ExecutionUnitPrice
alias Sutra.Data
alias Sutra.Data.Plutus.PList
alias Sutra.ProtocolParams
alias Sutra.SlotConfig
Expand All @@ -30,12 +31,12 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.Internal do
def extract_ref(%OutputReference{transaction_id: tx_id, output_index: indx}),
do: "#{tx_id}##{indx}"

## TODO: Handle collateral better way
def finalize_tx(%TxBuilder{} = builder, wallet_utxos, collateral_ref) do
# TODO: use better way to handle colateral
{new_wallet_inputs, collateral_input} =
Utils.without_elem(wallet_utxos, fn i ->
i.output_reference == collateral_ref
maybe(collateral_ref, {wallet_utxos, nil}, fn _ ->
Utils.without_elem(wallet_utxos, fn i ->
i.output_reference == collateral_ref
end)
end)

builder = %TxBuilder{
Expand Down Expand Up @@ -65,6 +66,87 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.Internal do
end
end

defp with_collateral(
%Transaction{
witnesses: %Witness{
redeemer: redeemer
}
} = tx,
_wallet_utxos,
_config
)
when redeemer == [] or is_nil(redeemer),
do:
{:ok,
%Transaction{
tx
| tx_body: %TxBody{tx.tx_body | collateral_return: nil, collateral: nil}
}}

defp with_collateral(
%Transaction{tx_body: %TxBody{collateral: nil}} = tx,
wallet_utxos,
%TxBuilder{config: %TxConfig{} = config}
)
when is_list(wallet_utxos) do
# TODO calculate collateral Fee using TX
collateral_fee = 5_000_000

sorted_by_val =
Enum.sort_by(wallet_utxos, fn %Input{output: %Output{value: val}} ->
Asset.lovelace_of(val)
end)

fetch_collateral_combined = fn ->
Enum.reduce_while(sorted_by_val, {[], collateral_fee}, fn %Input{output: output} = input,
{inputs, amt_left} ->
if amt_left <= 0 do
{:halt, inputs}
else
{:cont, {[input | inputs], amt_left - Asset.lovelace_of(output.value)}}
end
end)
end

set_collateral = fn inputs ->
{total_asset_used, collateral_refs} =
Enum.reduce(inputs, {Asset.zero(), []}, fn i, {used_asset, refs} ->
{Asset.merge(used_asset, i.output.value), [i.output_reference | refs]}
end)

change = Asset.add(total_asset_used, "lovelace", -collateral_fee)

collateral_retun =
if Asset.is_positive_asset(change),
do: Output.new(config.change_address, change),
else: nil

%Transaction{
tx
| tx_body: %TxBody{
tx.tx_body
| collateral: collateral_refs,
collateral_return: collateral_retun,
total_collateral: total_asset_used
}
}
end

# TODO: check collateral exceedes total count
collateral_inputs =
sorted_by_val
|> Enum.find(fn i -> Asset.lovelace_of(i.output.value) >= collateral_fee end)
|> Utils.maybe(fetch_collateral_combined, fn i -> [i] end)

if is_list(collateral_inputs) and collateral_inputs != [] do
{:ok, set_collateral.(collateral_inputs)}
else
{:error, :no_suitable_collateral}
end
end

defp with_collateral(tx, _, _), do: {:ok, tx}

defp with_script_witness(%{
native: native_scripts,
plutus_v1: plutus_v1_scripts,
Expand Down Expand Up @@ -103,7 +185,8 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.Internal do
native: native_script_lookup,
plutus_v1: plutus_v1_scripts,
plutus_v2: plutus_v2_scripts,
plutus_v3: plutus_v3_scripts
plutus_v3: plutus_v3_scripts,
in_ref_script: ref_scripts
}
} = builder
)
Expand Down Expand Up @@ -133,18 +216,20 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.Internal do
raise "No Minting policy attached for PolicyId: #{k}"
end

indexed_script = Utils.with_sorted_indexed_map(scripts_lookup)
maybe(redeemer_data, acc, fn _ ->
indexed_script = Utils.with_sorted_indexed_map(scripts_lookup)

redeemer = %Redeemer{
index: indexed_script[k].index,
# We can use void here for Native script
data: redeemer_data || Data.void(),
tag: :mint,
# Initialize Exunits with 0,0. will be replaced by exact Exunits after Evaluation
exunits: {0, 0}
}
redeemer = %Redeemer{
index: indexed_script[k].index,
# We can use void here for Native script
data: redeemer_data,
tag: :mint,
# Initialize Exunits with 0,0. will be replaced by exact Exunits after Evaluation
exunits: {0, 0}
}

[redeemer | acc]
[redeemer | acc]
end)
end)
end

Expand Down Expand Up @@ -173,8 +258,8 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.Internal do
fee: Asset.from_lovelace(100_000),
mint: builder.mints,
reference_inputs: builder.ref_inputs,
collateral: [builder.collateral_input.output_reference],
total_collateral: builder.collateral_input.output.value,
collateral: maybe(builder.collateral_input, nil, fn i -> [i.output_reference] end),
total_collateral: maybe(builder.collateral_input, nil, fn i -> i.output.value end),
# TODO: use proper Auxiliary Data format
auxiliary_data_hash: set_auxiliary_data_hash(builder.metadata),
script_data_hash: Blake2b.blake2b_256("")
Expand Down Expand Up @@ -236,10 +321,22 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.Internal do
Witness.to_cbor(tx_with_evaluated_redeemer.witnesses.redeemer) |> Map.get(5)
)

utxos_left = wallet_utxos -- new_inputs

{:ok, with_collateral_tx} =
with_collateral(tx_with_evaluated_redeemer, utxos_left, builder)

new_wallet_utxos =
maybe(with_collateral_tx.tx_body.collateral, utxos_left, fn coll_inps ->
Enum.filter(utxos_left, fn %Input{output_reference: out_ref} ->
Enum.find_value(coll_inps, &(&1 == out_ref)) |> is_nil()
end)
end)

final_tx = %Transaction{
tx_with_evaluated_redeemer
with_collateral_tx
| tx_body: %TxBody{
tx_with_evaluated_redeemer.tx_body
with_collateral_tx.tx_body
| script_data_hash: script_data_hash
}
}
Expand All @@ -250,7 +347,7 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.Internal do
{:ok, final_tx}
else
%TxBody{tx_body | inputs: new_inputs, fee: Asset.from_lovelace(ceil(tx_fee * 1.06))}
|> create_tx(builder, wallet_utxos, tx.witnesses)
|> create_tx(builder, new_wallet_utxos, tx.witnesses)
end
end
end
Expand Down Expand Up @@ -341,7 +438,6 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.Internal do

if Asset.only_positive(diff_asset) == %{} do
# current inputs is enough to cover output

{:ok,
%CoinSelection{
selected_inputs: [],
Expand Down
17 changes: 5 additions & 12 deletions lib/sutra/cardano/transaction/tx_builder/tx_config.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.TxConfig do
"""
alias Sutra.Cardano.Address
alias Sutra.ProtocolParams
alias Sutra.SlotConfig

import Sutra.Utils, only: [maybe: 2]

Expand Down Expand Up @@ -77,36 +76,30 @@ defmodule Sutra.Cardano.Transaction.TxBuilder.TxConfig do
def __init(%__MODULE__{provider: nil} = cfg), do: cfg

def __init(%__MODULE__{wallet_address: nil, protocol_params: nil} = cfg) do
provider_mod = cfg.provider.__struct__
%__MODULE__{protocol_params: provider_mod.protocol_params(cfg.provider)}
%__MODULE__{protocol_params: cfg.provider.protocol_params()}
end

def __init(%__MODULE__{provider: provider} = cfg) do
provider_mod = provider.__struct__

Enum.reduce(Map.from_struct(cfg), cfg, fn cfg_field, %__MODULE__{} = acc ->
case cfg_field do
{:wallet_address, %Address{} = wallet_addr} ->
%__MODULE__{
acc
| wallet_utxos:
maybe(cfg.wallet_utxos, fn ->
provider_mod.utxos_at(provider, [Address.to_bech32(wallet_addr)])
provider.utxos_at([wallet_addr])
end),
change_address: maybe(cfg.change_address, wallet_addr)
}

{:change_address, %Address{} = change_address} ->
%__MODULE__{acc | change_address: change_address}

{:provider, provider} when is_struct(provider) ->
{:provider, provider} when not is_nil(provider) ->
%__MODULE__{
acc
| protocol_params:
maybe(cfg.protocol_params, fn -> provider_mod.protocol_params(provider) end),
# TODO: handle custom slot config
slot_config:
maybe(SlotConfig.fetch_slot_config(provider_mod.network(provider)), nil)
| protocol_params: maybe(cfg.protocol_params, fn -> provider.protocol_params() end),
slot_config: provider.slot_config()
}

_ ->
Expand Down
6 changes: 6 additions & 0 deletions lib/sutra/cardano/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,10 @@ defmodule Sutra.Utils do
{[h | elems], v}
end
end

def merge_list([]), do: []
def merge_list([a | b]), do: a ++ merge_list(b)

def fst({a, _}), do: a
def snd({_, b}), do: b
end
Binary file modified priv/native/libsutra_uplc.so
Binary file not shown.

0 comments on commit a30880a

Please sign in to comment.