From e4e818081190b5a787174bfc9fd6925fe587e269 Mon Sep 17 00:00:00 2001 From: Juan Hurtado Date: Thu, 10 Feb 2022 18:04:22 -0500 Subject: [PATCH] CAP-0027 Add support for muxed accounts (#96) * Bump to v0.5.0 stellar_base dep * Add new keypair functions to handle muxed accounts * Improve AccountID. Add descriptive error messages * CAP-0027 Add support for muxed accounts * Fix code formatting * Use muxed_id instead of id in the Account struct * Improve module description --- lib/keypair.ex | 13 ++ lib/keypair/default.ex | 31 ++++- lib/keypair/spec.ex | 20 ++-- lib/tx_build/account.ex | 113 +++++++++++++++--- lib/tx_build/account_id.ex | 14 +-- mix.exs | 2 +- mix.lock | 4 +- test/keypair/keypair_test.exs | 33 +++++ test/support/fixtures/xdr.ex | 15 +++ test/support/fixtures/xdr/accounts.ex | 44 +++++++ test/support/fixtures/xdr/transactions.ex | 81 +++++++++++++ test/test_helper.exs | 3 + test/tx_build/account_id_test.exs | 2 +- test/tx_build/account_merge_test.exs | 2 +- test/tx_build/account_test.exs | 73 +++++++++-- .../begin_sponsoring_future_reserves_test.exs | 2 +- test/tx_build/clawback_test.exs | 2 +- test/tx_build/create_account_test.exs | 2 +- .../path_payment_strict_receive_test.exs | 2 +- .../path_payment_strict_send_test.exs | 2 +- test/tx_build/payment_test.exs | 2 +- test/tx_build/set_options_test.exs | 2 +- test/tx_build/transaction_test.exs | 67 +++++++++-- test/tx_build/validations_test.exs | 6 +- 24 files changed, 463 insertions(+), 74 deletions(-) create mode 100644 test/support/fixtures/xdr.ex create mode 100644 test/support/fixtures/xdr/accounts.ex create mode 100644 test/support/fixtures/xdr/transactions.ex diff --git a/lib/keypair.ex b/lib/keypair.ex index e2f958e3..d19d3aea 100644 --- a/lib/keypair.ex +++ b/lib/keypair.ex @@ -13,9 +13,19 @@ defmodule Stellar.KeyPair do @impl true def from_secret_seed(secret), do: impl().from_secret_seed(secret) + @impl true + def from_raw_public_key(raw_public_key), do: impl().from_raw_public_key(raw_public_key) + + @impl true + def from_raw_muxed_account(raw_muxed_account), + do: impl().from_raw_muxed_account(raw_muxed_account) + @impl true def raw_public_key(public_key), do: impl().raw_public_key(public_key) + @impl true + def raw_muxed_account(public_key), do: impl().raw_muxed_account(public_key) + @impl true def raw_secret_seed(secret), do: impl().raw_secret_seed(secret) @@ -25,6 +35,9 @@ defmodule Stellar.KeyPair do @impl true def validate_public_key(public_key), do: impl().validate_public_key(public_key) + @impl true + def validate_muxed_account(public_key), do: impl().validate_muxed_account(public_key) + @impl true def validate_secret_seed(secret), do: impl().validate_secret_seed(secret) diff --git a/lib/keypair/default.ex b/lib/keypair/default.ex index 5b6e0082..5efc061f 100644 --- a/lib/keypair/default.ex +++ b/lib/keypair/default.ex @@ -26,6 +26,16 @@ defmodule Stellar.KeyPair.Default do {public_key, secret} end + @impl true + def from_raw_public_key(public_key) do + StrKey.encode!(public_key, :ed25519_public_key) + end + + @impl true + def from_raw_muxed_account(muxed_account) do + StrKey.encode!(muxed_account, :muxed_account) + end + @impl true def raw_public_key(public_key) do StrKey.decode!(public_key, :ed25519_public_key) @@ -36,6 +46,11 @@ defmodule Stellar.KeyPair.Default do StrKey.decode!(secret, :ed25519_secret_seed) end + @impl true + def raw_muxed_account(muxed_account) do + StrKey.decode!(muxed_account, :muxed_account) + end + @impl true def sign(<>, <>) do raw_secret = raw_secret_seed(secret) @@ -45,22 +60,26 @@ defmodule Stellar.KeyPair.Default do def sign(_payload, _secret), do: {:error, :invalid_signature_payload} @impl true - def validate_public_key(public_key) when byte_size(public_key) == 56 do + def validate_public_key(public_key) do case StrKey.decode(public_key, :ed25519_public_key) do {:ok, _key} -> :ok {:error, _reason} -> {:error, :invalid_ed25519_public_key} end end - def validate_public_key(_public_key), do: {:error, :invalid_ed25519_public_key} + @impl true + def validate_muxed_account(muxed_account) do + case StrKey.decode(muxed_account, :muxed_account) do + {:ok, _key} -> :ok + {:error, _reason} -> {:error, :invalid_ed25519_muxed_account} + end + end @impl true - def validate_secret_seed(secret) when byte_size(secret) == 56 do + def validate_secret_seed(secret) do case StrKey.decode(secret, :ed25519_secret_seed) do - {:ok, _secret} -> :ok + {:ok, _key} -> :ok {:error, _reason} -> {:error, :invalid_ed25519_secret_seed} end end - - def validate_secret_seed(_secret), do: {:error, :invalid_ed25519_secret_seed} end diff --git a/lib/keypair/spec.ex b/lib/keypair/spec.ex index d9ff60a0..1d4251b9 100644 --- a/lib/keypair/spec.ex +++ b/lib/keypair/spec.ex @@ -12,16 +12,20 @@ defmodule Stellar.KeyPair.Spec do @type validation :: :ok | error() @callback random() :: {public_key(), secret_seed()} - @callback from_secret_seed(secret_seed()) :: {public_key(), secret_seed()} - + @callback from_raw_public_key(binary()) :: public_key() + @callback from_raw_muxed_account(binary()) :: public_key() @callback raw_public_key(public_key()) :: binary() - + @callback raw_muxed_account(public_key()) :: binary() @callback raw_secret_seed(public_key()) :: binary() - - @callback validate_public_key(String.t()) :: validation() - - @callback validate_secret_seed(String.t()) :: validation() - + @callback validate_public_key(public_key()) :: validation() + @callback validate_muxed_account(public_key()) :: validation() + @callback validate_secret_seed(public_key()) :: validation() @callback sign(binary(), secret_seed()) :: binary() | error() + + @optional_callbacks from_raw_public_key: 1, + from_raw_muxed_account: 1, + validate_public_key: 1, + validate_muxed_account: 1, + validate_secret_seed: 1 end diff --git a/lib/tx_build/account.ex b/lib/tx_build/account.ex index 07855887..675f0679 100644 --- a/lib/tx_build/account.ex +++ b/lib/tx_build/account.ex @@ -3,40 +3,123 @@ defmodule Stellar.TxBuild.Account do `Account` struct definition. """ alias Stellar.KeyPair - alias StellarBase.XDR.{CryptoKeyType, MuxedAccount, UInt256} + alias StellarBase.XDR.{CryptoKeyType, MuxedAccount, MuxedAccountMed25519, UInt64, UInt256} @behaviour Stellar.TxBuild.XDR + @type address :: String.t() @type account_id :: String.t() - @type validation :: {:ok, account_id()} | {:error, atom()} + @type muxed_id :: integer() | nil + @type type :: :ed25519_public_key | :ed25519_muxed_account + @type error :: {:error, atom()} + @type validation :: {:ok, any()} | error() - @type t :: %__MODULE__{account_id: account_id()} + @type t :: %__MODULE__{ + address: address(), + account_id: account_id(), + muxed_id: muxed_id(), + type: type() + } - defstruct [:account_id] + defstruct [:address, :account_id, :muxed_id, :type] @impl true - def new(account_id, opts \\ []) + def new(address, opts \\ []) - def new(account_id, _opts) do - with {:ok, account_id} <- validate_raw_account_id(account_id), - do: %__MODULE__{account_id: account_id} + def new(address, _opts) when byte_size(address) == 69 do + with {:ok, address} <- validate_muxed_address(address), + {:ok, {account_id, muxed_id}} <- parse_muxed_address(address) do + %__MODULE__{ + address: address, + account_id: account_id, + muxed_id: muxed_id, + type: :ed25519_muxed_account + } + end end + def new(address, _opts) when byte_size(address) == 56 do + case validate_public_key(address) do + {:ok, account_id} -> + %__MODULE__{ + address: account_id, + account_id: account_id, + type: :ed25519_public_key + } + + error -> + error + end + end + + def new(_address, _opts), do: {:error, :invalid_ed25519_public_key} + @impl true + def to_xdr(%__MODULE__{account_id: account_id, muxed_id: muxed_id, type: :ed25519_muxed_account}) do + type = CryptoKeyType.new(:KEY_TYPE_MUXED_ED25519) + ed25519_public_key_xdr = ed25519_public_key_xdr(account_id) + + muxed_id + |> UInt64.new() + |> MuxedAccountMed25519.new(ed25519_public_key_xdr) + |> MuxedAccount.new(type) + end + def to_xdr(%__MODULE__{account_id: account_id}) do type = CryptoKeyType.new(:KEY_TYPE_ED25519) + ed25519_public_key_xdr = ed25519_public_key_xdr(account_id) + MuxedAccount.new(ed25519_public_key_xdr, type) + end + + @spec create_muxed(account_id :: account_id(), muxed_id :: muxed_id()) :: t() + def create_muxed(account_id, muxed_id) + when byte_size(account_id) == 56 and is_integer(muxed_id) do account_id |> KeyPair.raw_public_key() - |> UInt256.new() - |> MuxedAccount.new(type) + |> Kernel.<>(<>) + |> KeyPair.from_raw_muxed_account() + |> new() end - @spec validate_raw_account_id(account_id :: String.t()) :: validation() - defp validate_raw_account_id(account_id) do - case KeyPair.validate_public_key(account_id) do - :ok -> {:ok, account_id} - _error -> {:error, :invalid_account_id} + def create_muxed(_account_id, _id), do: {:error, :invalid_muxed_account} + + @spec validate_muxed_address(address :: address()) :: validation() + defp validate_muxed_address(address) do + case KeyPair.validate_muxed_account(address) do + :ok -> {:ok, address} + error -> error + end + end + + @spec validate_public_key(address :: address()) :: validation() + defp validate_public_key(address) do + case KeyPair.validate_public_key(address) do + :ok -> {:ok, address} + error -> error end end + + @spec parse_muxed_address(address :: address()) :: {:ok, {account_id(), muxed_id()}} | error() + defp parse_muxed_address(address) do + address + |> KeyPair.raw_muxed_account() + |> encode_muxed_address() + end + + @spec encode_muxed_address(decoded_address :: binary()) :: + {:ok, {account_id(), muxed_id()}} | error() + defp encode_muxed_address(<>) do + ed25519_key = KeyPair.from_raw_public_key(decoded) + {:ok, {ed25519_key, muxed_id}} + end + + defp encode_muxed_address(_muxed_account), do: {:error, :invalid_muxed_address} + + @spec ed25519_public_key_xdr(account_id :: account_id()) :: UInt256.t() + defp ed25519_public_key_xdr(account_id) do + account_id + |> KeyPair.raw_public_key() + |> UInt256.new() + end end diff --git a/lib/tx_build/account_id.ex b/lib/tx_build/account_id.ex index 8c4caec8..51c04534 100644 --- a/lib/tx_build/account_id.ex +++ b/lib/tx_build/account_id.ex @@ -18,8 +18,10 @@ defmodule Stellar.TxBuild.AccountID do def new(account_id, opts \\ []) def new(account_id, _opts) do - with {:ok, account_id} <- validate_raw_account_id(account_id), - do: %__MODULE__{account_id: account_id} + case KeyPair.validate_public_key(account_id) do + :ok -> %__MODULE__{account_id: account_id} + error -> error + end end @impl true @@ -32,12 +34,4 @@ defmodule Stellar.TxBuild.AccountID do |> PublicKey.new(type) |> AccountID.new() end - - @spec validate_raw_account_id(account_id :: String.t()) :: validation() - defp validate_raw_account_id(account_id) do - case KeyPair.validate_public_key(account_id) do - :ok -> {:ok, account_id} - _error -> {:error, :invalid_account_id} - end - end end diff --git a/mix.exs b/mix.exs index db3e61b1..0c45e6b3 100644 --- a/mix.exs +++ b/mix.exs @@ -37,7 +37,7 @@ defmodule Stellar.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:stellar_base, "~> 0.3.0"}, + {:stellar_base, "~> 0.5.0"}, {:ed25519, "~> 1.3"}, {:hackney, "~> 1.17", optional: true}, {:jason, "~> 1.0", optional: true}, diff --git a/mix.lock b/mix.lock index 068e99f6..51ebb744 100644 --- a/mix.lock +++ b/mix.lock @@ -1,7 +1,7 @@ %{ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, - "crc": {:hex, :crc, "0.10.2", "93ee6788904735d4d93f59a1e80860e4c9aa44e8d2ff7c69857eb62757454137", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "6b931cfb5e7d20c3c4113adab460f29ee5a50a36b397edd81c9bede2bbdb505c"}, + "crc": {:hex, :crc, "0.10.4", "06f5f54e2ec2954968703dcd37d7a4c65cee7a5305c48a23c509dc20a5469d4f", [:mix, :rebar3], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "90bdd5b5ac883f0b1860692324ed3f53fdaa6f1e8483771873fea07e71def91d"}, "credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.15", "b29e8e729f4aa4a00436580dcc2c9c5c51890613457c193cc8525c388ccb2f06", [:mix], [], "hexpm", "044523d6438ea19c1b8ec877ec221b008661d3c27e3b848f4c879f500421ca5c"}, @@ -24,6 +24,6 @@ "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, - "stellar_base": {:hex, :stellar_base, "0.3.0", "21e0253044ba2bcb86f1af92e2847543b42c40cac0863318a22d5a252814d799", [:mix], [{:crc, "~> 0.10.0", [hex: :crc, repo: "hexpm", optional: false]}, {:elixir_xdr, "~> 0.2.0", [hex: :elixir_xdr, repo: "hexpm", optional: false]}], "hexpm", "9f53a22277842a2fc70d5544944bd04a8cd9e00b4cfb5d627887cb30313eff5d"}, + "stellar_base": {:hex, :stellar_base, "0.5.0", "41dab1d99b86f7ee8a86d07d1ddc59234f3be1df6a327479a7225b347425f546", [:mix], [{:crc, "~> 0.10.0", [hex: :crc, repo: "hexpm", optional: false]}, {:elixir_xdr, "~> 0.2.0", [hex: :elixir_xdr, repo: "hexpm", optional: false]}], "hexpm", "fa9f5c9bcc8607f2b80b49791541fab3835442f89b98f848bd86744873862cdc"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/test/keypair/keypair_test.exs b/test/keypair/keypair_test.exs index 1974e890..5af789ad 100644 --- a/test/keypair/keypair_test.exs +++ b/test/keypair/keypair_test.exs @@ -15,6 +15,12 @@ defmodule Stellar.KeyPair.CannedKeyPairImpl do :ok end + @impl true + def from_raw_public_key(_public_key) do + send(self(), {:from_raw_public_key, "PUBLIC_KEY"}) + :ok + end + @impl true def raw_public_key(_public_key) do send(self(), {:raw_public_key, "RAW_PUBLIC_KEY"}) @@ -27,6 +33,12 @@ defmodule Stellar.KeyPair.CannedKeyPairImpl do :ok end + @impl true + def raw_muxed_account(_muxed_account) do + send(self(), {:raw_muxed_account, "RAW_MUXED_ACCOUNT"}) + :ok + end + @impl true def sign(_payload, _secret) do send(self(), {:signature, "SIGNATURE"}) @@ -44,6 +56,12 @@ defmodule Stellar.KeyPair.CannedKeyPairImpl do send(self(), {:ok, "SECRET_SEED"}) :ok end + + @impl true + def validate_muxed_account(_muxed_account) do + send(self(), {:validate_muxed_account, "MUXED_ACCOUNT"}) + :ok + end end defmodule Stellar.KeyPairTest do @@ -69,6 +87,11 @@ defmodule Stellar.KeyPairTest do assert_receive({:secret, "SECRET"}) end + test "from_raw_public_key/2" do + Stellar.KeyPair.from_raw_public_key("RAW_PUBLIC_KEY") + assert_receive({:from_raw_public_key, "PUBLIC_KEY"}) + end + test "raw_public_key/1" do Stellar.KeyPair.raw_public_key("RAW_PUBLIC_KEY") assert_receive({:raw_public_key, "RAW_PUBLIC_KEY"}) @@ -79,6 +102,11 @@ defmodule Stellar.KeyPairTest do assert_receive({:raw_secret, "RAW_SECRET"}) end + test "raw_muxed_account/2" do + Stellar.KeyPair.raw_muxed_account("MUXED_ACCOUNT") + assert_receive({:raw_muxed_account, "RAW_MUXED_ACCOUNT"}) + end + test "sign/2" do Stellar.KeyPair.sign(<<0, 0, 0, 0>>, "SECRET") assert_receive({:signature, "SIGNATURE"}) @@ -93,4 +121,9 @@ defmodule Stellar.KeyPairTest do Stellar.KeyPair.validate_secret_seed("SECRET_SEED") assert_receive({:ok, "SECRET_SEED"}) end + + test "validate_muxed_account/2" do + Stellar.KeyPair.validate_muxed_account("MUXED_ACCOUNT") + assert_receive({:validate_muxed_account, "MUXED_ACCOUNT"}) + end end diff --git a/test/support/fixtures/xdr.ex b/test/support/fixtures/xdr.ex new file mode 100644 index 00000000..1fe05b88 --- /dev/null +++ b/test/support/fixtures/xdr.ex @@ -0,0 +1,15 @@ +defmodule Stellar.Test.Fixtures.XDR do + @moduledoc """ + Mocks for XDR constructions. + """ + + alias Stellar.Test.Fixtures.XDR.{Accounts, Transactions} + + # accounts + defdelegate muxed_account(account_id), to: Accounts + defdelegate muxed_account_med25519(address), to: Accounts + + # transactions + defdelegate transaction(account_id), to: Transactions + defdelegate transaction_with_muxed_account(address), to: Transactions +end diff --git a/test/support/fixtures/xdr/accounts.ex b/test/support/fixtures/xdr/accounts.ex new file mode 100644 index 00000000..adb97317 --- /dev/null +++ b/test/support/fixtures/xdr/accounts.ex @@ -0,0 +1,44 @@ +defmodule Stellar.Test.Fixtures.XDR.Accounts do + @moduledoc """ + XDR constructions for Accounts. + """ + + alias StellarBase.XDR.{CryptoKeyType, MuxedAccount, MuxedAccountMed25519, UInt256, UInt64} + + @type account_id :: String.t() + @type address :: String.t() + @type xdr :: MuxedAccount.t() | :error + + @spec muxed_account(account_id :: account_id()) :: xdr() + def muxed_account("GBXV5U2D67J7HUW42JKBGD4WNZON4SOPXXDFTYQ7BCOG5VCARGCRMQQH") do + %MuxedAccount{ + account: %UInt256{ + datum: + <<111, 94, 211, 67, 247, 211, 243, 210, 220, 210, 84, 19, 15, 150, 110, 92, 222, 73, + 207, 189, 198, 89, 226, 31, 8, 156, 110, 212, 64, 137, 133, 22>> + }, + type: %CryptoKeyType{identifier: :KEY_TYPE_ED25519} + } + end + + def muxed_account(_address), do: :error + + @spec muxed_account_med25519(address :: address()) :: xdr() + def muxed_account_med25519( + "MBXV5U2D67J7HUW42JKBGD4WNZON4SOPXXDFTYQ7BCOG5VCARGCRMAAAAAAAAAAAARKPQ" + ) do + %MuxedAccount{ + account: %MuxedAccountMed25519{ + ed25519: %UInt256{ + datum: + <<111, 94, 211, 67, 247, 211, 243, 210, 220, 210, 84, 19, 15, 150, 110, 92, 222, 73, + 207, 189, 198, 89, 226, 31, 8, 156, 110, 212, 64, 137, 133, 22>> + }, + id: %UInt64{datum: 4} + }, + type: %CryptoKeyType{identifier: :KEY_TYPE_MUXED_ED25519} + } + end + + def muxed_account_med25519(_address), do: :error +end diff --git a/test/support/fixtures/xdr/transactions.ex b/test/support/fixtures/xdr/transactions.ex new file mode 100644 index 00000000..43cfd382 --- /dev/null +++ b/test/support/fixtures/xdr/transactions.ex @@ -0,0 +1,81 @@ +defmodule Stellar.Test.Fixtures.XDR.Transactions do + @moduledoc """ + XDR constructions for Transactions. + """ + + alias StellarBase.XDR.{ + Transaction, + Ext, + UInt32, + Memo, + MemoType, + Operations, + SequenceNumber, + MuxedAccount, + MuxedAccountMed25519, + UInt256, + UInt64, + CryptoKeyType, + OptionalTimeBounds, + Void + } + + @type account_id :: String.t() + @type address :: String.t() + @type xdr :: Transaction.t() | :error + + @spec transaction(account_id :: account_id()) :: xdr() + def transaction("GBXV5U2D67J7HUW42JKBGD4WNZON4SOPXXDFTYQ7BCOG5VCARGCRMQQH") do + %Transaction{ + ext: %Ext{type: 0, value: %Void{value: nil}}, + fee: %UInt32{datum: 100}, + memo: %Memo{ + type: %MemoType{identifier: :MEMO_NONE}, + value: nil + }, + operations: %Operations{operations: []}, + seq_num: %SequenceNumber{sequence_number: 4_130_487_228_432_385}, + source_account: %MuxedAccount{ + account: %UInt256{ + datum: + <<111, 94, 211, 67, 247, 211, 243, 210, 220, 210, 84, 19, 15, 150, 110, 92, 222, 73, + 207, 189, 198, 89, 226, 31, 8, 156, 110, 212, 64, 137, 133, 22>> + }, + type: %CryptoKeyType{identifier: :KEY_TYPE_ED25519} + }, + time_bounds: %OptionalTimeBounds{time_bounds: nil} + } + end + + def transaction(_address), do: :error + + @spec transaction_with_muxed_account(address :: address()) :: xdr() + def transaction_with_muxed_account( + "MBXV5U2D67J7HUW42JKBGD4WNZON4SOPXXDFTYQ7BCOG5VCARGCRMAAAAAAAAAAAARKPQ" + ) do + %Transaction{ + ext: %Ext{type: 0, value: %Void{value: nil}}, + fee: %UInt32{datum: 100}, + memo: %Memo{ + type: %MemoType{identifier: :MEMO_NONE}, + value: nil + }, + operations: %Operations{operations: []}, + seq_num: %SequenceNumber{sequence_number: 4_130_487_228_432_385}, + source_account: %MuxedAccount{ + account: %MuxedAccountMed25519{ + ed25519: %UInt256{ + datum: + <<111, 94, 211, 67, 247, 211, 243, 210, 220, 210, 84, 19, 15, 150, 110, 92, 222, 73, + 207, 189, 198, 89, 226, 31, 8, 156, 110, 212, 64, 137, 133, 22>> + }, + id: %UInt64{datum: 4} + }, + type: %CryptoKeyType{identifier: :KEY_TYPE_MUXED_ED25519} + }, + time_bounds: %OptionalTimeBounds{time_bounds: nil} + } + end + + def transaction_with_muxed_account(_address), do: :error +end diff --git a/test/test_helper.exs b/test/test_helper.exs index 37411c62..c442cf89 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,7 @@ Code.require_file("test/support/xdr_fixtures.ex") Code.require_file("test/support/fixtures/horizon.ex") +Code.require_file("test/support/fixtures/xdr/accounts.ex") +Code.require_file("test/support/fixtures/xdr/transactions.ex") +Code.require_file("test/support/fixtures/xdr.ex") ExUnit.start() diff --git a/test/tx_build/account_id_test.exs b/test/tx_build/account_id_test.exs index 2fb84c45..35dc70ce 100644 --- a/test/tx_build/account_id_test.exs +++ b/test/tx_build/account_id_test.exs @@ -18,7 +18,7 @@ defmodule Stellar.TxBuild.AccountIDTest do end test "new/2 invalid_key" do - {:error, :invalid_account_id} = AccountID.new("ABCD") + {:error, :invalid_ed25519_public_key} = AccountID.new("ABCD") end test "to_xdr/1", %{xdr: xdr, account_id: account_id} do diff --git a/test/tx_build/account_merge_test.exs b/test/tx_build/account_merge_test.exs index cbebee31..e3283a1d 100644 --- a/test/tx_build/account_merge_test.exs +++ b/test/tx_build/account_merge_test.exs @@ -20,7 +20,7 @@ defmodule Stellar.TxBuild.AccountMergeTest do end test "new/2 with_invalid_destination" do - {:error, [destination: :invalid_account_id]} = AccountMerge.new(destination: "ABC") + {:error, [destination: :invalid_ed25519_public_key]} = AccountMerge.new(destination: "ABC") end test "new/2 with_invalid_attributes" do diff --git a/test/tx_build/account_test.exs b/test/tx_build/account_test.exs index 652f7367..88865fcc 100644 --- a/test/tx_build/account_test.exs +++ b/test/tx_build/account_test.exs @@ -1,30 +1,79 @@ defmodule Stellar.TxBuild.AccountTest do use ExUnit.Case - import Stellar.Test.XDRFixtures, only: [muxed_account_xdr: 1] + alias Stellar.Test.Fixtures.XDR, as: XDRFixtures alias Stellar.TxBuild.Account setup do - account_id = "GD726E62G6G4ANHWHIQTH5LNMFVF2EQSEXITB6DZCCTKVU6EQRRE2SJS" + account_id = "GBXV5U2D67J7HUW42JKBGD4WNZON4SOPXXDFTYQ7BCOG5VCARGCRMQQH" + muxed_address = "MBXV5U2D67J7HUW42JKBGD4WNZON4SOPXXDFTYQ7BCOG5VCARGCRMAAAAAAAAAAAARKPQ" %{ account_id: account_id, - xdr: muxed_account_xdr(account_id) + muxed_address: muxed_address, + muxed_account_xdr: XDRFixtures.muxed_account(account_id), + muxed_account_med25519_xdr: XDRFixtures.muxed_account_med25519(muxed_address) } end - test "new/2", %{account_id: account_id} do - %Account{account_id: ^account_id} = Account.new(account_id) + describe "new/2" do + test "ed25519_public_key", %{account_id: account_id} do + %Account{address: ^account_id, account_id: ^account_id, type: :ed25519_public_key} = + Account.new(account_id) + end + + test "muxed_account", %{muxed_address: muxed_address, account_id: account_id} do + %Account{ + address: ^muxed_address, + account_id: ^account_id, + muxed_id: 4, + type: :ed25519_muxed_account + } = Account.new(muxed_address) + end + + test "invalid_ed25519_public_key" do + {:error, :invalid_ed25519_public_key} = + Account.new("GAAAAA2D67J7HUW42JKBGD4WNZON4SOPXXDFTYQ7BCOG5VCARGCRMQQH") + end + + test "invalid_muxed_account" do + {:error, :invalid_ed25519_muxed_account} = + Account.new("MBXV5U2D67J7HUW42JKBGD4WNZON4SOPXXDFTYQ7BCOG5VCARGCRMAAAAAAAAAAAARKPD") + end + + test "invalid_key" do + {:error, :invalid_ed25519_public_key} = Account.new("ABCD") + end end - test "new/2 invalid_key" do - {:error, :invalid_account_id} = Account.new("ABCD") + describe "to_xdr/1" do + test "ed25519_public_key", %{muxed_account_xdr: xdr, account_id: account_id} do + ^xdr = + account_id + |> Account.new() + |> Account.to_xdr() + end + + test "muxed_account", %{muxed_account_med25519_xdr: xdr, muxed_address: muxed_address} do + ^xdr = + muxed_address + |> Account.new() + |> Account.to_xdr() + end end - test "to_xdr/1", %{xdr: xdr, account_id: account_id} do - ^xdr = - account_id - |> Account.new() - |> Account.to_xdr() + describe "create_muxed/2" do + test "success", %{muxed_address: muxed_address, account_id: account_id} do + %Account{ + address: ^muxed_address, + account_id: ^account_id, + muxed_id: 4, + type: :ed25519_muxed_account + } = Account.create_muxed(account_id, 4) + end + + test "invalid_attribute", %{account_id: account_id} do + {:error, :invalid_muxed_account} = Account.create_muxed(account_id, "123") + end end end diff --git a/test/tx_build/begin_sponsoring_future_reserves_test.exs b/test/tx_build/begin_sponsoring_future_reserves_test.exs index 51059ba9..9c06262a 100644 --- a/test/tx_build/begin_sponsoring_future_reserves_test.exs +++ b/test/tx_build/begin_sponsoring_future_reserves_test.exs @@ -21,7 +21,7 @@ defmodule Stellar.TxBuild.BeginSponsoringFutureReservesTest do end test "new/2 with_invalid_sponsored_id" do - {:error, [sponsored_id: :invalid_account_id]} = + {:error, [sponsored_id: :invalid_ed25519_public_key]} = BeginSponsoringFutureReserves.new(sponsored_id: "ABC") end diff --git a/test/tx_build/clawback_test.exs b/test/tx_build/clawback_test.exs index 7f3551fb..08bd5d61 100644 --- a/test/tx_build/clawback_test.exs +++ b/test/tx_build/clawback_test.exs @@ -33,7 +33,7 @@ defmodule Stellar.TxBuild.ClawbackTest do end test "new/2 with_invalid_from" do - {:error, [from: :invalid_account_id]} = + {:error, [from: :invalid_ed25519_public_key]} = Clawback.new( asset: :native, from: "ABC", diff --git a/test/tx_build/create_account_test.exs b/test/tx_build/create_account_test.exs index 91c314f7..72ad5998 100644 --- a/test/tx_build/create_account_test.exs +++ b/test/tx_build/create_account_test.exs @@ -29,7 +29,7 @@ defmodule Stellar.TxBuild.CreateAccountTest do end test "new/2 with_invalid_destination", %{amount: amount} do - {:error, [destination: :invalid_account_id]} = + {:error, [destination: :invalid_ed25519_public_key]} = CreateAccount.new(destination: "ABC", starting_balance: amount) end diff --git a/test/tx_build/path_payment_strict_receive_test.exs b/test/tx_build/path_payment_strict_receive_test.exs index 9da15d60..d97f2408 100644 --- a/test/tx_build/path_payment_strict_receive_test.exs +++ b/test/tx_build/path_payment_strict_receive_test.exs @@ -66,7 +66,7 @@ defmodule Stellar.TxBuild.PathPaymentStrictReceiveTest do end test "new/2 with_invalid_destination" do - {:error, [destination: :invalid_account_id]} = + {:error, [destination: :invalid_ed25519_public_key]} = PathPaymentStrictReceive.new( destination: "ABC", send_asset: :native, diff --git a/test/tx_build/path_payment_strict_send_test.exs b/test/tx_build/path_payment_strict_send_test.exs index 5e494dc8..83e9d4ec 100644 --- a/test/tx_build/path_payment_strict_send_test.exs +++ b/test/tx_build/path_payment_strict_send_test.exs @@ -66,7 +66,7 @@ defmodule Stellar.TxBuild.PathPaymentStrictSendTest do end test "new/2 with_invalid_destination" do - {:error, [destination: :invalid_account_id]} = + {:error, [destination: :invalid_ed25519_public_key]} = PathPaymentStrictSend.new( destination: "ABC", send_asset: :native, diff --git a/test/tx_build/payment_test.exs b/test/tx_build/payment_test.exs index 8f7a857f..7783cc25 100644 --- a/test/tx_build/payment_test.exs +++ b/test/tx_build/payment_test.exs @@ -33,7 +33,7 @@ defmodule Stellar.TxBuild.PaymentTest do end test "new/2 with_invalid_destination" do - {:error, [destination: :invalid_account_id]} = + {:error, [destination: :invalid_ed25519_public_key]} = Payment.new( destination: "ABC", asset: :native, diff --git a/test/tx_build/set_options_test.exs b/test/tx_build/set_options_test.exs index 3f5f1461..9c187e96 100644 --- a/test/tx_build/set_options_test.exs +++ b/test/tx_build/set_options_test.exs @@ -101,7 +101,7 @@ defmodule Stellar.TxBuild.SetOptionsTest do end test "new/2 with_invalid_destination" do - {:error, [inflation_destination: :invalid_account_id]} = + {:error, [inflation_destination: :invalid_ed25519_public_key]} = SetOptions.new(inflation_destination: "ABC", master_weight: 1) end diff --git a/test/tx_build/transaction_test.exs b/test/tx_build/transaction_test.exs index ad2caba1..dbaca22f 100644 --- a/test/tx_build/transaction_test.exs +++ b/test/tx_build/transaction_test.exs @@ -1,7 +1,7 @@ defmodule Stellar.TxBuild.TransactionTest do use ExUnit.Case - import Stellar.Test.XDRFixtures, only: [transaction_xdr: 1] + alias Stellar.Test.Fixtures.XDR, as: XDRFixtures alias Stellar.TxBuild.{ Account, @@ -14,17 +14,19 @@ defmodule Stellar.TxBuild.TransactionTest do } setup do - account_id = "GD726E62G6G4ANHWHIQTH5LNMFVF2EQSEXITB6DZCCTKVU6EQRRE2SJS" - source_account = Account.new(account_id) + account_id = "GBXV5U2D67J7HUW42JKBGD4WNZON4SOPXXDFTYQ7BCOG5VCARGCRMQQH" + muxed_address = "MBXV5U2D67J7HUW42JKBGD4WNZON4SOPXXDFTYQ7BCOG5VCARGCRMAAAAAAAAAAAARKPQ" %{ - source_account: source_account, + account_id: account_id, + muxed_address: muxed_address, + source_account: Account.new(account_id), + muxed_source_account: Account.new(muxed_address), sequence_number: SequenceNumber.new(), memo: Memo.new(), time_bounds: TimeBounds.new(), base_fee: BaseFee.new(), - operations: Operations.new(), - xdr: transaction_xdr(account_id) + operations: Operations.new() } end @@ -163,15 +165,41 @@ defmodule Stellar.TxBuild.TransactionTest do ) end + test "new/2 with_muxed_source_account", %{ + muxed_source_account: muxed_source_account, + sequence_number: sequence_number, + base_fee: base_fee, + memo: memo, + time_bounds: time_bounds, + operations: operations + } do + %Transaction{ + source_account: ^muxed_source_account, + sequence_number: ^sequence_number, + time_bounds: ^time_bounds, + base_fee: ^base_fee, + memo: ^memo + } = + Transaction.new( + source_account: muxed_source_account, + sequence_number: sequence_number, + base_fee: base_fee, + time_bounds: time_bounds, + memo: memo, + operations: operations + ) + end + test "to_xdr/1", %{ + account_id: account_id, source_account: source_account, base_fee: base_fee, memo: memo, time_bounds: time_bounds, - operations: operations, - xdr: xdr + operations: operations } do sequence_number = SequenceNumber.new(4_130_487_228_432_385) + xdr = XDRFixtures.transaction(account_id) ^xdr = Transaction.new( @@ -184,4 +212,27 @@ defmodule Stellar.TxBuild.TransactionTest do ) |> Transaction.to_xdr() end + + test "to_xdr/1 with_muxed_source_account", %{ + muxed_address: muxed_address, + muxed_source_account: muxed_source_account, + base_fee: base_fee, + memo: memo, + time_bounds: time_bounds, + operations: operations + } do + sequence_number = SequenceNumber.new(4_130_487_228_432_385) + xdr = XDRFixtures.transaction_with_muxed_account(muxed_address) + + ^xdr = + Transaction.new( + source_account: muxed_source_account, + sequence_number: sequence_number, + base_fee: base_fee, + time_bounds: time_bounds, + memo: memo, + operations: operations + ) + |> Transaction.to_xdr() + end end diff --git a/test/tx_build/validations_test.exs b/test/tx_build/validations_test.exs index e85e0d46..49d6a147 100644 --- a/test/tx_build/validations_test.exs +++ b/test/tx_build/validations_test.exs @@ -33,7 +33,7 @@ defmodule Stellar.TxBuild.ValidationsTest do end test "validate_account_id/1 error" do - {:error, [destination: :invalid_account_id]} = + {:error, [destination: :invalid_ed25519_public_key]} = Validations.validate_account_id({:destination, "ABC"}) end @@ -48,7 +48,7 @@ defmodule Stellar.TxBuild.ValidationsTest do end test "validate_optional_account_id/1 error" do - {:error, [destination: :invalid_account_id]} = + {:error, [destination: :invalid_ed25519_public_key]} = Validations.validate_optional_account_id({:destination, "2"}) end @@ -57,7 +57,7 @@ defmodule Stellar.TxBuild.ValidationsTest do end test "validate_account/1 error" do - {:error, [destination: :invalid_account_id]} = + {:error, [destination: :invalid_ed25519_public_key]} = Validations.validate_account_id({:destination, "ABC"}) end