From a01aa637bd4d4085f2bd7e47533e0850871cb953 Mon Sep 17 00:00:00 2001 From: Neylix Date: Wed, 20 Sep 2023 11:33:44 +0200 Subject: [PATCH 1/8] Add Chain.get_previous_address --- .../interpreter/library/common/chain.ex | 5 +++ .../interpreter/library/common/chain_impl.ex | 38 ++++++++++++++++--- .../interpreter/library/common/chain_test.exs | 35 +++++++++++++++++ 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/lib/archethic/contracts/interpreter/library/common/chain.ex b/lib/archethic/contracts/interpreter/library/common/chain.ex index 98c564fd2..815bbb7b6 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain.ex @@ -14,6 +14,7 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do @callback get_genesis_public_key(binary()) :: Crypto.key() | nil @callback get_transaction(binary()) :: map() @callback get_burn_address() :: Crypto.prepended_hash() + @callback get_previous_address(Crypto.key() | map()) :: Crypto.prepended_hash() @spec check_types(atom(), list()) :: boolean() def check_types(:get_genesis_address, [first]) do @@ -34,5 +35,9 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do def check_types(:get_burn_address, []), do: true + def check_types(:get_previous_address, [first]) do + AST.is_map?(first) || AST.is_binary?(first) || AST.is_variable_or_function_call?(first) + end + def check_types(_, _), do: false end diff --git a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex index 4e07b66eb..7c43cdc88 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex @@ -2,10 +2,15 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do @moduledoc false alias Archethic.Contracts.Interpreter.Legacy + alias Archethic.Contracts.Interpreter.Library alias Archethic.Contracts.Interpreter.Library.Common.Chain alias Archethic.Contracts.Interpreter.Legacy.UtilsInterpreter alias Archethic.Contracts.ContractConstants, as: Constants + + alias Archethic.Crypto + alias Archethic.Tag + alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations @behaviour Chain @@ -44,14 +49,37 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do |> UtilsInterpreter.get_address("Chain.get_transaction/1") |> Archethic.search_transaction() |> then(fn - {:ok, tx} -> - Constants.from_transaction(tx) - - {:error, _} -> - nil + {:ok, tx} -> Constants.from_transaction(tx) + {:error, _} -> nil end) end @impl Chain def get_burn_address(), do: LedgerOperations.burning_address() |> Base.encode16() + + @impl Chain + def get_previous_address(previous_public_key) when is_binary(previous_public_key), + do: previous_address(previous_public_key) + + def get_previous_address(%{"previous_public_key" => previous_public_key}), + do: previous_address(previous_public_key) + + def get_previous_address(arg), + do: + raise(Library.Error, + message: + "Invalid arg for Chain.get_previous_address(), expected string or map, got #{inspect(arg)}" + ) + + defp previous_address(previous_public_key) do + with {:ok, pub_key} <- Base.decode16(previous_public_key), + true <- Crypto.valid_public_key?(pub_key) do + pub_key |> Crypto.derive_address() |> Base.encode16() + else + _ -> + raise Library.Error, + message: + "Invalid previous public key in Chain.get_previous_address(), got #{inspect(previous_public_key)}" + end + end end diff --git a/test/archethic/contracts/interpreter/library/common/chain_test.exs b/test/archethic/contracts/interpreter/library/common/chain_test.exs index a6dbfafd1..b4098f554 100644 --- a/test/archethic/contracts/interpreter/library/common/chain_test.exs +++ b/test/archethic/contracts/interpreter/library/common/chain_test.exs @@ -7,6 +7,8 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainTest do use ArchethicCase import ArchethicCase + alias Archethic.Contracts.ContractConstants + alias Archethic.Contracts.Interpreter.Library alias Archethic.Contracts.Interpreter.Library.Common.Chain alias Archethic.Crypto @@ -213,4 +215,37 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainTest do assert <<0::16, 0::256>> |> Base.encode16() == Chain.get_burn_address() end end + + describe "get_previous_address/1" do + test "should return previous address from a previous public key" do + seed = :crypto.strong_rand_bytes(32) + {pub, _priv} = Crypto.derive_keypair(seed, 0) + + assert pub |> Crypto.derive_address() |> Base.encode16() == + pub |> Base.encode16() |> Chain.get_previous_address() + end + + test "should return previous address from a transaction constant" do + seed = :crypto.strong_rand_bytes(32) + previous_address = Crypto.derive_keypair(seed, 0) |> elem(0) |> Crypto.derive_address() + + transaction_constant = + TransactionFactory.create_non_valided_transaction(seed: seed, index: 0) + |> ContractConstants.from_transaction() + + assert Base.encode16(previous_address) == Chain.get_previous_address(transaction_constant) + end + + test "should raise an error if arg is not string or map" do + assert_raise(Library.Error, fn -> Chain.get_previous_address(12) end) + end + + test "should raise an error if public key is invalid" do + assert_raise(Library.Error, fn -> Chain.get_previous_address("invalid") end) + + assert_raise(Library.Error, fn -> + :crypto.strong_rand_bytes(32) |> Base.encode16() |> Chain.get_previous_address() + end) + end + end end From e01c85a2b1f181907cca603309a95f8f699e3020 Mon Sep 17 00:00:00 2001 From: Neylix Date: Wed, 20 Sep 2023 16:30:23 +0200 Subject: [PATCH 2/8] Add Chain.get_balance --- .../interpreter/library/common/chain.ex | 5 ++ .../interpreter/library/common/chain_impl.ex | 24 ++++++++ .../interpreter/library/common/chain_test.exs | 61 +++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/lib/archethic/contracts/interpreter/library/common/chain.ex b/lib/archethic/contracts/interpreter/library/common/chain.ex index 815bbb7b6..4c7e18eb9 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain.ex @@ -15,6 +15,7 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do @callback get_transaction(binary()) :: map() @callback get_burn_address() :: Crypto.prepended_hash() @callback get_previous_address(Crypto.key() | map()) :: Crypto.prepended_hash() + @callback get_balance(Crypto.prepended_hash()) :: map() @spec check_types(atom(), list()) :: boolean() def check_types(:get_genesis_address, [first]) do @@ -39,5 +40,9 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do AST.is_map?(first) || AST.is_binary?(first) || AST.is_variable_or_function_call?(first) end + def check_types(:get_balance, [first]) do + AST.is_binary?(first) || AST.is_variable_or_function_call?(first) + end + def check_types(_, _), do: false end diff --git a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex index 7c43cdc88..cf55a6ff3 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex @@ -13,6 +13,8 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations + alias Archethic.Utils + @behaviour Chain use Tag @@ -82,4 +84,26 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do "Invalid previous public key in Chain.get_previous_address(), got #{inspect(previous_public_key)}" end end + + @impl Chain + @tag [:io] + def get_balance(address_hex) do + with {:ok, address} <- Base.decode16(address_hex), + true <- Crypto.valid_address?(address), + {:ok, last_address} <- Archethic.get_last_transaction_address(address), + {:ok, %{uco: uco_amount, token: tokens}} <- Archethic.get_balance(last_address) do + tokens = + Enum.reduce(tokens, %{}, fn {{token_address, token_id}, amount}, acc -> + key = %{"token_address" => Base.encode16(token_address), "token_id" => token_id} + Map.put(acc, key, Utils.from_bigint(amount)) + end) + + %{"uco" => Utils.from_bigint(uco_amount), "tokens" => tokens} + else + _ -> + # Can only catch _ otherwise dialyzer is not happy + raise Library.Error, + message: "Invalid address in Chain.get_balance(), got #{inspect(address_hex)}" + end + end end diff --git a/test/archethic/contracts/interpreter/library/common/chain_test.exs b/test/archethic/contracts/interpreter/library/common/chain_test.exs index b4098f554..9039195d2 100644 --- a/test/archethic/contracts/interpreter/library/common/chain_test.exs +++ b/test/archethic/contracts/interpreter/library/common/chain_test.exs @@ -14,13 +14,17 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainTest do alias Archethic.Crypto alias Archethic.P2P + alias Archethic.P2P.Message.Balance alias Archethic.P2P.Message.FirstPublicKey alias Archethic.P2P.Message.FirstTransactionAddress + alias Archethic.P2P.Message.GetBalance alias Archethic.P2P.Message.GetFirstPublicKey alias Archethic.P2P.Message.GetFirstTransactionAddress + alias Archethic.P2P.Message.GetLastTransactionAddress alias Archethic.P2P.Message.GenesisAddress alias Archethic.P2P.Message.GetGenesisAddress alias Archethic.P2P.Message.GetTransaction + alias Archethic.P2P.Message.LastTransactionAddress alias Archethic.P2P.Message.NotFound alias Archethic.P2P.Node @@ -29,6 +33,8 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainTest do alias Archethic.TransactionFactory + alias Archethic.Utils + import Mox doctest Chain @@ -248,4 +254,59 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainTest do end) end end + + describe "get_balance/1" do + test "should return the balance of the last address of the target chain" do + address = random_address() + last_address = random_address() + + fungible_token_address = random_address() + fungible_token_address_hex = Base.encode16(fungible_token_address) + non_fungible_token_address = random_address() + non_fungible_token_address_hex = Base.encode16(non_fungible_token_address) + + balance = %Balance{ + uco: Utils.to_bigint(14.35), + token: %{ + {fungible_token_address, 0} => Utils.to_bigint(134.489), + {non_fungible_token_address, 2} => Utils.to_bigint(1), + {non_fungible_token_address, 6} => Utils.to_bigint(1) + } + } + + MockClient + |> expect(:send_message, fn _, %GetLastTransactionAddress{address: ^address}, _ -> + {:ok, %LastTransactionAddress{address: last_address}} + end) + |> expect(:send_message, fn _, %GetBalance{address: ^last_address}, _ -> {:ok, balance} end) + + assert %{ + "uco" => 14.35, + "tokens" => %{ + %{"token_address" => fungible_token_address_hex, "token_id" => 0} => 134.489, + %{"token_address" => non_fungible_token_address_hex, "token_id" => 2} => 1, + %{"token_address" => non_fungible_token_address_hex, "token_id" => 6} => 1 + } + } == address |> Base.encode16() |> Chain.get_balance() + end + + test "should raise an error if address is invalid" do + assert_raise(Library.Error, fn -> Chain.get_balance("invalid") end) + + assert_raise(Library.Error, fn -> + :crypto.strong_rand_bytes(32) |> Base.encode16() |> Chain.get_balance() + end) + end + + test "should raise an error on network issue" do + MockClient + |> expect(:send_message, fn _, %GetLastTransactionAddress{}, _ -> + {:error, :network_issue} + end) + + assert_raise(Library.Error, fn -> + random_address() |> Base.encode16() |> Chain.get_balance() + end) + end + end end From 722c555294c486aa2f350b497b11100ad936b5bb Mon Sep 17 00:00:00 2001 From: Neylix Date: Wed, 20 Sep 2023 18:37:34 +0200 Subject: [PATCH 3/8] Add Chain.get_uco_balance --- .../interpreter/library/common/chain.ex | 21 +++++--- .../interpreter/library/common/chain_impl.ex | 49 ++++++++++++++----- .../interpreter/library/common/chain_test.exs | 17 +++++++ 3 files changed, 69 insertions(+), 18 deletions(-) diff --git a/lib/archethic/contracts/interpreter/library/common/chain.ex b/lib/archethic/contracts/interpreter/library/common/chain.ex index 4c7e18eb9..9dcdb8093 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain.ex @@ -16,33 +16,42 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do @callback get_burn_address() :: Crypto.prepended_hash() @callback get_previous_address(Crypto.key() | map()) :: Crypto.prepended_hash() @callback get_balance(Crypto.prepended_hash()) :: map() + @callback get_uco_balance(Crypto.prepended_hash()) :: float() @spec check_types(atom(), list()) :: boolean() def check_types(:get_genesis_address, [first]) do - AST.is_binary?(first) || AST.is_variable_or_function_call?(first) + binary_or_variable_or_function?(first) end def check_types(:get_first_transaction_address, [first]) do - AST.is_binary?(first) || AST.is_variable_or_function_call?(first) + binary_or_variable_or_function?(first) end def check_types(:get_genesis_public_key, [first]) do - AST.is_binary?(first) || AST.is_variable_or_function_call?(first) + binary_or_variable_or_function?(first) end def check_types(:get_transaction, [first]) do - AST.is_binary?(first) || AST.is_variable_or_function_call?(first) + binary_or_variable_or_function?(first) end def check_types(:get_burn_address, []), do: true def check_types(:get_previous_address, [first]) do - AST.is_map?(first) || AST.is_binary?(first) || AST.is_variable_or_function_call?(first) + AST.is_map?(first) || binary_or_variable_or_function?(first) end def check_types(:get_balance, [first]) do - AST.is_binary?(first) || AST.is_variable_or_function_call?(first) + binary_or_variable_or_function?(first) + end + + def check_types(:get_uco_balance, [first]) do + binary_or_variable_or_function?(first) end def check_types(_, _), do: false + + defp binary_or_variable_or_function?(arg) do + AST.is_binary?(arg) || AST.is_variable_or_function_call?(arg) + end end diff --git a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex index cf55a6ff3..efa2bcd86 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex @@ -88,22 +88,47 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do @impl Chain @tag [:io] def get_balance(address_hex) do + function = "Chain.get_balance" + + %{uco: uco_amount, token: tokens} = + address_hex |> get_binary_address(function) |> fetch_balance(function) + + tokens = + Enum.reduce(tokens, %{}, fn {{token_address, token_id}, amount}, acc -> + key = %{"token_address" => Base.encode16(token_address), "token_id" => token_id} + Map.put(acc, key, Utils.from_bigint(amount)) + end) + + %{"uco" => Utils.from_bigint(uco_amount), "tokens" => tokens} + end + + @impl Chain + @tag [:io] + def get_uco_balance(address_hex) do + function = "Chain.get_balance" + %{uco: uco_amount} = address_hex |> get_binary_address(function) |> fetch_balance(function) + + Utils.from_bigint(uco_amount) + end + + defp get_binary_address(address_hex, function) do with {:ok, address} <- Base.decode16(address_hex), - true <- Crypto.valid_address?(address), - {:ok, last_address} <- Archethic.get_last_transaction_address(address), - {:ok, %{uco: uco_amount, token: tokens}} <- Archethic.get_balance(last_address) do - tokens = - Enum.reduce(tokens, %{}, fn {{token_address, token_id}, amount}, acc -> - key = %{"token_address" => Base.encode16(token_address), "token_id" => token_id} - Map.put(acc, key, Utils.from_bigint(amount)) - end) - - %{"uco" => Utils.from_bigint(uco_amount), "tokens" => tokens} + true <- Crypto.valid_address?(address) do + address else _ -> - # Can only catch _ otherwise dialyzer is not happy raise Library.Error, - message: "Invalid address in Chain.get_balance(), got #{inspect(address_hex)}" + message: "Invalid address in #{function}, got #{inspect(address_hex)}" + end + end + + defp fetch_balance(address, function) do + with {:ok, last_address} <- Archethic.get_last_transaction_address(address), + {:ok, balance} <- Archethic.get_balance(last_address) do + balance + else + _ -> + raise Library.Error, message: "Network issue in #{function}" end end end diff --git a/test/archethic/contracts/interpreter/library/common/chain_test.exs b/test/archethic/contracts/interpreter/library/common/chain_test.exs index 9039195d2..04c286524 100644 --- a/test/archethic/contracts/interpreter/library/common/chain_test.exs +++ b/test/archethic/contracts/interpreter/library/common/chain_test.exs @@ -309,4 +309,21 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainTest do end) end end + + describe "get_uco_balance/1" do + test "should return uco balance" do + address = random_address() + last_address = random_address() + + balance = %Balance{uco: Utils.to_bigint(14.35), token: %{}} + + MockClient + |> expect(:send_message, fn _, %GetLastTransactionAddress{address: ^address}, _ -> + {:ok, %LastTransactionAddress{address: last_address}} + end) + |> expect(:send_message, fn _, %GetBalance{address: ^last_address}, _ -> {:ok, balance} end) + + assert 14.35 == address |> Base.encode16() |> Chain.get_uco_balance() + end + end end From 421017e1cc825d14d1855a9518bd529d7d69a1d8 Mon Sep 17 00:00:00 2001 From: Neylix Date: Wed, 20 Sep 2023 18:55:27 +0200 Subject: [PATCH 4/8] Add Chain.get_token_balance --- .../interpreter/library/common/chain.ex | 15 +++++++ .../interpreter/library/common/chain_impl.ex | 12 ++++++ .../interpreter/library/common/chain_test.exs | 39 +++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/lib/archethic/contracts/interpreter/library/common/chain.ex b/lib/archethic/contracts/interpreter/library/common/chain.ex index 9dcdb8093..c21dc090a 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain.ex @@ -17,6 +17,9 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do @callback get_previous_address(Crypto.key() | map()) :: Crypto.prepended_hash() @callback get_balance(Crypto.prepended_hash()) :: map() @callback get_uco_balance(Crypto.prepended_hash()) :: float() + @callback get_token_balance(Crypto.prepended_hash(), Crypto.prepended_hash()) :: float() + @callback get_token_balance(Crypto.prepended_hash(), Crypto.prepended_hash(), non_neg_integer()) :: + float() @spec check_types(atom(), list()) :: boolean() def check_types(:get_genesis_address, [first]) do @@ -49,9 +52,21 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do binary_or_variable_or_function?(first) end + def check_types(:get_token_balance, [first, second]) do + binary_or_variable_or_function?(first) && binary_or_variable_or_function?(second) + end + + def check_types(:get_token_balance, [first, second, third]) do + check_types(:get_token_balance, [first, second]) && number_or_variable_or_function?(third) + end + def check_types(_, _), do: false defp binary_or_variable_or_function?(arg) do AST.is_binary?(arg) || AST.is_variable_or_function_call?(arg) end + + defp number_or_variable_or_function?(arg) do + AST.is_number?(arg) || AST.is_variable_or_function_call?(arg) + end end diff --git a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex index efa2bcd86..8cec2afec 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex @@ -111,6 +111,18 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do Utils.from_bigint(uco_amount) end + @impl Chain + @tag [:io] + def get_token_balance(address_hex, token_address_hex, token_id \\ 0) + + def get_token_balance(address_hex, token_address_hex, token_id) do + function = "Chain.get_token_balance" + token_address = get_binary_address(token_address_hex, function) + %{token: tokens} = address_hex |> get_binary_address(function) |> fetch_balance(function) + + tokens |> Map.get({token_address, token_id}, 0) |> Utils.from_bigint() + end + defp get_binary_address(address_hex, function) do with {:ok, address} <- Base.decode16(address_hex), true <- Crypto.valid_address?(address) do diff --git a/test/archethic/contracts/interpreter/library/common/chain_test.exs b/test/archethic/contracts/interpreter/library/common/chain_test.exs index 04c286524..78399d533 100644 --- a/test/archethic/contracts/interpreter/library/common/chain_test.exs +++ b/test/archethic/contracts/interpreter/library/common/chain_test.exs @@ -326,4 +326,43 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainTest do assert 14.35 == address |> Base.encode16() |> Chain.get_uco_balance() end end + + describe "get_token_balance/3" do + test "should return token balance" do + address = random_address() + address_hex = Base.encode16(address) + last_address = random_address() + + fungible_token_address = random_address() + fungible_token_address_hex = Base.encode16(fungible_token_address) + non_fungible_token_address = random_address() + non_fungible_token_address_hex = Base.encode16(non_fungible_token_address) + + balance = %Balance{ + uco: Utils.to_bigint(14.35), + token: %{ + {fungible_token_address, 0} => Utils.to_bigint(134.489), + {non_fungible_token_address, 2} => Utils.to_bigint(1), + {non_fungible_token_address, 6} => Utils.to_bigint(1) + } + } + + MockClient + |> stub(:send_message, fn + _, %GetLastTransactionAddress{address: ^address}, _ -> + {:ok, %LastTransactionAddress{address: last_address}} + + _, %GetBalance{address: ^last_address}, _ -> + {:ok, balance} + end) + + assert 134.489 == Chain.get_token_balance(address_hex, fungible_token_address_hex) + assert 1 == Chain.get_token_balance(address_hex, non_fungible_token_address_hex, 2) + assert 1 == Chain.get_token_balance(address_hex, non_fungible_token_address_hex, 6) + + assert 0 == Chain.get_token_balance(address_hex, Base.encode16(random_address())) + assert 0 == Chain.get_token_balance(address_hex, fungible_token_address_hex, 1) + assert 0 == Chain.get_token_balance(address_hex, non_fungible_token_address_hex, 3) + end + end end From 65e3336cd120012b1a1f9b532364f6a70d2e2f47 Mon Sep 17 00:00:00 2001 From: Neylix Date: Thu, 21 Sep 2023 09:34:58 +0200 Subject: [PATCH 5/8] update types to match reality --- .../interpreter/library/common/chain.ex | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/archethic/contracts/interpreter/library/common/chain.ex b/lib/archethic/contracts/interpreter/library/common/chain.ex index c21dc090a..2c6023c35 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain.ex @@ -2,24 +2,21 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do @moduledoc false @behaviour Archethic.Contracts.Interpreter.Library - alias Archethic.Crypto - alias Archethic.Contracts.Interpreter.ASTHelper, as: AST alias Archethic.Contracts.Interpreter.Library.Common.ChainImpl use Knigge, otp_app: :archethic, default: ChainImpl, delegate_at_runtime?: true - @callback get_genesis_address(binary()) :: Crypto.prepended_hash() - @callback get_first_transaction_address(binary()) :: Crypto.prepended_hash() | nil - @callback get_genesis_public_key(binary()) :: Crypto.key() | nil + @callback get_genesis_address(binary()) :: binary() + @callback get_first_transaction_address(binary()) :: binary() | nil + @callback get_genesis_public_key(binary()) :: binary() | nil @callback get_transaction(binary()) :: map() - @callback get_burn_address() :: Crypto.prepended_hash() - @callback get_previous_address(Crypto.key() | map()) :: Crypto.prepended_hash() - @callback get_balance(Crypto.prepended_hash()) :: map() - @callback get_uco_balance(Crypto.prepended_hash()) :: float() - @callback get_token_balance(Crypto.prepended_hash(), Crypto.prepended_hash()) :: float() - @callback get_token_balance(Crypto.prepended_hash(), Crypto.prepended_hash(), non_neg_integer()) :: - float() + @callback get_burn_address() :: binary() + @callback get_previous_address(binary() | map()) :: binary() + @callback get_balance(binary()) :: map() + @callback get_uco_balance(binary()) :: float() + @callback get_token_balance(binary(), binary()) :: float() + @callback get_token_balance(binary(), binary(), non_neg_integer()) :: float() @spec check_types(atom(), list()) :: boolean() def check_types(:get_genesis_address, [first]) do From a854bbcf331cddd19561aa10e8f7de0aac5fcd07 Mon Sep 17 00:00:00 2001 From: Neylix Date: Thu, 21 Sep 2023 10:58:50 +0200 Subject: [PATCH 6/8] Add Chain.get_tokens_balance --- .../interpreter/library/common/chain.ex | 9 +++++ .../interpreter/library/common/chain_impl.ex | 24 ++++++++++++ .../interpreter/library/common/chain_test.exs | 37 +++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/lib/archethic/contracts/interpreter/library/common/chain.ex b/lib/archethic/contracts/interpreter/library/common/chain.ex index 2c6023c35..8ebe5d081 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain.ex @@ -17,6 +17,7 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do @callback get_uco_balance(binary()) :: float() @callback get_token_balance(binary(), binary()) :: float() @callback get_token_balance(binary(), binary(), non_neg_integer()) :: float() + @callback get_tokens_balance(binary(), list()) :: list() @spec check_types(atom(), list()) :: boolean() def check_types(:get_genesis_address, [first]) do @@ -57,6 +58,10 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do check_types(:get_token_balance, [first, second]) && number_or_variable_or_function?(third) end + def check_types(:get_tokens_balance, [first, second]) do + binary_or_variable_or_function?(first) && list_or_variable_or_function?(second) + end + def check_types(_, _), do: false defp binary_or_variable_or_function?(arg) do @@ -66,4 +71,8 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do defp number_or_variable_or_function?(arg) do AST.is_number?(arg) || AST.is_variable_or_function_call?(arg) end + + defp list_or_variable_or_function?(arg) do + AST.is_list?(arg) || AST.is_variable_or_function_call?(arg) + end end diff --git a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex index 8cec2afec..c7cb84150 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex @@ -123,6 +123,30 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do tokens |> Map.get({token_address, token_id}, 0) |> Utils.from_bigint() end + @impl Chain + @tag [:io] + def get_tokens_balance(address_hex, requested_tokens) do + function = "Chain.get_tokens_balance" + + requested_tokens = + Enum.map( + requested_tokens, + fn %{"token_address" => token_address_hex, "token_id" => token_id} -> + {get_binary_address(token_address_hex, function), token_id} + end + ) + + %{token: tokens} = address_hex |> get_binary_address(function) |> fetch_balance(function) + + tokens + |> Map.take(requested_tokens) + |> Enum.map(fn {{token_address, token_id}, amount} -> + key_map = %{"token_address" => Base.encode16(token_address), "token_id" => token_id} + {key_map, Utils.from_bigint(amount)} + end) + |> Enum.into(%{}) + end + defp get_binary_address(address_hex, function) do with {:ok, address} <- Base.decode16(address_hex), true <- Crypto.valid_address?(address) do diff --git a/test/archethic/contracts/interpreter/library/common/chain_test.exs b/test/archethic/contracts/interpreter/library/common/chain_test.exs index 78399d533..cb98650ac 100644 --- a/test/archethic/contracts/interpreter/library/common/chain_test.exs +++ b/test/archethic/contracts/interpreter/library/common/chain_test.exs @@ -365,4 +365,41 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainTest do assert 0 == Chain.get_token_balance(address_hex, non_fungible_token_address_hex, 3) end end + + describe "get_tokens_balance/2" do + test "should return token balance" do + address = random_address() + address_hex = Base.encode16(address) + last_address = random_address() + + fungible_token_address = random_address() + fungible_token_address_hex = Base.encode16(fungible_token_address) + non_fungible_token_address = random_address() + non_fungible_token_address_hex = Base.encode16(non_fungible_token_address) + + balance = %Balance{ + uco: Utils.to_bigint(14.35), + token: %{ + {fungible_token_address, 0} => Utils.to_bigint(134.489), + {non_fungible_token_address, 2} => Utils.to_bigint(1), + {non_fungible_token_address, 6} => Utils.to_bigint(1) + } + } + + MockClient + |> stub(:send_message, fn + _, %GetLastTransactionAddress{address: ^address}, _ -> + {:ok, %LastTransactionAddress{address: last_address}} + + _, %GetBalance{address: ^last_address}, _ -> + {:ok, balance} + end) + + token_key1 = %{"token_address" => fungible_token_address_hex, "token_id" => 0} + token_key2 = %{"token_address" => non_fungible_token_address_hex, "token_id" => 6} + + assert %{token_key1 => 134.489, token_key2 => 1} == + Chain.get_tokens_balance(address_hex, [token_key1, token_key2]) + end + end end From 78d837589f4cc165883e5ccf2173058f47685924 Mon Sep 17 00:00:00 2001 From: Neylix Date: Tue, 26 Sep 2023 17:01:21 +0200 Subject: [PATCH 7/8] Chain.get_tokens_balance return also 0 balance --- .../interpreter/library/common/chain_impl.ex | 24 +++++++------------ .../interpreter/library/common/chain_test.exs | 5 ++-- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex index c7cb84150..182048b29 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex @@ -128,23 +128,17 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do def get_tokens_balance(address_hex, requested_tokens) do function = "Chain.get_tokens_balance" - requested_tokens = - Enum.map( - requested_tokens, - fn %{"token_address" => token_address_hex, "token_id" => token_id} -> - {get_binary_address(token_address_hex, function), token_id} - end - ) - %{token: tokens} = address_hex |> get_binary_address(function) |> fetch_balance(function) - tokens - |> Map.take(requested_tokens) - |> Enum.map(fn {{token_address, token_id}, amount} -> - key_map = %{"token_address" => Base.encode16(token_address), "token_id" => token_id} - {key_map, Utils.from_bigint(amount)} - end) - |> Enum.into(%{}) + Enum.reduce( + requested_tokens, + %{}, + fn token = %{"token_address" => token_address_hex, "token_id" => token_id}, acc -> + key = {get_binary_address(token_address_hex, function), token_id} + amount = Map.get(tokens, key, 0) |> Utils.from_bigint() + Map.put(acc, token, amount) + end + ) end defp get_binary_address(address_hex, function) do diff --git a/test/archethic/contracts/interpreter/library/common/chain_test.exs b/test/archethic/contracts/interpreter/library/common/chain_test.exs index cb98650ac..115adc000 100644 --- a/test/archethic/contracts/interpreter/library/common/chain_test.exs +++ b/test/archethic/contracts/interpreter/library/common/chain_test.exs @@ -397,9 +397,10 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainTest do token_key1 = %{"token_address" => fungible_token_address_hex, "token_id" => 0} token_key2 = %{"token_address" => non_fungible_token_address_hex, "token_id" => 6} + token_key3 = %{"token_address" => non_fungible_token_address_hex, "token_id" => 12} - assert %{token_key1 => 134.489, token_key2 => 1} == - Chain.get_tokens_balance(address_hex, [token_key1, token_key2]) + assert %{token_key1 => 134.489, token_key2 => 1, token_key3 => 0} == + Chain.get_tokens_balance(address_hex, [token_key1, token_key2, token_key3]) end end end From 37a4cd2a7ebcd2fae8cefc982d123b347324db12 Mon Sep 17 00:00:00 2001 From: Neylix Date: Thu, 28 Sep 2023 14:01:54 +0200 Subject: [PATCH 8/8] Add Chain.get_tokens_balance/1 --- .../interpreter/library/common/chain.ex | 7 +++- .../interpreter/library/common/chain_impl.ex | 13 ++++++++ .../interpreter/library/common/chain_test.exs | 33 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/archethic/contracts/interpreter/library/common/chain.ex b/lib/archethic/contracts/interpreter/library/common/chain.ex index 8ebe5d081..289216a92 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain.ex @@ -17,6 +17,7 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do @callback get_uco_balance(binary()) :: float() @callback get_token_balance(binary(), binary()) :: float() @callback get_token_balance(binary(), binary(), non_neg_integer()) :: float() + @callback get_tokens_balance(binary()) :: list() @callback get_tokens_balance(binary(), list()) :: list() @spec check_types(atom(), list()) :: boolean() @@ -58,8 +59,12 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.Chain do check_types(:get_token_balance, [first, second]) && number_or_variable_or_function?(third) end + def check_types(:get_tokens_balance, [first]) do + binary_or_variable_or_function?(first) + end + def check_types(:get_tokens_balance, [first, second]) do - binary_or_variable_or_function?(first) && list_or_variable_or_function?(second) + check_types(:get_tokens_balance, [first]) && list_or_variable_or_function?(second) end def check_types(_, _), do: false diff --git a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex index 182048b29..4bd75d4d5 100644 --- a/lib/archethic/contracts/interpreter/library/common/chain_impl.ex +++ b/lib/archethic/contracts/interpreter/library/common/chain_impl.ex @@ -123,6 +123,19 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainImpl do tokens |> Map.get({token_address, token_id}, 0) |> Utils.from_bigint() end + @impl Chain + @tag [:io] + def get_tokens_balance(address_hex) do + function = "Chain.get_tokens_balance" + + %{token: tokens} = address_hex |> get_binary_address(function) |> fetch_balance(function) + + Enum.reduce(tokens, %{}, fn {{token_address, token_id}, amount}, acc -> + key = %{"token_address" => Base.encode16(token_address), "token_id" => token_id} + Map.put(acc, key, Utils.from_bigint(amount)) + end) + end + @impl Chain @tag [:io] def get_tokens_balance(address_hex, requested_tokens) do diff --git a/test/archethic/contracts/interpreter/library/common/chain_test.exs b/test/archethic/contracts/interpreter/library/common/chain_test.exs index 115adc000..ef1d20872 100644 --- a/test/archethic/contracts/interpreter/library/common/chain_test.exs +++ b/test/archethic/contracts/interpreter/library/common/chain_test.exs @@ -366,6 +366,39 @@ defmodule Archethic.Contracts.Interpreter.Library.Common.ChainTest do end end + describe "get_tokens_balance/1" do + test "should return token balance" do + address = random_address() + last_address = random_address() + + fungible_token_address = random_address() + fungible_token_address_hex = Base.encode16(fungible_token_address) + non_fungible_token_address = random_address() + non_fungible_token_address_hex = Base.encode16(non_fungible_token_address) + + balance = %Balance{ + uco: Utils.to_bigint(14.35), + token: %{ + {fungible_token_address, 0} => Utils.to_bigint(134.489), + {non_fungible_token_address, 2} => Utils.to_bigint(1), + {non_fungible_token_address, 6} => Utils.to_bigint(1) + } + } + + MockClient + |> expect(:send_message, fn _, %GetLastTransactionAddress{address: ^address}, _ -> + {:ok, %LastTransactionAddress{address: last_address}} + end) + |> expect(:send_message, fn _, %GetBalance{address: ^last_address}, _ -> {:ok, balance} end) + + assert %{ + %{"token_address" => fungible_token_address_hex, "token_id" => 0} => 134.489, + %{"token_address" => non_fungible_token_address_hex, "token_id" => 2} => 1, + %{"token_address" => non_fungible_token_address_hex, "token_id" => 6} => 1 + } == address |> Base.encode16() |> Chain.get_tokens_balance() + end + end + describe "get_tokens_balance/2" do test "should return token balance" do address = random_address()