diff --git a/.github/workflows/elixir.yaml b/.github/workflows/elixir.yaml index 97a514f..1e45f6d 100644 --- a/.github/workflows/elixir.yaml +++ b/.github/workflows/elixir.yaml @@ -30,7 +30,7 @@ jobs: # and running the workflow steps. matrix: otp: ["25.0.4"] # Define the OTP version [required] - elixir: ["1.15.0"] # Define the elixir version [required] + elixir: ["1.17.3"] # Define the elixir version [required] steps: # Step: Setup Elixir + Erlang image as the base. - name: Set up Elixir diff --git a/flake.nix b/flake.nix index d6364c9..6f4174a 100644 --- a/flake.nix +++ b/flake.nix @@ -5,33 +5,36 @@ flake-utils = { url = "github:numtide/flake-utils"; }; }; -outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachDefaultSystem (system: - let - inherit (pkgs.lib) optional optionals; - pkgs = import nixpkgs { inherit system; }; + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + inherit (pkgs.lib) optional optionals; + pkgs = import nixpkgs { inherit system; }; - #elixir = pkgs.beam.packages.erlang_27.elixir.override { - # version = "1.17.2"; - # rev = "47abe2d107e654ccede845356773bcf6e11ef7cb"; - # sha256 = "sha256-8rb2f4CvJzio3QgoxvCv1iz8HooXze0tWUJ4Sc13dxg="; - #}; + elixir = pkgs.beam.packages.erlang_27.elixir.override { + version = "1.17.3"; + rev = "78f63d08313677a680868685701ae79a2459dcc1"; + sha256 = "sha256-8rb2f4CvJzio3QgoxvCv1iz8HooXze0tWUJ4Sc13dxg="; + }; - in - with pkgs; - { - devShell = pkgs.mkShell { - buildInputs = [ - elixir_1_16 - elixir_ls - glibcLocales - - ] ++ optional stdenv.isLinux inotify-tools + in + with pkgs; + { + devShell = pkgs.mkShell { + buildInputs = [ + elixir + elixir_ls + glibcLocales + cargo + rustc + + ] ++ optional stdenv.isLinux inotify-tools ++ optional stdenv.isDarwin terminal-notifier ++ optionals stdenv.isDarwin (with darwin.apple_sdk.frameworks; [ CoreFoundation CoreServices ]); - }; - }); -} \ No newline at end of file + }; + }); +} + diff --git a/lib/sutra/blake_2b.ex b/lib/sutra/blake_2b.ex new file mode 100644 index 0000000..608cc77 --- /dev/null +++ b/lib/sutra/blake_2b.ex @@ -0,0 +1,24 @@ +defmodule Sutra.Blake2b do + @moduledoc """ + Helper function to calculate blake2b hash + """ + alias Blake2.Blake2b + + @type blake2b_256() :: String.t() + @type blake2b_224() :: String.t() + + @doc """ + Returns a 32-byte digest. + """ + @spec blake2b_256(binary()) :: binary() + def blake2b_256(data) do + Blake2b.hash_hex(data, "", 32) + end + + @doc """ + Returns a 28-byte digest. + """ + def blake2b_224(data) do + Blake2b.hash_hex(data, "", 28) + end +end diff --git a/lib/sutra/cardano/address.ex b/lib/sutra/cardano/address.ex index d7c23be..f71aa22 100644 --- a/lib/sutra/cardano/address.ex +++ b/lib/sutra/cardano/address.ex @@ -203,4 +203,8 @@ defmodule Sutra.Cardano.Address do fields: [payment_credential, stake_credential] } end + + def to_cbor(%Address{} = addr) do + %CBOR.Tag{tag: :bytes, value: Parser.encode(addr)} + end end diff --git a/lib/sutra/cardano/asset.ex b/lib/sutra/cardano/asset.ex index 04e3897..eed8c1b 100644 --- a/lib/sutra/cardano/asset.ex +++ b/lib/sutra/cardano/asset.ex @@ -5,6 +5,8 @@ defmodule Sutra.Cardano.Asset do alias Sutra.Data + alias Sutra.Data.Cbor + import Sutra.Data.Cbor, only: [extract_value: 1] def from_plutus(cbor) when is_binary(cbor) do @@ -48,7 +50,7 @@ defmodule Sutra.Cardano.Asset do end acc - |> Map.put(%CBOR.Tag{tag: :bytes, value: key_val}, from_asset_class(val)) + |> Map.put(Cbor.as_byte(key_val), from_asset_class(val)) end) end @@ -59,11 +61,24 @@ defmodule Sutra.Cardano.Asset do defp from_asset_class(asset_map) when is_map(asset_map) do Enum.reduce(asset_map, %{}, fn {key, val}, acc -> - key = %CBOR.Tag{tag: :bytes, value: key} - Map.put(acc, key, val) + Map.put(acc, Cbor.as_byte(key), val) end) end def lovelace_of(value) when is_integer(value), do: %{"lovelace" => value} def lovelace_of(_), do: nil + + def from_cbor(lovelace) when is_integer(lovelace), do: lovelace_of(lovelace) + + def from_cbor([lovelace, other_assets]) do + with {:ok, assets} <- from_plutus(other_assets) do + Map.put(assets, "lovelace", lovelace) + end + end + + def to_cbor(%{"lovelace" => lovelace} = asset) when map_size(asset) == 1, do: lovelace + + def to_cbor(assets) do + [Map.get(assets, "lovelace", 0), Map.delete(assets, "lovelace") |> to_plutus()] + end end diff --git a/lib/sutra/cardano/common/pool_relay.ex b/lib/sutra/cardano/common/pool_relay.ex index abe2613..6f22fc4 100644 --- a/lib/sutra/cardano/common/pool_relay.ex +++ b/lib/sutra/cardano/common/pool_relay.ex @@ -1,25 +1,78 @@ defmodule Sutra.Cardano.Common.PoolRelay do @moduledoc """ - Pool Relay Information + This module defines the relay information for a pool. + + It can be of three types: + 1. single host address, + 2. single host name + 3. multiple host names. + + ## CDDL + https://github.com/IntersectMBO/cardano-ledger/blob/master/eras/conway/impl/cddl-files/conway.cddl#L347 + ``` + relay = [single_host_addr // single_host_name // multi_host_name] + + ``` + """ use TypedStruct typedstruct(module: SingleHostAddr) do + @moduledoc """ + Single Host Address Relay + + ## CDDL + ``` + single_host_addr = (0, port / nil, ipv4 / nil, ipv6 / nil) + ``` + """ field(:port, :integer) field(:ipv4, :string) field(:ipv6, :string) end typedstruct(module: SingleHostName) do + @moduledoc """ + Single Host Name Relay + + ## CDDL + ``` + single_host_name = (1, port / nil, dns_name) + ``` + """ field(:port, :integer) field(:dns_name, :string) end typedstruct(module: MultiHostName) do + @moduledoc """ + Multiple Host Name Relay + + ## CDDL + ``` + multi_host_name = (2, dns_name) + ``` + """ + field(:dns_name, :string) end + @doc """ + Decode the relay information from the CBOR data. + + ## Examples + + iex> decode([0, "8080", "192.168.1.1", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"]) + %SingleHostAddr{} + + iex> decode([1, "8080", "example.com"]) + %SingleHostName{} + + iex> decode([2, "example.com"]) + %MultiHostName{} + + """ def decode([0, port, ipv4, ipv6]) do %SingleHostAddr{port: port, ipv4: ipv4, ipv6: ipv6} end @@ -31,4 +84,31 @@ defmodule Sutra.Cardano.Common.PoolRelay do def decode([2, dns_name]) do %MultiHostName{dns_name: dns_name} end + + @doc """ + Encode the relay information to the CBOR data. + + ## Examples + + iex> encode(%SingleHostAddr{}) + [0, port, ipv4, ipv6] + + iex> encode(%SingleHostName{}) + [1, port, dns_name] + + iex> encode(%MultiHostName{}) + [2, dns_name] + + """ + def encode(%SingleHostAddr{port: port, ipv4: ipv4, ipv6: ipv6}) do + [0, port, ipv4, ipv6] + end + + def encode(%SingleHostName{port: port, dns_name: dns_name}) do + [1, port, dns_name] + end + + def encode(%MultiHostName{dns_name: dns_name}) do + [2, dns_name] + end end diff --git a/lib/sutra/cardano/script/native_script.ex b/lib/sutra/cardano/script/native_script.ex index c63f586..1bcc899 100644 --- a/lib/sutra/cardano/script/native_script.ex +++ b/lib/sutra/cardano/script/native_script.ex @@ -62,4 +62,28 @@ defmodule Sutra.Cardano.Script.NativeScript do def from_witness_set([5, slot]) do %ScriptInvalidHereafter{slot: slot} end + + def to_witness_set(%ScriptPubkey{pubkey_hash: pubkey}) do + [0, pubkey] + end + + def to_witness_set(%ScriptAll{scripts: scripts}) do + [1, Enum.map(scripts, &to_witness_set/1)] + end + + def to_witness_set(%ScriptAny{scripts: scripts}) do + [2, Enum.map(scripts, &to_witness_set/1)] + end + + def to_witness_set(%ScriptNOfK{n: n, scripts: scripts}) do + [3, n, Enum.map(scripts, &to_witness_set/1)] + end + + def to_witness_set(%ScriptInvalidBefore{slot: slot}) do + [4, slot] + end + + def to_witness_set(%ScriptInvalidHereafter{slot: slot}) do + [5, slot] + end end diff --git a/lib/sutra/cardano/transaction.ex b/lib/sutra/cardano/transaction.ex index 13d5595..0cbec27 100644 --- a/lib/sutra/cardano/transaction.ex +++ b/lib/sutra/cardano/transaction.ex @@ -3,6 +3,7 @@ defmodule Sutra.Cardano.Transaction do Cardano Transaction """ + alias Sutra.Blake2b alias Sutra.Cardano.Transaction.TxBody alias Sutra.Cardano.Transaction.Witness alias Sutra.Data.Cbor @@ -19,6 +20,16 @@ defmodule Sutra.Cardano.Transaction do field(:metadata, any()) end + @doc """ + Generate transaction from hex encoded cbor + + iex> from_hex("valid-hex-transaction") + {:ok, %Sutra.Cardano.Transaction{}} + + iex> from_hex("some-invalid-hex-transaction") + {:error, :invalid_cbor} + + """ def from_hex(cbor) when is_binary(cbor) do case Sutra.Data.decode(cbor) do {:ok, data} -> from_cbor(data) @@ -26,11 +37,17 @@ defmodule Sutra.Cardano.Transaction do end end - # Conway era transaction - def from_cbor(%PList{value: [tx_body, witness, is_valid, metadata]}) + @doc """ + Generate transaction from cbor + + iex> from_cbor(%PList{value: [valid_tx_body, valid_witness_cbor, true, metadata]}) + %Sutra.Cardano.Transaction{} + """ + @spec from_cbor(CBOR.Tag.t()) :: __MODULE__.t() + def from_cbor(%PList{value: [tx_body, witness_cbor, is_valid, metadata]}) when is_boolean(is_valid) do witness = - Enum.reduce(Cbor.extract_value!(witness), [], fn w, acc -> + Enum.reduce(Cbor.extract_value!(witness_cbor), [], fn w, acc -> acc ++ Witness.decode(w) end) @@ -44,7 +61,35 @@ defmodule Sutra.Cardano.Transaction do def from_cbor(%PList{value: _values}) do raise """ - Only Conway era transaction supported + Only Conway era transaction supported. Todo: support other eras. """ end + + @doc """ + Convert transaction to hex encoded cbor + + iex> to_hex(%Sutra.Cardano.Transaction{}) + "some-valid-hex-encoded-cbor" + """ + @spec to_cbor(__MODULE__.t()) :: Cbor.t() + def to_cbor(%__MODULE__{} = tx) do + tx_body_cbor = TxBody.to_cbor(tx.tx_body) + + %PList{value: [tx_body_cbor, Witness.to_cbor(tx.witnesses), tx.is_valid, tx.metadata]} + end + + @doc """ + Get transaction id + + iex> tx_id(%Sutra.Cardano.Transaction{}) + "88350824a9557e16a8f18b9b3cc4ab7cc0c282c178132083babde3cdb33393ee" + + """ + @spec tx_id(__MODULE__.t()) :: Blake2b.blake2b_256() + def tx_id(%__MODULE__{} = tx) do + tx.tx_body + |> TxBody.to_cbor() + |> CBOR.encode() + |> Blake2b.blake2b_256() + end end diff --git a/lib/sutra/cardano/transaction/certificate.ex b/lib/sutra/cardano/transaction/certificate.ex index df3abca..68d5b8f 100644 --- a/lib/sutra/cardano/transaction/certificate.ex +++ b/lib/sutra/cardano/transaction/certificate.ex @@ -11,6 +11,7 @@ defmodule Sutra.Cardano.Transaction.Certificate do alias Sutra.Cardano.Transaction.Certificate.PoolRegistration alias Sutra.Cardano.Transaction.Certificate.PoolRetirement alias Sutra.Cardano.Transaction.Certificate.StakeRegistration + alias Sutra.Data.Cbor import Sutra.Data.Cbor, only: [extract_value!: 1] import Sutra.Utils, only: [maybe: 3] @@ -44,10 +45,11 @@ defmodule Sutra.Cardano.Transaction.Certificate do field(:vrf_key_hash, :string, enforce: true) field(:pledge, :integer, enforce: true) field(:cost, :integer, enforce: true) - field(:margin, :integer, enforce: true) + field(:margin, :float, enforce: true) + field(:margin_ratio, {pos_integer(), pos_integer()}, enforce: true) field(:reward_account, :string, enforce: true) field(:owners, [:string], enforce: true) - field(:relays, [PoolRelay.t()], enforce: true) + field(:relays, [__MODULE__.PoolRelay.t()], enforce: true) field(:metadata, %{url: :string, hash: :string}) end @@ -140,17 +142,20 @@ defmodule Sutra.Cardano.Transaction.Certificate do field(:drep_value, :string) end + @doc """ + decode CBOR data to Certificate + """ def decode([0, stake_credential]) do - %StakeRegistration{stake_credential: parse_credential(stake_credential)} + %StakeRegistration{stake_credential: decode_credential(stake_credential)} end def decode([1, stake_credential]) do - %StakeDeRegistration{stake_credential: parse_credential(stake_credential)} + %StakeDeRegistration{stake_credential: decode_credential(stake_credential)} end def decode([2, stake_cred, pool_key]) do %StakeDelegation{ - stake_credential: parse_credential(stake_cred), + stake_credential: decode_credential(stake_cred), pool_keyhash: extract_value!(pool_key) } end @@ -173,6 +178,7 @@ defmodule Sutra.Cardano.Transaction.Certificate do pledge: Asset.lovelace_of(pledge), cost: Asset.lovelace_of(cost), margin: n / d, + margin_ratio: {n, d}, reward_account: extract_value!(reward_accont), owners: Enum.map(extract_value!(owners), &extract_value!/1), relays: Enum.map(extract_value!(relays), &PoolRelay.decode/1), @@ -198,21 +204,21 @@ defmodule Sutra.Cardano.Transaction.Certificate do def decode([8, stake_credential, coin]) do %UnRegisterCert{ - stake_credential: parse_credential(stake_credential), + stake_credential: decode_credential(stake_credential), coin: Asset.lovelace_of(coin) } end def decode([9, stake_credential, drep]) do %VoteDelegCert{ - stake_credential: parse_credential(stake_credential), + stake_credential: decode_credential(stake_credential), drep: decode_drep(drep) } end def decode([10, stake_cred, pool_key_hash, drep]) do %StakeVoteDelegCert{ - stake_credential: parse_credential(stake_cred), + stake_credential: decode_credential(stake_cred), pool_keyhash: extract_value!(pool_key_hash), drep: decode_drep(drep) } @@ -220,7 +226,7 @@ defmodule Sutra.Cardano.Transaction.Certificate do def decode([11, stake_cred, pool_key_hash, coin]) do %StakeRegDelegCert{ - stake_credential: parse_credential(stake_cred), + stake_credential: decode_credential(stake_cred), pool_keyhash: extract_value!(pool_key_hash), deposit: Asset.lovelace_of(coin) } @@ -228,7 +234,7 @@ defmodule Sutra.Cardano.Transaction.Certificate do def decode([13, stake_cred, pool_key_hash, drep, coin]) do %StakeVoteRegDelegCert{ - stake_credential: parse_credential(stake_cred), + stake_credential: decode_credential(stake_cred), pool_keyhash: extract_value!(pool_key_hash), drep: decode_drep(drep), deposit: Asset.lovelace_of(coin) @@ -237,14 +243,14 @@ defmodule Sutra.Cardano.Transaction.Certificate do def decode([14, cold_cred, hot_cred]) do %AuthCommitteeHotCert{ - committee_cold_credential: parse_credential(cold_cred), - committee_hot_credential: parse_credential(hot_cred) + committee_cold_credential: decode_credential(cold_cred), + committee_hot_credential: decode_credential(hot_cred) } end def decode([12, stake_cred, drep, coin]) do %VoteRegDelegCert{ - stake_credential: parse_credential(stake_cred), + stake_credential: decode_credential(stake_cred), drep: decode_drep(drep), deposit: Asset.lovelace_of(coin) } @@ -252,7 +258,7 @@ defmodule Sutra.Cardano.Transaction.Certificate do def decode([16, drep_cred, coin, anchor]) do %RegDrepCert{ - drep_credential: parse_credential(drep_cred), + drep_credential: decode_credential(drep_cred), deposit: Asset.lovelace_of(coin), anchor: maybe(anchor, nil, fn [u, h] -> %{url: u, hash: h} end) } @@ -260,25 +266,163 @@ defmodule Sutra.Cardano.Transaction.Certificate do def decode([17, drep_cred, coin]) do %UnRegDrepCert{ - drep_credential: parse_credential(drep_cred), + drep_credential: decode_credential(drep_cred), deposit: Asset.lovelace_of(coin) } end def decode([18, drep_cred, anchor]) do %UpdateDrepCert{ - drep_credential: parse_credential(drep_cred), + drep_credential: decode_credential(drep_cred), anchor: maybe(anchor, nil, fn [u, h] -> %{url: u, hash: h} end) } end - defp parse_credential([cred_type, %CBOR.Tag{} = stake_credential]) do + defp decode_credential([cred_type, %CBOR.Tag{} = stake_credential]) do credential_type = if cred_type == 0, do: :vkey, else: :script %Credential{credential_type: credential_type, hash: extract_value!(stake_credential)} end + defp encode_credential(%Credential{} = cred) do + credential_type = if cred.credential_type == :vkey, do: 0, else: 1 + [credential_type, Cbor.as_byte(cred.hash)] + end + def decode_drep([0, v]), do: %Drep{drep_type: :vkey, drep_value: extract_value!(v)} def decode_drep([1, v]), do: %Drep{drep_type: :script, drep_value: extract_value!(v)} def decode_drep([n | _]) when is_integer(n), do: %Drep{drep_type: n} def decode_drep(_), do: nil + + @doc """ + encode Certificate to CBOR data + + """ + + def to_cbor(%StakeRegistration{} = stk_reg) do + [0, encode_credential(stk_reg.stake_credential)] + end + + def to_cbor(%StakeDeRegistration{} = stk_dereg) do + [1, encode_credential(stk_dereg.stake_credential)] + end + + def to_cbor(%StakeDelegation{} = stk_deleg) do + [2, encode_credential(stk_deleg.stake_credential), Cbor.as_byte(stk_deleg.pool_keyhash)] + end + + def to_cbor(%PoolRegistration{} = pool_reg) do + owners = + pool_reg.owners + |> Enum.map(&Cbor.as_byte/1) + |> Cbor.as_set() + + [ + 3, + Cbor.as_byte(pool_reg.pool_key_hash), + Cbor.as_byte(pool_reg.vrf_key_hash), + Asset.to_cbor(pool_reg.pledge), + Asset.to_cbor(pool_reg.cost), + Cbor.as_unit_interval(pool_reg.margin_ratio), + Cbor.as_byte(pool_reg.reward_account), + owners, + Enum.map(pool_reg.relays, &PoolRelay.encode/1), + maybe(pool_reg.metadata, nil, fn %{url: u, hash: h} -> + [u, Cbor.as_byte(h)] + end) + ] + end + + def to_cbor(%PoolRetirement{} = pool_ret) do + [4, Cbor.as_byte(pool_ret.pool_keyhash), pool_ret.epoch_no] + end + + def to_cbor(%RegisterCert{} = reg_cert) do + [7, encode_credential(reg_cert.stake_credential), Asset.to_cbor(reg_cert.coin)] + end + + def to_cbor(%UnRegisterCert{} = unreg_cert) do + [8, encode_credential(unreg_cert.stake_credential), Asset.to_cbor(unreg_cert.coin)] + end + + def to_cbor(%VoteDelegCert{} = vote_deleg_cert) do + [9, encode_credential(vote_deleg_cert.stake_credential), encode_drep(vote_deleg_cert.drep)] + end + + def to_cbor(%StakeVoteDelegCert{} = stake_vote_deleg_cert) do + [ + 10, + encode_credential(stake_vote_deleg_cert.stake_credential), + Cbor.as_byte(stake_vote_deleg_cert.pool_keyhash), + encode_drep(stake_vote_deleg_cert.drep) + ] + end + + def to_cbor(%StakeRegDelegCert{} = stake_reg_deleg_cert) do + [ + 11, + encode_credential(stake_reg_deleg_cert.stake_credential), + Cbor.as_byte(stake_reg_deleg_cert.pool_keyhash), + Asset.to_cbor(stake_reg_deleg_cert.deposit) + ] + end + + def to_cbor(%StakeVoteRegDelegCert{} = stake_vote_reg_deleg_cert) do + [ + 13, + encode_credential(stake_vote_reg_deleg_cert.stake_credential), + Cbor.as_byte(stake_vote_reg_deleg_cert.pool_keyhash), + encode_drep(stake_vote_reg_deleg_cert.drep), + Asset.to_cbor(stake_vote_reg_deleg_cert.deposit) + ] + end + + def to_cbor(%AuthCommitteeHotCert{} = auth_committee_hot_cert) do + [ + 14, + encode_credential(auth_committee_hot_cert.committee_cold_credential), + encode_credential(auth_committee_hot_cert.committee_hot_credential) + ] + end + + def to_cbor(%VoteRegDelegCert{} = vote_reg_deleg_cert) do + [ + 12, + encode_credential(vote_reg_deleg_cert.stake_credential), + encode_drep(vote_reg_deleg_cert.drep), + Asset.to_cbor(vote_reg_deleg_cert.deposit) + ] + end + + def to_cbor(%RegDrepCert{} = reg_drep_cert) do + [ + 16, + encode_credential(reg_drep_cert.drep_credential), + Asset.to_cbor(reg_drep_cert.deposit), + maybe(reg_drep_cert.anchor, nil, fn %{url: u, hash: h} -> [u, Cbor.as_byte(h)] end) + ] + end + + def to_cbor(%UnRegDrepCert{} = unreg_drep_cert) do + [ + 17, + encode_credential(unreg_drep_cert.drep_credential), + Asset.to_cbor(unreg_drep_cert.deposit) + ] + end + + def to_cbor(%UpdateDrepCert{} = update_drep_cert) do + [ + 18, + encode_credential(update_drep_cert.drep_credential), + maybe(update_drep_cert.anchor, nil, fn %{url: u, hash: h} -> [u, Cbor.as_byte(h)] end) + ] + end + + defp encode_drep(%Drep{} = drep) do + case drep do + %Drep{drep_type: :vkey, drep_value: v} -> [0, Cbor.as_byte(v)] + %Drep{drep_type: :script, drep_value: v} -> [1, Cbor.as_byte(v)] + %Drep{drep_type: n} when is_integer(n) -> [n] + end + end end diff --git a/lib/sutra/cardano/transaction/datum.ex b/lib/sutra/cardano/transaction/datum.ex index 61f09e4..a3efa73 100644 --- a/lib/sutra/cardano/transaction/datum.ex +++ b/lib/sutra/cardano/transaction/datum.ex @@ -2,8 +2,42 @@ defmodule Sutra.Cardano.Transaction.Datum do @moduledoc """ Cardano Transaction Datum """ + alias Sutra.Data.Cbor + + import Sutra.Data.Cbor, only: [extract_value!: 1] use Sutra.Data defenum(no_datum: :null, datum_hash: :string, inline_datum: :string) + + def from_cbor(cbor) do + case cbor do + datum_hash when is_binary(datum_hash) -> + %__MODULE__{kind: :datum_hash, value: datum_hash} + + [0, datum_hash] -> + %__MODULE__{kind: :datum_hash, value: datum_hash} + + [1, %CBOR.Tag{tag: 24, value: data_value}] -> + %__MODULE__{kind: :inline_datum, value: extract_value!(data_value)} + + _ -> + %__MODULE__{kind: :no_datum} + end + end + + def to_cbor(%__MODULE__{} = datum, opts \\ []) do + case datum do + %__MODULE__{kind: :datum_hash, value: datum_hash} -> + if Keyword.get(opts, :encoding) == :datum_option, + do: [0, Cbor.as_byte(datum_hash)], + else: Cbor.as_byte(datum_hash) + + %__MODULE__{kind: :inline_datum, value: data} -> + [1, %CBOR.Tag{tag: 24, value: Cbor.as_byte(data)}] + + _ -> + nil + end + end end diff --git a/lib/sutra/cardano/transaction/output.ex b/lib/sutra/cardano/transaction/output.ex index 6e66e28..c5a693c 100644 --- a/lib/sutra/cardano/transaction/output.ex +++ b/lib/sutra/cardano/transaction/output.ex @@ -6,13 +6,19 @@ defmodule Sutra.Cardano.Transaction.Output do alias Sutra.Cardano.Address alias Sutra.Cardano.Asset alias Sutra.Cardano.Transaction.Datum + alias Sutra.Data.Cbor + alias Sutra.Utils + + import Sutra.Utils, only: [maybe: 3] use Sutra.Data - defdata(name: OutputReference) do - data(:transaction_id, :string) - data(:output_index, :integer) - end + @type t() :: %__MODULE__{ + address: Address.t(), + value: Asset.t(), + datum: Datum.t(), + reference_script: String.t() | nil + } defdata do data(:address, Address) @@ -20,4 +26,64 @@ defmodule Sutra.Cardano.Transaction.Output do data(:datum, Datum) data(:reference_script, ~OPTION(:string)) end + + @doc """ + decode CBOR data to Output + + ## CDDL + """ + def from_cbor([%CBOR.Tag{value: raw_addr} | [assets | dtm_hash]]) + when is_binary(raw_addr) do + %__MODULE__{ + address: Address.Parser.decode(raw_addr), + value: Asset.from_cbor(assets), + datum: dtm_hash |> Utils.safe_head() |> Cbor.extract_value!() |> Datum.from_cbor() + } + end + + def from_cbor(%{0 => %CBOR.Tag{tag: :bytes, value: addr_value}} = ops) do + %__MODULE__{ + address: Address.Parser.decode(addr_value), + value: Asset.from_cbor(ops[1]), + datum: Datum.from_cbor(ops[2]), + reference_script: ops[3] + } + end + + @doc """ + encode Output to CBOR data + + ## CDDL + """ + # Pre babbage Era Output + def to_cbor(%__MODULE__{datum: %Datum{} = dtm, reference_script: nil} = output) + when dtm.kind != :inline_datum do + datum_cbor = if dtm.kind == :datum_hash, do: [Datum.to_cbor(dtm)], else: [] + + [ + Address.to_cbor(output.address), + Asset.to_cbor(output.value) + ] ++ datum_cbor + end + + def to_cbor(%__MODULE__{} = output) do + Enum.reduce(Map.to_list(output) |> tl(), %{}, fn current_val, acc -> + case current_val do + {_, nil} -> + acc + + {:address, addr_info} -> + Map.put(acc, 0, Address.to_cbor(addr_info)) + + {:value, asset_info} -> + Map.put(acc, 1, Asset.to_cbor(asset_info)) + + {:datum, datum_info} -> + datum_info |> Datum.to_cbor(encoding: :datum_option) |> maybe(acc, &Map.put(acc, 2, &1)) + + {:reference_script, script} -> + Map.put(acc, 3, script) + end + end) + end end diff --git a/lib/sutra/cardano/transaction/output_reference.ex b/lib/sutra/cardano/transaction/output_reference.ex new file mode 100644 index 0000000..69318aa --- /dev/null +++ b/lib/sutra/cardano/transaction/output_reference.ex @@ -0,0 +1,30 @@ +defmodule Sutra.Cardano.Transaction.OutputReference do + @moduledoc """ + Cardano Transaction Output Reference + """ + + alias Sutra.Data.Cbor + + use Sutra.Data + + @type t() :: %__MODULE__{ + transaction_id: String.t(), + output_index: integer() + } + + defdata do + data(:transaction_id, :string) + data(:output_index, :integer) + end + + def to_cbor(%__MODULE__{} = out_ref) do + [Cbor.as_byte(out_ref.transaction_id), out_ref.output_index] + end + + def from_cbor([tx_id, output_index]) do + %__MODULE__{ + transaction_id: Cbor.extract_value!(tx_id), + output_index: output_index + } + end +end diff --git a/lib/sutra/cardano/transaction/tx_body.ex b/lib/sutra/cardano/transaction/tx_body.ex index fbf823f..f963016 100644 --- a/lib/sutra/cardano/transaction/tx_body.ex +++ b/lib/sutra/cardano/transaction/tx_body.ex @@ -2,12 +2,12 @@ defmodule Sutra.Cardano.Transaction.TxBody do @moduledoc """ Cardano Transaction Body """ - alias Sutra.Cardano.Address + alias CBOR.Utils alias Sutra.Cardano.Asset alias Sutra.Cardano.Transaction.Certificate - alias Sutra.Cardano.Transaction.Datum alias Sutra.Cardano.Transaction.Output - alias Sutra.Cardano.Transaction.Output.OutputReference + alias Sutra.Cardano.Transaction.OutputReference + alias Sutra.Data.Cbor alias Sutra.Utils import Sutra.Data.Cbor, only: [extract_value!: 1] @@ -23,7 +23,7 @@ defmodule Sutra.Cardano.Transaction.TxBody do field(:fee, :integer) # --- (3) Slot Number field(:ttl, :integer) - # --- (4) Certificates -- TODO create Type + # --- (4) Certificates field(:certificates, []) # --- (5) Withdrawals field(:withdrawals, []) @@ -61,79 +61,171 @@ defmodule Sutra.Cardano.Transaction.TxBody do field(:treasury_donation, :integer) end - def decode(tx_body) when is_map(tx_body) do - network_id = if tx_body[15] == 1, do: "mainnet", else: "testnet" + defp decode_netword_id(0), do: :testnet + defp decode_netword_id(1), do: :mainnet + defp decode_netword_id(_), do: nil + defp encode_network_id(nid) do + case nid do + :testnet -> 0 + :mainnet -> 1 + _ -> nil + end + end + + def decode(tx_body) when is_map(tx_body) do certificates = tx_body[4] - |> maybe([], &extract_value!/1) - |> Enum.map(&Certificate.decode/1) + |> extract_value!() + |> maybe(nil, fn certs -> + Enum.map(certs, &Certificate.decode/1) + end) + + inputs = + tx_body[0] + |> extract_value!() + |> Enum.map(&OutputReference.from_cbor/1) %__MODULE__{ - inputs: parse_inputs(tx_body[0]), - outputs: Enum.map(tx_body[1], &parse_tx_outputs/1), + inputs: inputs, + outputs: Enum.map(tx_body[1], &Output.from_cbor/1), fee: Asset.lovelace_of(tx_body[2]), ttl: tx_body[3], certificates: certificates, + withdrawals: withdrawal_from_cbor(tx_body[5]), auxiliary_data_hash: extract_value!(tx_body[7]), validaty_interval_start: tx_body[8], mint: maybe(tx_body[9], nil, &Asset.from_plutus/1) |> Utils.ok_or(nil), + required_signers: + maybe(extract_value!(tx_body[14]), nil, fn d -> + Enum.map(d, &extract_value!/1) + end), script_data_hash: extract_value!(tx_body[11]), - collateral: parse_inputs(extract_value!(tx_body[13]) || []), - network_id: network_id, - collateral_return: maybe(tx_body[16], nil, &parse_tx_outputs/1), - total_collateral: maybe(tx_body[17], nil, &parse_value/1), - reference_inputs: parse_inputs(extract_value!(tx_body[18]) || []) + collateral: + maybe(extract_value!(tx_body[13]), nil, fn d -> + Enum.map(d, &OutputReference.from_cbor/1) + end), + network_id: decode_netword_id(extract_value!(tx_body[15])), + collateral_return: maybe(tx_body[16], nil, &Output.from_cbor/1), + total_collateral: maybe(tx_body[17], nil, &Asset.from_cbor/1), + reference_inputs: + maybe(extract_value!(tx_body[18]), nil, fn d -> + Enum.map(d, &OutputReference.from_cbor/1) + end) } end - defp parse_inputs(%CBOR.Tag{tag: 258, value: inputs}), do: parse_inputs(inputs) + defp withdrawal_from_cbor(withdrawals) when is_map(withdrawals) do + for {k, v} <- withdrawals, into: %{}, do: {extract_value!(k), Asset.from_cbor(v)} + end + + defp withdrawal_from_cbor(_), do: nil - defp parse_inputs(inputs) when is_list(inputs) do - Enum.map(inputs, fn [tx_id, index] -> - %OutputReference{ - transaction_id: extract_value!(tx_id), - output_index: index - } - end) + def to_cbor(%__MODULE__{} = tx_body) do + Map.to_list(tx_body) + |> Enum.reduce(%{}, &do_map_to_cbor/2) end - # pre babbage Transaction output - defp parse_tx_outputs([%CBOR.Tag{value: raw_addr} | [amt | dtm_hash]]) - when is_binary(raw_addr) do - %Output{ - address: Address.Parser.decode(raw_addr), - value: parse_value(amt), - datum: dtm_hash |> Utils.safe_head() |> parse_datum() - } + # Ignore nil values + defp do_map_to_cbor({_, nil}, acc), do: acc + # Ignore values with script definition + defp do_map_to_cbor({:__struct__, _}, acc), do: acc + + defp do_map_to_cbor({:inputs, inputs}, acc) do + inputs + |> Enum.map(&OutputReference.to_cbor/1) + |> Cbor.as_nonempty_set() + |> Cbor.as_indexed_map(0, acc) end - defp parse_tx_outputs(%{0 => %CBOR.Tag{tag: :bytes, value: addr_value}} = ops) do - %Output{ - address: Address.Parser.decode(addr_value), - value: parse_value(ops[1]), - datum: parse_datum(ops[2]), - reference_script: ops[3] - } + defp do_map_to_cbor({:outputs, outputs}, acc) do + outputs + |> Enum.map(&Output.to_cbor/1) + |> Cbor.as_indexed_map(1, acc) end - # datum_hash = $hash32 - defp parse_datum(datum_hash) when is_binary(datum_hash), - do: %Datum{kind: :datum_hash, value: datum_hash} + defp do_map_to_cbor({:fee, fee}, acc) do + fee + |> Asset.to_cbor() + |> Cbor.as_indexed_map(2, acc) + end - # datum_option = [0, $hash32 // 1, data] - defp parse_datum([0, datum_hash]), do: %Datum{kind: :datum_hash, value: datum_hash} + defp do_map_to_cbor({:ttl, ttl}, acc) do + Cbor.as_indexed_map(ttl, 3, acc) + end - defp parse_datum([1, %CBOR.Tag{tag: 24, value: %CBOR.Tag{tag: :bytes, value: data}}]), - do: %Datum{kind: :inline_datum, value: data} + defp do_map_to_cbor({:certificates, certs}, acc) do + certs + |> Enum.map(&Certificate.to_cbor/1) + |> Cbor.as_nonempty_set() + |> Cbor.as_indexed_map(4, acc) + end - defp parse_datum(_), do: %Datum{kind: :no_datum} + defp do_map_to_cbor({:withdrawals, withdrawals}, acc) do + withdrawal_cbor = + for {k, v} <- withdrawals, into: %{}, do: {Cbor.as_byte(k), Asset.to_cbor(v)} - defp parse_value(lovelace) when is_integer(lovelace), do: Asset.lovelace_of(lovelace) + Cbor.as_indexed_map(withdrawal_cbor, 5, acc) + end - defp parse_value([lovelace, other_assets]) do - with {:ok, assets} <- Asset.from_plutus(other_assets) do - Map.put(assets, "lovelace", lovelace) - end + defp do_map_to_cbor({:auxiliary_data_hash, aux_data_hash}, acc) do + aux_data_hash + |> Cbor.as_byte() + |> Cbor.as_indexed_map(7, acc) + end + + defp do_map_to_cbor({:validaty_interval_start, slot}, acc) do + Cbor.as_indexed_map(slot, 8, acc) + end + + defp do_map_to_cbor({:mint, mint_info}, acc) do + mint_info + |> Asset.to_plutus() + |> Cbor.as_indexed_map(9, acc) + end + + defp do_map_to_cbor({:script_data_hash, script_data_hash}, acc) do + script_data_hash + |> Cbor.as_byte() + |> Cbor.as_indexed_map(11, acc) + end + + defp do_map_to_cbor({:collateral, collateral}, acc) do + collateral + |> Enum.map(&OutputReference.to_cbor/1) + |> Cbor.as_nonempty_set() + |> Cbor.as_indexed_map(13, acc) + end + + defp do_map_to_cbor({:required_signers, required_signers}, acc) do + required_signers + |> Enum.map(&Cbor.as_byte/1) + |> Cbor.as_nonempty_set() + |> Cbor.as_indexed_map(14, acc) + end + + defp do_map_to_cbor({:network_id, network_id}, acc) do + network_id + |> encode_network_id() + |> Cbor.as_indexed_map(15, acc) + end + + defp do_map_to_cbor({:collateral_return, collateral_return}, acc) do + collateral_return + |> Output.to_cbor() + |> Cbor.as_indexed_map(16, acc) + end + + defp do_map_to_cbor({:total_collateral, total_collateral}, acc) do + total_collateral + |> Asset.to_cbor() + |> Cbor.as_indexed_map(17, acc) + end + + defp do_map_to_cbor({:reference_inputs, ref_inputs}, acc) do + ref_inputs + |> Enum.map(&OutputReference.to_cbor/1) + |> Cbor.as_nonempty_set() + |> Cbor.as_indexed_map(18, acc) end end diff --git a/lib/sutra/cardano/transaction/witness.ex b/lib/sutra/cardano/transaction/witness.ex index 6e2fbf5..297a4b7 100644 --- a/lib/sutra/cardano/transaction/witness.ex +++ b/lib/sutra/cardano/transaction/witness.ex @@ -6,10 +6,14 @@ defmodule Sutra.Cardano.Transaction.Witness do use TypedStruct alias Sutra.Cardano.Script.NativeScript + alias Sutra.Data.Cbor alias Sutra.Data.Plutus alias Sutra.Utils + alias __MODULE__.{PlutusData, Redeemer, VkeyWitness, ScriptWitness} + import Sutra.Data.Cbor, only: [extract_value!: 1] + import Sutra.Utils, only: [maybe: 3] @type t() :: __MODULE__.VkeyWitness.t() | __MODULE__.Redeemer.t() @@ -18,6 +22,10 @@ defmodule Sutra.Cardano.Transaction.Witness do field(:signature, String.t()) end + typedstruct(module: PlutusData) do + field(:value, Plutus.t()) + end + typedstruct(module: Redeemer) do @type redeemer_tag() :: :spend | :mint | :cert | :reward | :vote | :propose @@ -25,7 +33,6 @@ defmodule Sutra.Cardano.Transaction.Witness do field(:index, integer()) field(:data, Plutus.t()) field(:exunits, {integer(), integer()}) - field(:is_legacy, boolean(), default: false) def decode_tag!(0), do: :spend def decode_tag!(1), do: :mint @@ -34,6 +41,17 @@ defmodule Sutra.Cardano.Transaction.Witness do def decode_tag!(4), do: :vote def decode_tag!(5), do: :propose def decode_tag!(n), do: raise("Invalid redeemer tag: #{n}") + + def encode_tag(tag) do + case tag do + :spend -> 0 + :mint -> 1 + :cert -> 2 + :reward -> 3 + :vote -> 4 + :propose -> 5 + end + end end typedstruct(module: ScriptWitness) do @@ -50,6 +68,15 @@ defmodule Sutra.Cardano.Transaction.Witness do def decode_script_type!(3), do: :plutus_v1 def decode_script_type!(6), do: :plutus_v2 def decode_script_type!(7), do: :plutus_v3 + + def encode_script_type(script_type) do + case script_type do + :native -> 1 + :plutus_v1 -> 3 + :plutus_v2 -> 6 + :plutus_v3 -> 7 + end + end end def decode({0, %CBOR.Tag{tag: 258, value: vkey_witnesses}}), do: decode({0, vkey_witnesses}) @@ -84,8 +111,7 @@ defmodule Sutra.Cardano.Transaction.Witness do tag: __MODULE__.Redeemer.decode_tag!(tag), index: index, data: Plutus.decode(data) |> Utils.ok_or(data), - exunits: {mem, exec}, - is_legacy: true + exunits: {mem, exec} } end) end @@ -96,12 +122,15 @@ defmodule Sutra.Cardano.Transaction.Witness do tag: __MODULE__.Redeemer.decode_tag!(tag), index: index, data: Plutus.decode(data) |> Utils.ok_or(data), - exunits: {mem, exec}, - is_legacy: true + exunits: {mem, exec} } end) end + def decode({4, plutus_data}) do + [%PlutusData{value: extract_value!(plutus_data) |> Plutus.decode()}] + end + def decode({red_type, redeemer_info}) do raise """ Not Implemented Redeemer Type: \n #{inspect(red_type)} @@ -110,4 +139,93 @@ defmodule Sutra.Cardano.Transaction.Witness do """ end + + @doc """ + Encode WitnessSet to CBOR + https://github.com/IntersectMBO/cardano-ledger/blob/master/eras/conway/impl/cddl-files/conway.cddl#L489 + + + transaction_witness_set = {? 0 : nonempty_set + , ? 1 : nonempty_set + , ? 2 : nonempty_set + , ? 3 : nonempty_set + , ? 4 : nonempty_set + , ? 5 : redeemers + , ? 6 : nonempty_set + , ? 7 : nonempty_set} + + + + iex> to_cbor([vkey_witness, native_script, redeemer, plutus_data]) + %{0 => vkey_witness_cbor, 1 => script_witness_cbor, 5 => redeemer_cbor, 4 => plutus_data_cbor} + + """ + def to_cbor(tx_witness_sets) when is_list(tx_witness_sets) do + Enum.reduce(tx_witness_sets, %{}, &do_encode_witness_to_cbor/2) + end + + # Encode VkeyWitness to CBOR + # {0 : nonempty_set extract_value!() + |> Utils.maybe(val, &[&1 | [val]]) + |> Cbor.as_nonempty_set() + |> Cbor.as_indexed_map(0, acc) + end + + # Encode Native Script to CBOR + # {1 : nonempty_set} + defp do_encode_witness_to_cbor(%ScriptWitness{script_type: :native} = script, acc) do + cbor_witness = NativeScript.to_witness_set(script.data) + + Map.get(acc, 1) + |> extract_value!() + |> Utils.safe_append([cbor_witness]) + |> Cbor.as_nonempty_set() + |> Cbor.as_indexed_map(1, acc) + end + + # Encode Plutus Script to CBOR + # {3, 6, 7 : nonempty_set} + defp do_encode_witness_to_cbor(%__MODULE__.ScriptWitness{} = script_witness, acc) do + script_indx = __MODULE__.ScriptWitness.encode_script_type(script_witness.script_type) + values = Enum.map(script_witness.data, &Cbor.as_byte/1) + + acc + |> Map.get(script_indx) + |> extract_value!() + |> Utils.maybe(values, &[&1 | values]) + |> Cbor.as_nonempty_set() + |> Cbor.as_indexed_map(script_indx, acc) + end + + # Encode Redeemer to CBOR + # + # redeemer_tag = 0 / 1 / 2 / 3 / 4 / 5 + # {+ [tag : redeemer_tag , index : uint .size 4] => + # [data : plutus_data , ex_units : ex_units]} + # + defp do_encode_witness_to_cbor(%Redeemer{exunits: {mem, steps}} = redeemer, acc) do + redeemer_key = [Redeemer.encode_tag(redeemer.tag), redeemer.index] + redeemer_val = [redeemer.data, [mem, steps]] + + Map.get(acc, 5, %{}) + |> Map.put(redeemer_key, redeemer_val) + |> Cbor.as_indexed_map(5, acc) + end + + # Encode plutus Data Witness + # + # + defp do_encode_witness_to_cbor(%PlutusData{value: value}, acc) do + Map.get(acc, 4) + |> extract_value!() + |> maybe([value], &[&1 | value]) + |> Cbor.as_nonempty_set() + |> Cbor.as_indexed_map(4, acc) + end end diff --git a/lib/sutra/cardano/utils.ex b/lib/sutra/cardano/utils.ex index abe8a65..f6e7adc 100644 --- a/lib/sutra/cardano/utils.ex +++ b/lib/sutra/cardano/utils.ex @@ -17,5 +17,14 @@ defmodule Sutra.Utils do def maybe(data, _, f2), do: f2.(data) def ok_or({:ok, result}, _), do: result + def ok_or(_, default) when is_function(default, 0), do: default.() + def ok_or(result, default) when is_function(default, 1), do: default.(result) def ok_or(_, default), do: default + + def safe_append(list, val) when is_list(list) do + new_val = if is_list(val), do: val, else: [val] + list ++ new_val + end + + def safe_append(_, val), do: if(is_list(val), do: val, else: [val]) end diff --git a/lib/sutra/data/cbor.ex b/lib/sutra/data/cbor.ex index 5b45be3..202f678 100644 --- a/lib/sutra/data/cbor.ex +++ b/lib/sutra/data/cbor.ex @@ -3,15 +3,45 @@ defmodule Sutra.Data.Cbor do CBOR handling """ - def extract_value(%CBOR.Tag{tag: :bytes, value: value}), do: {:ok, Base.encode16(value)} + alias Sutra.Data.Plutus.PList + + @type t() :: %PList{} | %CBOR.Tag{} | map() + + alias Sutra.Utils + + def extract_value(%CBOR.Tag{tag: :bytes, value: value}), + do: {:ok, Base.encode16(value)} + def extract_value(%CBOR.Tag{value: value}), do: {:ok, value} def extract_value(%Sutra.Data.Plutus.PList{value: value}), do: {:ok, value} def extract_value(value), do: {:ok, value} def extract_value!(v) do - case extract_value(v) do - {:ok, value} -> value - {:error, _} -> raise "Invalid CBOR value: #{inspect(v)}" - end + extract_value(v) + |> Utils.ok_or(fn -> raise "Invalid CBOR value: #{inspect(v)}" end) + end + + def as_byte(value) when is_binary(value), + do: %CBOR.Tag{tag: :bytes, value: Base.decode16!(value, case: :mixed)} + + def as_nonempty_set(value), do: %CBOR.Tag{tag: 258, value: value} + def as_set(value), do: %CBOR.Tag{tag: 258, value: value} + + def as_indexed_map(value, index, map \\ %{}) do + Map.put(map, index, value) end + + def as_unit_interval({numerator, denomanator}) do + %CBOR.Tag{tag: 30, value: [numerator, denomanator]} + end + + def as_tagged(values) when is_list(values) do + Enum.map(values, &as_tagged/1) + end + + def as_tagged(value) when is_binary(value) do + %CBOR.Tag{tag: :bytes, value: value} + end + + def as_tagged(value), do: value end diff --git a/lib/sutra/data/plutus.ex b/lib/sutra/data/plutus.ex index 45dbf18..75ac306 100644 --- a/lib/sutra/data/plutus.ex +++ b/lib/sutra/data/plutus.ex @@ -5,34 +5,25 @@ defmodule Sutra.Data.Plutus do use TypedStruct + alias Sutra.Data.Cbor alias __MODULE__, as: Plutus + alias __MODULE__.PList @type pbytes() :: String.t() @type pInt() :: pos_integer() - @type pMap() :: [{Data.t(), Data.t()}] + @type pMap() :: [{__MODULE__.t(), __MODULE__.t()}] - @type t() :: Constr.t() | pMap() | PList.t() | pInt() | pbytes() - - typedstruct module: Constr do - @moduledoc """ - Data Constr - """ - field(:index, Integer.t(), enforce: true) - field(:fields, [Plutus.t()], enforce: true) - end + @type t() :: __MODULE__.Constr.t() | pMap() | __MODULE__.PList.t() | pInt() | pbytes() defmodule PList do - typedstruct do - @moduledoc """ - Phantom type for Plutus List as List is encoded weirdly. + @moduledoc """ + Phantom type for Plutus List as List is encoded weirdly. - For empty list, it is encoded as definite empty lists (0x80) - For non-empty list, it is encoded as indefinite lists - """ - field(:value, [Plutus.t()], enforce: true) - end + For empty list, it is encoded as definite empty lists (0x80) + For non-empty list, it is encoded as indefinite lists + """ - alias __MODULE__, as: PList + defstruct [:value] defimpl CBOR.Encoder do @impl true @@ -46,10 +37,37 @@ defmodule Sutra.Data.Plutus do end end - @spec decode(binary() | CBOR.Tag.t() | integer()) :: {:ok, CBOR.Tag.t()} | {:error, any()} - def decode(val) when is_integer(val), do: {:ok, val} - def decode(%CBOR.Tag{} = cbor_tag), do: {:ok, decode_cbor_tag(cbor_tag)} + defmodule Constr do + @moduledoc """ + Data Constr + """ + + typedstruct do + field(:index, pos_integer(), enforce: true) + field(:fields, [Plutus.t()], enforce: true) + end + + defimpl CBOR.Encoder do + @impl true + def encode_into(%Constr{index: index, fields: fields}, acc) when is_integer(index) do + tag = + if index >= 0 and index < 7, + do: index + 121, + else: 1280 + (index - 7) + + constr = %CBOR.Tag{ + tag: tag, + value: %PList{ + value: Cbor.as_tagged(fields) + } + } + + CBOR.Encoder.encode_into(constr, acc) + end + end + end + @spec decode(binary() | Cbor.t() | integer()) :: {:ok, __MODULE__.t()} | {:error, any()} def decode(raw) when is_binary(raw) do with {:ok, bytes} <- normalize_bytes(raw), {:ok, cbor_decoded, _} <- CBOR.decode(bytes) do @@ -57,7 +75,7 @@ defmodule Sutra.Data.Plutus do end end - def decode(data), do: {:error, {:invalid_data, data}} + def decode(data), do: {:ok, decode_cbor_tag(data)} defp normalize_bytes(str) when is_binary(str) do case Base.decode16(str, case: :mixed) do @@ -84,8 +102,8 @@ defmodule Sutra.Data.Plutus do defp decode_cbor_tag(%CBOR.Tag{tag: 102} = _tag), do: raise("TODO: Handle alternatives 102") - defp decode_cbor_tag(%CBOR.Tag{tag: n} = _tag) when is_integer(n), - do: raise("Invalid Tag: #{n}") + defp decode_cbor_tag(%CBOR.Tag{tag: n} = tag) when is_integer(n), + do: tag defp decode_cbor_tag(tags) when is_list(tags), do: %PList{value: Enum.map(tags, &decode_cbor_tag/1)} @@ -95,30 +113,8 @@ defmodule Sutra.Data.Plutus do def encode(data) do data - |> encode_data() + |> Cbor.as_tagged() |> CBOR.encode() |> Base.encode16() end - - defp encode_data(%Constr{index: index, fields: fields}) when index >= 0 and index < 7 do - %CBOR.Tag{tag: index + 121, value: %PList{value: Enum.map(fields, &encode_data/1)}} - end - - defp encode_data(%Constr{index: index, fields: fields}) when index >= 7 and index < 128 do - %CBOR.Tag{tag: 1280 + (index - 7), value: %PList{value: Enum.map(fields, &encode_data/1)}} - end - - defp encode_data(%PList{} = list) do - list - end - - defp encode_data(d) when is_list(d) do - Enum.map(d, &encode_data/1) - end - - defp encode_data(d) when is_binary(d) do - %CBOR.Tag{tag: :bytes, value: d} - end - - defp encode_data(d), do: d end diff --git a/lib/sutra/uplc.ex b/lib/sutra/uplc.ex new file mode 100644 index 0000000..b841955 --- /dev/null +++ b/lib/sutra/uplc.ex @@ -0,0 +1,11 @@ +defmodule Sutra.Uplc do + @moduledoc """ + Handle UPLC + """ + + use Rustler, otp_app: :sutra_offchain, crate: "sutra_uplc" + + # NIF function stub - actual implementation in Rust + def eval_phase_two(_tx_bytes, _utxos, _cost_models, _initial_budget, _slot_config), + do: :erlang.nif_error(:nif_not_loaded) +end diff --git a/mix.exs b/mix.exs index cb3ed54..18196f5 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule Sutra.MixProject do [ app: :sutra_offchain, version: "0.1.0", - elixir: "~> 1.15", + elixir: "~> 1.17.0", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, deps: deps() @@ -28,9 +28,11 @@ defmodule Sutra.MixProject do # {:dep_from_hexpm, "~> 0.3.0"}, # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} {:cbor, "~> 1.0.1"}, - {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, - {:bech32, "~> 1.0"}, - {:typed_struct, "~> 0.3.0"} + {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, + {:blake2_elixir, "~> 0.8.1"}, + {:typed_struct, "~> 0.3.0"}, + {:rustler, "~> 0.35.0"}, + {:bech32, "~> 1.0"} ] end end diff --git a/mix.lock b/mix.lock index 1a18327..d3e44f7 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,21 @@ %{ "bech32": {:hex, :bech32, "1.0.0", "85a3bb58c408d735b5becb39e8a23536660ec0df1ef0afee72377c130939de1b", [:mix], [], "hexpm", "f781b8524c30a524922613d97c1858c27bd9f639b4e6b350f4a4843ee97607d3"}, + "blake2_elixir": {:hex, :blake2_elixir, "0.8.1", "93913497a79faf97c46e9140dd33d85d8cad0e93ae0039f2503c50204dd94c25", [:make, :mix], [{:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3f37b4fb1d1ae9f118f8d1e0358a0f80b422d52af99a23ddcaba5b0b48b33e5d"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"}, - "credo": {:hex, :credo, "1.7.4", "68ca5cf89071511c12fd9919eb84e388d231121988f6932756596195ccf7fd35", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9cf776d062c78bbe0f0de1ecaee183f18f2c3ec591326107989b054b7dddefc2"}, - "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "credo": {:hex, :credo, "1.7.10", "6e64fe59be8da5e30a1b96273b247b5cf1cc9e336b5fd66302a64b25749ad44d", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "71fbc9a6b8be21d993deca85bf151df023a3097b01e09a2809d460348561d8cd"}, + "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, + "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "req": {:hex, :req, "0.5.7", "b722680e03d531a2947282adff474362a48a02aa54b131196fbf7acaff5e4cee", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "c6035374615120a8923e8089d0c21a3496cf9eda2d287b806081b8f323ceee29"}, + "rustler": {:hex, :rustler, "0.35.0", "1e2e379e1150fab9982454973c74ac9899bd0377b3882166ee04127ea613b2d9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "a176bea1bb6711474f9dfad282066f2b7392e246459bf4e29dfff6d828779fdf"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, } diff --git a/native/sutra_uplc/.cargo/config.toml b/native/sutra_uplc/.cargo/config.toml new file mode 100644 index 0000000..20f03f3 --- /dev/null +++ b/native/sutra_uplc/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.'cfg(target_os = "macos")'] +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] diff --git a/native/sutra_uplc/.gitignore b/native/sutra_uplc/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/native/sutra_uplc/.gitignore @@ -0,0 +1 @@ +/target diff --git a/native/sutra_uplc/Cargo.lock b/native/sutra_uplc/Cargo.lock new file mode 100644 index 0000000..446d4d2 --- /dev/null +++ b/native/sutra_uplc/Cargo.lock @@ -0,0 +1,994 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blst" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "cc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cryptoxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382ce8820a5bb815055d3553a610e8cb542b2d767bbacea99038afda96cd760d" + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "libc" +version = "0.2.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miette" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +dependencies = [ + "miette-derive", + "once_cell", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "minicbor" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d15f4203d71fdf90903c2696e55426ac97a363c67b218488a73b534ce7aca10" +dependencies = [ + "half", + "minicbor-derive", +] + +[[package]] +name = "minicbor-derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1154809406efdb7982841adb6311b3d095b46f78342dd646736122fe6b19e267" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pallas-addresses" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84460293bb3323066e9ce608702750c14f02bc36d41c469e44b3eef5ec0fdbf6" +dependencies = [ + "base58", + "bech32", + "crc", + "cryptoxide", + "hex", + "pallas-codec", + "pallas-crypto", + "thiserror", +] + +[[package]] +name = "pallas-codec" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747279d1bc612986035619a3eaded8f9f4ceae29668aa7a5feae83681a0e93f4" +dependencies = [ + "hex", + "minicbor", + "num-bigint", + "serde", + "thiserror", +] + +[[package]] +name = "pallas-crypto" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6f8b08e32c7dbb50302222701ae15ef9ac1a7cc39225ce29c253f6ddab2aa7" +dependencies = [ + "cryptoxide", + "hex", + "pallas-codec", + "rand_core", + "serde", + "thiserror", +] + +[[package]] +name = "pallas-primitives" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24929d461308626183d5bf15290e6315f4cc67fa38a1a66469425919683cceb2" +dependencies = [ + "base58", + "bech32", + "hex", + "log", + "pallas-codec", + "pallas-crypto", + "serde", + "serde_json", +] + +[[package]] +name = "pallas-traverse" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ca94c2278a160c226d6f5bb1756ea5f355421158aaa697445f59f09477a6a4" +dependencies = [ + "hex", + "itertools 0.13.0", + "pallas-addresses", + "pallas-codec", + "pallas-crypto", + "pallas-primitives", + "paste", + "serde", + "thiserror", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "peg" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pretty" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f3aa1e3ca87d3b124db7461265ac176b40c277f37e503eaa29c9c75c037846" +dependencies = [ + "arrayvec", + "log", + "typed-arena", + "unicode-segmentation", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustler" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94bdfa68c0388cbd725f1ca54e975956482c262599e5cced04a903eec918b7f" +dependencies = [ + "inventory", + "rustler_codegen", + "rustler_sys", +] + +[[package]] +name = "rustler_codegen" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "996dc019acb78b91b4e0c1bd6fa2cd509a835d309de762dc15213b97eac399da" +dependencies = [ + "heck 0.5.0", + "inventory", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "rustler_sys" +version = "2.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd0e2c955cfc86ea4680067e1d5e711427b43f7befcb6e23c7807cf3dd90e97" +dependencies = [ + "regex", + "unreachable", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "sutra_uplc" +version = "0.1.0" +dependencies = [ + "rustler", + "uplc", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + +[[package]] +name = "uplc" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645fea1801ddac381079b508e93c9f7983cae57ffb25662257a6ef131adb18e2" +dependencies = [ + "blst", + "cryptoxide", + "hex", + "indexmap", + "itertools 0.10.5", + "k256", + "miette", + "num-bigint", + "num-integer", + "num-traits", + "once_cell", + "pallas-addresses", + "pallas-codec", + "pallas-crypto", + "pallas-primitives", + "pallas-traverse", + "peg", + "pretty", + "secp256k1", + "serde", + "serde_json", + "strum", + "strum_macros", + "thiserror", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] diff --git a/native/sutra_uplc/Cargo.toml b/native/sutra_uplc/Cargo.toml new file mode 100644 index 0000000..ccd82c1 --- /dev/null +++ b/native/sutra_uplc/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = [] +edition = "2021" +name = "sutra_uplc" +version = "0.1.0" + +[lib] +crate-type = ["cdylib"] +name = "sutra_uplc" +path = "src/lib.rs" + +[dependencies] +rustler = "0.34.0" +uplc = "=1.1.5" diff --git a/native/sutra_uplc/README.md b/native/sutra_uplc/README.md new file mode 100644 index 0000000..cdf093b --- /dev/null +++ b/native/sutra_uplc/README.md @@ -0,0 +1,20 @@ +# NIF for Elixir.Sutra.Uplc + +## To build the NIF module: + +- Your NIF will now build along with your project. + +## To load the NIF: + +```elixir +defmodule Sutra.Uplc do + use Rustler, otp_app: :sutra_offchain, crate: "sutra_uplc" + + # When your NIF is loaded, it will override this function. + def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded) +end +``` + +## Examples + +[This](https://github.com/rusterlium/NifIo) is a complete example of a NIF written in Rust. diff --git a/native/sutra_uplc/src/lib.rs b/native/sutra_uplc/src/lib.rs new file mode 100644 index 0000000..8d119ba --- /dev/null +++ b/native/sutra_uplc/src/lib.rs @@ -0,0 +1,62 @@ +use uplc::tx::{eval_phase_two_raw}; +use rustler::{Encoder, Env, NifResult, Term, Binary, OwnedBinary}; + +fn convert_to_binary<'a>(env: Env<'a>, bytes: Vec) -> Binary<'a> { + let mut binary = OwnedBinary::new(bytes.len()).unwrap(); + binary.as_mut_slice().copy_from_slice(&bytes); + binary.release(env) +} + +#[rustler::nif] +fn eval_phase_two<'a>( + env: Env<'a>, + tx_bytes: Binary<'a>, + utxos: Vec<(Binary<'a>, Binary<'a>)>, + cost_models_bytes: Option>, + initial_budget: (u64, u64), + slot_config: (u64, u64, u32) +) -> NifResult> { + // Convert Binary to Vec to extend lifetime + let tx_vec = tx_bytes.as_slice().to_vec(); + + // Convert Vec<(Binary, Binary)> to Vec<(Vec, Vec)> + let utxos_converted: Vec<(Vec, Vec)> = utxos + .into_iter() + .map(|(key, value)| ( + key.as_slice().to_vec(), + value.as_slice().to_vec() + )) + .collect(); + + // Convert Optional Binary to Vec + let cost_models_vec = cost_models_bytes + .map(|b| b.as_slice().to_vec()); + + // Get cost models slice reference + let cost_models_slice = cost_models_vec + .as_ref() + .map(|v| v.as_slice()); + + + // Call the evaluation function + match eval_phase_two_raw( + &tx_vec, + &utxos_converted, + cost_models_slice, + initial_budget, + slot_config, + true, // run_phase_one + |_| () + ) { + Ok(results) => { + let term_results: Vec = results + .into_iter() + .map(|bytes| convert_to_binary(env, bytes)) + .collect(); + Ok((true, term_results).encode(env)) + } + Err(e) => Ok((false, e.to_string()).encode(env)) + } +} + +rustler::init!("Elixir.Sutra.Uplc"); diff --git a/priv/native/libsutra_uplc.so b/priv/native/libsutra_uplc.so new file mode 100755 index 0000000..c3b81ab Binary files /dev/null and b/priv/native/libsutra_uplc.so differ diff --git a/test/fixture/transaction_certificate_fixture.ex b/test/fixture/transaction_certificate_fixture.ex index c5ead99..97c6e56 100644 --- a/test/fixture/transaction_certificate_fixture.ex +++ b/test/fixture/transaction_certificate_fixture.ex @@ -20,6 +20,7 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do %Certificate.PoolRegistration{ cost: %{"lovelace" => 170_000_000}, margin: 0.05, + margin_ratio: {1, 20}, metadata: %{ hash: "7DE1A14FD91A5C307C9816FDBC970BDD724C62C7EA1EB2BA97AC89EE0FB9FB7A", url: "https://upstream.org.uk/assets/preprod/metadata.json" @@ -44,16 +45,14 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } } ], - collateral: [], fee: %{"lovelace" => 189_173}, inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 0, transaction_id: "55E2606FD2FAA05415C721A846767FA1B37FCB477800851F5C2AFCA234604470" } ], mint: nil, - network_id: "testnet", outputs: [ %Transaction.Output{ address: %Sutra.Cardano.Address{ @@ -73,7 +72,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do value: %{"lovelace" => 9_497_638_770} } ], - reference_inputs: [], ttl: 75_103_845 } end @@ -111,18 +109,16 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do 133, 14, 82, 223, 173, 142, 226, 255, 254, 234>> } ], - collateral: [], collateral_return: nil, current_treasury_value: nil, fee: %{"lovelace" => 171_837}, inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 0, transaction_id: "0AC7C67527386E1E4E939497C92D442F6B0D9A46BB18C545F8FD1601AA625A24" } ], mint: nil, - network_id: "testnet", outputs: [ %Transaction.Output{ reference_script: nil, @@ -142,7 +138,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } } ], - reference_inputs: [], ttl: 74_828_452 } end @@ -165,9 +160,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do # For TxId: b67026a02bb1235e466fe56d64c0cd7155abccc2b0f3c646e8cc6bd4a0cf3275 (preprod) def body_b6703275 do %Transaction.TxBody{ - reference_inputs: [], - network_id: "testnet", - collateral: [], certificates: [ %Certificate.StakeRegistration{ stake_credential: %Sutra.Cardano.Address.Credential{ @@ -205,7 +197,7 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } ], inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 1, transaction_id: "B929989211AF8CA57E2EFB4C178EA50978D25B2A15E35BA2EF30D69022C1924E" } @@ -239,15 +231,13 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } } ], - collateral: [], fee: %{"lovelace" => 171_661}, inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 5, transaction_id: "7F946BFB9BBE8135C68D1096B7A52A0F47E715916D501440E7B6DAE59D243CB8" } ], - network_id: "testnet", outputs: [ %Transaction.Output{ reference_script: nil, @@ -267,7 +257,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } } ], - reference_inputs: [], ttl: 77_298_745 } end @@ -291,9 +280,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do def body_c8776215 do %TxBody{ - reference_inputs: [], - network_id: "testnet", - collateral: [], certificates: [ %Certificate.StakeRegDelegCert{ deposit: %{"lovelace" => 2_000_000}, @@ -360,7 +346,7 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } ], inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 0, transaction_id: "A4B94F6A211DD03DFE43E66A798EF4268285EC707A65E645C447FD96980E0601" } @@ -387,9 +373,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do def body_6ed6cedc do %TxBody{ - reference_inputs: [], - network_id: "testnet", - collateral: [], certificates: [ %Certificate.StakeVoteRegDelegCert{ deposit: %{"lovelace" => 2_000_000}, @@ -457,7 +440,7 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } ], inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 0, transaction_id: "7604E9B3B02F1C67955CACC6049FD70F69547A09AD347C85052AEC5D7FD07195" } @@ -483,9 +466,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do # for TxId: 16bdb2bad71be8b43df33ed41d6c785fbc4722f87600fd10311956898ec94ae9 (Preprod) def body_16bd4ae9 do %Transaction.TxBody{ - reference_inputs: [], - network_id: "testnet", - collateral: [], certificates: [ %Certificate.StakeVoteDelegCert{ drep: %Certificate.Drep{drep_value: nil, drep_type: 2}, @@ -552,7 +532,7 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } ], inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 0, transaction_id: "37E9AA9B258DEB306431E09A862F86CF3D59085EA025BF27A3064E06D0858A77" } @@ -579,9 +559,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do def body_a4809f97 do %TxBody{ - reference_inputs: [], - network_id: "testnet", - collateral: [], certificates: [ %Certificate.VoteRegDelegCert{ deposit: %{"lovelace" => 2_000_000}, @@ -648,7 +625,7 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } ], inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 0, transaction_id: "130663F385984456F5D3F9B1C7EDA359F942F325A30218CADEB23413DDAAF6B8" } @@ -675,9 +652,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do def body_92714dd1 do %TxBody{ - reference_inputs: [], - network_id: "testnet", - collateral: [], certificates: [ %Certificate.VoteDelegCert{ drep: %Certificate.Drep{drep_value: nil, drep_type: 3}, @@ -726,7 +700,7 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } ], inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 1, transaction_id: "FC3CB52653C3F2E0B983EBC10B7634A88816AA1EA01D68AACC40C4B08A669A84" } @@ -752,9 +726,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do # For TxId: 0891a7e18016b08bfe1bed306c0c12bd68a2e4cc933181659ace22b331e476f3 (Preprod) def body_089176f3 do %TxBody{ - reference_inputs: [], - network_id: "testnet", - collateral: [], certificates: [ %Certificate.RegDrepCert{ anchor: nil, @@ -787,7 +758,7 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } ], inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 0, transaction_id: "9E8EF69967AE9FB1234D4210453DE1C8A5882952EE7FC0B90877AF1368239327" } @@ -814,7 +785,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do def body_4ab1f4ed do %Transaction.TxBody{ - reference_inputs: [], total_collateral: %{"lovelace" => 5_000_000}, collateral_return: %Transaction.Output{ reference_script: nil, @@ -868,9 +838,8 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do network: :testnet } }, - network_id: "testnet", collateral: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 0, transaction_id: "4EDEDD73B30FC7C7140BB79ECC072CE800617356809850968E4E5EB2699A465E" } @@ -942,7 +911,7 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } ], inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 0, transaction_id: "4EDEDD73B30FC7C7140BB79ECC072CE800617356809850968E4E5EB2699A465E" } @@ -958,7 +927,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do vkey: "0ABB7B89E091DCD3201AEA501854A4CB05290862D88B6EB30AFA6DFD23F54467" }, %Witness.Redeemer{ - is_legacy: true, exunits: {8291, 2_214_370}, data: %Sutra.Data.Plutus.Constr{fields: [], index: 0}, index: 0, @@ -977,9 +945,6 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do def body_d77f127d do %TxBody{ - reference_inputs: [], - network_id: "testnet", - collateral: [], certificates: [ %Certificate.UpdateDrepCert{ anchor: nil, @@ -1045,7 +1010,7 @@ defmodule Sutra.Test.Fixture.TransactionCertificateFixture do } ], inputs: [ - %Transaction.Output.OutputReference{ + %Transaction.OutputReference{ output_index: 0, transaction_id: "8267A54D323ED85E5271546AA85E888AE4D120D24B49A1A5DEC70F3536ACFEE7" } diff --git a/test/sutra/cardano/asset_test.exs b/test/sutra/cardano/asset_test.exs index b51cd01..1903bee 100644 --- a/test/sutra/cardano/asset_test.exs +++ b/test/sutra/cardano/asset_test.exs @@ -31,8 +31,8 @@ defmodule Sutra.Cardano.AssetTest do assert @asset_cbor["with_token"] == Asset.to_plutus(%{ "lovelace" => 1_000_000, - "policy-id-1" => %{"tkn1" => 100, "tkn2" => 200}, - "policy-id-2" => %{"tkn3" => 300} + "706F6C6963792D69642D31" => %{"746B6E31" => 100, "746B6E32" => 200}, + "706F6C6963792D69642D32" => %{"746B6E33" => 300} }) |> Data.encode() end diff --git a/test/sutra/cardano/transaction_test.exs b/test/sutra/cardano/transaction_test.exs new file mode 100644 index 0000000..ceda9cd --- /dev/null +++ b/test/sutra/cardano/transaction_test.exs @@ -0,0 +1,17 @@ +defmodule Sutra.Cardano.TransactionTest do + @moduledoc false + + use ExUnit.Case + + alias Sutra.Cardano.Transaction + + describe "Transaction Utility Functions" do + # Tx raw for tx_id: b7132e1233ec3898c5190b6c35853c08b99316c133d8a946ee29be63c34aab84, preprod + @valid_cbor_hex1 "84a800d901028282582039fbe758f447a2a0e3f219c88f52a55946ab34aee8b506531d0554767164c71f00825820f8314222bc4ec30bccdc4b7e78d624c9f57f7f7b60722dd23db37e417fd95fe90001818258390025195af85c41b9d97da7f4f215d3e74c9cef7f04739d6ba473ba72a2358a4e4105c08f59e4779070699c7a72566893332f9857db4e742beb821a002ee49ba1581cc6e65ba7878b2f8ea0ad39287d3e2fd256dc5c4160fc19bdf4c4d87ea1457447454e531a01f78a40021a00033c670b58209917c9109930038c163b1d8c9e7bce627925d6b95ca8b458022482522e6c47d30dd901028182582033971398f5b7c7c0773e96414e757c44e0659cc09f080154fca3dcca52ed316b040ed9010281581c25195af85c41b9d97da7f4f215d3e74c9cef7f04739d6ba473ba72a2108258390025195af85c41b9d97da7f4f215d3e74c9cef7f04739d6ba473ba72a2358a4e4105c08f59e4779070699c7a72566893332f9857db4e742beb1a004770a5111a0004da9ba400d901028182582056cf5d8dbb766190b751681732b36d877aac24e6a50738fe78bdceb8b8d97d1658403b1a306f48b807fab76051cbf66c5fee5b01a75b2b9d71c8b0bd3c0c24ad90325acc8cceb3720ccaf7ae108e455ad68e9f6274dceb2702d102088add0906cf0603d90102815901bf5901bc0100003232323232323232323232322223232323232323253330123015300f0021533301200314985854ccc04800c4c8c94ccc0514ccc050cc034c058008c0580044cc034c058c04c004c058c04c0085280a4c2c60286ea8c054c044c050dd500418099baa30163300e30163300e3374a9001198071ba8375a602800297ae03300e300d4a297ae03300e30163300e3374a900225eb80cc038c03528a5eb812f5c02c60240026ea8c048c03cc03cc044dd5004198011198010010009bac3011300e300d301037540084600446600400400244a666aae7c0045280a99980799baf301100100314a226004601c0026ea4dd7180718069baa004300d300c3754002446464646464646464a666022a66602266e200080045288a99980899b8700200113232533301330160041337106eb4c054008dd6980a8008a503013004301200414a0294454ccc044cdd78030028999808980598099808004180598099808003a5114a0601a00460180046ea8008dd50011807001180680118059baa002300a37540044601460086ea80048cdd2999803000a4004900025eb815d02ab9d23002300230023002300230020015744ae6955cf2ba1370e90011ba548000104d9010281d8799f581c25195af85c41b9d97da7f4f215d3e74c9cef7f04739d6ba473ba72a2d8799fd8799f581c25195af85c41b9d97da7f4f215d3e74c9cef7f04739d6ba473ba72a2ffd8799fd8799fd8799f581c358a4e4105c08f59e4779070699c7a72566893332f9857db4e742bebffffffffd87a80ff05a182000082d8798082194be91a006592d4f5f6" + + test "tx_id/1 returns valid TransactionId" do + assert Transaction.from_hex(@valid_cbor_hex1) |> Transaction.tx_id() == + "b7132e1233ec3898c5190b6c35853c08b99316c133d8a946ee29be63c34aab84" + end + end +end