From e61e1fb5f890eb726618d4e264068c61508f829f Mon Sep 17 00:00:00 2001 From: ApoorvGupta Date: Mon, 20 Jun 2022 14:41:49 +0530 Subject: [PATCH 1/7] Add NFT id --- lib/archethic/account.ex | 6 +- .../account/mem_tables/nft_ledger.ex | 70 ++++---- lib/archethic/account/mem_tables_loader.ex | 6 +- lib/archethic/contracts/contract/constants.ex | 9 +- lib/archethic/contracts/interpreter.ex | 5 +- .../interpreter/transaction_statements.ex | 22 ++- lib/archethic/contracts/worker.ex | 14 +- lib/archethic/p2p/message.ex | 7 +- lib/archethic/p2p/message/balance.ex | 2 +- .../transaction_chain/transaction.ex | 6 +- .../transaction/data/ledger.ex | 15 +- .../transaction/data/ledger/nft.ex | 18 +- .../transaction/data/ledger/nft/transfer.ex | 80 +++++++-- .../validation_stamp/ledger_operations.ex | 161 +++++++++++++----- .../ledger_operations/transaction_movement.ex | 100 +++++++++-- .../transaction_movement/type.ex | 12 +- .../ledger_operations/unspent_output.ex | 124 ++++++++++++-- .../transaction_chain/transaction_input.ex | 5 +- lib/archethic/utils/regression/playbook.ex | 10 +- .../controllers/api/schema/nft_ledger.ex | 6 +- lib/archethic_web/graphql_schema/resolver.ex | 4 +- .../graphql_schema/transaction_type.ex | 5 + .../explorer/transaction_details.html.leex | 6 +- .../account/mem_tables/nft_ledger_test.exs | 1 + .../account/mem_tables_loader_test.exs | 11 +- test/archethic/account_test.exs | 7 +- .../transaction_statements_test.exs | 1 + test/archethic/contracts/interpreter_test.exs | 10 +- test/archethic/p2p/messages_test.exs | 5 +- .../ledger_operations/unspent_output_test.exs | 1 + .../transaction/validation_stamp_test.exs | 10 +- .../ledger/nft/transfer_test.exs | 1 + .../transaction_data/ledger/nft_test.exs | 9 +- .../transaction_data/ledger_test.exs | 1 + .../transaction_chain/transaction_test.exs | 1 + .../api/transaction_payload_test.exs | 12 +- test/archethic_web/graphql_schema_test.exs | 7 +- 37 files changed, 565 insertions(+), 205 deletions(-) diff --git a/lib/archethic/account.ex b/lib/archethic/account.ex index 6158dd8c0..bdc895b92 100644 --- a/lib/archethic/account.ex +++ b/lib/archethic/account.ex @@ -13,7 +13,7 @@ defmodule Archethic.Account do @type balance :: %{ uco: amount :: pos_integer(), - nft: %{(address :: binary()) => amount :: pos_integer()} + nft: %{{address :: binary(), nft_id :: non_neg_integer()} => amount :: pos_integer()} } @doc """ @@ -27,8 +27,8 @@ defmodule Archethic.Account do %UnspentOutput{type: :UCO, amount: amount}, acc -> Map.update!(acc, :uco, &(&1 + amount)) - %UnspentOutput{type: {:NFT, nft_address}, amount: amount}, acc -> - update_in(acc, [:nft, Access.key(nft_address, 0)], &(&1 + amount)) + %UnspentOutput{type: {:NFT, nft_address, nft_id}, amount: amount}, acc -> + update_in(acc, [:nft, Access.key({nft_address, nft_id}, 0)], &(&1 + amount)) end) end diff --git a/lib/archethic/account/mem_tables/nft_ledger.ex b/lib/archethic/account/mem_tables/nft_ledger.ex index d4d3725d6..71fe04723 100644 --- a/lib/archethic/account/mem_tables/nft_ledger.ex +++ b/lib/archethic/account/mem_tables/nft_ledger.ex @@ -13,8 +13,8 @@ defmodule Archethic.Account.MemTables.NFTLedger do @doc """ Initialize the NFT ledger tables: - - Main NFT ledger as ETS set ({nft, to, from}, amount, spent?) - - NFT Unspent Output Index as ETS bag (to, {from, nft}) + - Main NFT ledger as ETS set ({nft, to, from, nft_id}, amount, spent?) + - NFT Unspent Output Index as ETS bag (to, {from, nft, nft_id}) ## Examples @@ -51,17 +51,17 @@ defmodule Archethic.Account.MemTables.NFTLedger do ## Examples iex> {:ok, _pid} = NFTLedger.start_link() - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1"}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1"}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}}, ~U[2021-03-05 13:41:34Z]) iex> { :ets.tab2list(:archethic_nft_ledger), :ets.tab2list(:archethic_nft_unspent_output_index) } { [ - {{"@Alice2", "@Bob3", "@NFT1"}, 300_000_000, false, ~U[2021-03-05 13:41:34Z]}, - {{"@Alice2", "@Charlie10", "@NFT1"}, 100_000_000, false, ~U[2021-03-05 13:41:34Z]} + {{"@Alice2", "@Charlie10", "@NFT1", 1}, 100_000_000, false, ~U[2021-03-05 13:41:34Z]}, + {{"@Alice2", "@Bob3", "@NFT1", 0}, 300_000_000, false, ~U[2021-03-05 13:41:34Z]} ], [ - {"@Alice2", "@Bob3", "@NFT1"}, - {"@Alice2", "@Charlie10", "@NFT1"} + {"@Alice2", "@Bob3", "@NFT1", 0}, + {"@Alice2", "@Charlie10", "@NFT1", 1} ] } @@ -72,19 +72,20 @@ defmodule Archethic.Account.MemTables.NFTLedger do %UnspentOutput{ from: from_address, amount: amount, - type: {:NFT, nft_address} + type: {:NFT, nft_address, nft_id} }, timestamp = %DateTime{} ) when is_binary(to_address) and is_binary(from_address) and is_integer(amount) and amount > 0 and - is_binary(nft_address) do + is_binary(nft_address) and is_integer(nft_id) and nft_id >= 0 do true = :ets.insert( @ledger_table, - {{to_address, from_address, nft_address}, amount, false, timestamp} + {{to_address, from_address, nft_address, nft_id}, amount, false, timestamp} ) - true = :ets.insert(@unspent_output_index_table, {to_address, from_address, nft_address}) + true = + :ets.insert(@unspent_output_index_table, {to_address, from_address, nft_address, nft_id}) Logger.info( "#{amount} unspent NFT (#{Base.encode16(nft_address)}) added for #{Base.encode16(to_address)}", @@ -100,12 +101,12 @@ defmodule Archethic.Account.MemTables.NFTLedger do ## Examples iex> {:ok, _pid} = NFTLedger.start_link() - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1"}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1"}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}}, ~U[2021-03-05 13:41:34Z]) iex> NFTLedger.get_unspent_outputs("@Alice2") [ - %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1"}}, - %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1"}}, + %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}}, + %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}} ] iex> {:ok, _pid} = NFTLedger.start_link() @@ -116,14 +117,14 @@ defmodule Archethic.Account.MemTables.NFTLedger do def get_unspent_outputs(address) when is_binary(address) do @unspent_output_index_table |> :ets.lookup(address) - |> Enum.reduce([], fn {_, from, nft_address}, acc -> - case :ets.lookup(@ledger_table, {address, from, nft_address}) do + |> Enum.reduce([], fn {_, from, nft_address, nft_id}, acc -> + case :ets.lookup(@ledger_table, {address, from, nft_address, nft_id}) do [{_, amount, false, _}] -> [ %UnspentOutput{ from: from, amount: amount, - type: {:NFT, nft_address} + type: {:NFT, nft_address, nft_id} } | acc ] @@ -140,8 +141,8 @@ defmodule Archethic.Account.MemTables.NFTLedger do ## Examples iex> {:ok, _pid} = NFTLedger.start_link() - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1"}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1"}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1",0}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1",1}}, ~U[2021-03-05 13:41:34Z]) iex> :ok = NFTLedger.spend_all_unspent_outputs("@Alice2") iex> NFTLedger.get_unspent_outputs("@Alice2") [] @@ -151,8 +152,8 @@ defmodule Archethic.Account.MemTables.NFTLedger do def spend_all_unspent_outputs(address) do @unspent_output_index_table |> :ets.lookup(address) - |> Enum.each(fn {_, from, nft_address} -> - :ets.update_element(@ledger_table, {address, from, nft_address}, {3, true}) + |> Enum.each(fn {_, from, nft_address, nft_id} -> + :ets.update_element(@ledger_table, {address, from, nft_address, nft_id}, {3, true}) end) :ok @@ -164,35 +165,36 @@ defmodule Archethic.Account.MemTables.NFTLedger do ## Examples iex> {:ok, _pid} = NFTLedger.start_link() - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1"}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1"}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}}, ~U[2021-03-05 13:41:34Z]) iex> NFTLedger.get_inputs("@Alice2") [ - %TransactionInput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1"}, spent?: false, timestamp: ~U[2021-03-05 13:41:34Z]}, - %TransactionInput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1"}, spent?: false, timestamp: ~U[2021-03-05 13:41:34Z]} + %TransactionInput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}, spent?: false, timestamp: ~U[2021-03-05 13:41:34Z]}, + %TransactionInput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}, spent?: false, timestamp: ~U[2021-03-05 13:41:34Z]} ] iex> {:ok, _pid} = NFTLedger.start_link() - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1"}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1"}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}}, ~U[2021-03-05 13:41:34Z]) iex> :ok = NFTLedger.spend_all_unspent_outputs("@Alice2") iex> NFTLedger.get_inputs("@Alice2") [ - %TransactionInput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1"}, spent?: true, timestamp: ~U[2021-03-05 13:41:34Z]}, - %TransactionInput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1"}, spent?: true, timestamp: ~U[2021-03-05 13:41:34Z]} + %TransactionInput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}, spent?: true, timestamp: ~U[2021-03-05 13:41:34Z]}, + %TransactionInput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}, spent?: true, timestamp: ~U[2021-03-05 13:41:34Z]} ] """ @spec get_inputs(binary()) :: list(TransactionInput.t()) def get_inputs(address) when is_binary(address) do @unspent_output_index_table |> :ets.lookup(address) - |> Enum.map(fn {_, from, nft_address} -> - [{_, amount, spent?, timestamp}] = :ets.lookup(@ledger_table, {address, from, nft_address}) + |> Enum.map(fn {_, from, nft_address, nft_id} -> + [{_, amount, spent?, timestamp}] = + :ets.lookup(@ledger_table, {address, from, nft_address, nft_id}) %TransactionInput{ from: from, amount: amount, - type: {:NFT, nft_address}, + type: {:NFT, nft_address, nft_id}, spent?: spent?, timestamp: timestamp } diff --git a/lib/archethic/account/mem_tables_loader.ex b/lib/archethic/account/mem_tables_loader.ex index d3e327e09..16d45fd66 100644 --- a/lib/archethic/account/mem_tables_loader.ex +++ b/lib/archethic/account/mem_tables_loader.ex @@ -95,13 +95,13 @@ defmodule Archethic.Account.MemTablesLoader do timestamp ) - %TransactionMovement{to: to, amount: amount, type: {:NFT, nft_address}} -> + %TransactionMovement{to: to, amount: amount, type: {:NFT, nft_address, nft_id}} -> NFTLedger.add_unspent_output( to, %UnspentOutput{ amount: amount, from: address, - type: {:NFT, nft_address} + type: {:NFT, nft_address, nft_id} }, timestamp ) @@ -115,7 +115,7 @@ defmodule Archethic.Account.MemTablesLoader do unspent_output = %UnspentOutput{type: :UCO} -> UCOLedger.add_unspent_output(address, unspent_output, timestamp) - unspent_output = %UnspentOutput{type: {:NFT, _nft_address}} -> + unspent_output = %UnspentOutput{type: {:NFT, _nft_address, _nft_id}} -> NFTLedger.add_unspent_output(address, unspent_output, timestamp) end) end diff --git a/lib/archethic/contracts/contract/constants.ex b/lib/archethic/contracts/contract/constants.ex index 13951b6c8..d40bac3a8 100644 --- a/lib/archethic/contracts/contract/constants.ex +++ b/lib/archethic/contracts/contract/constants.ex @@ -66,9 +66,10 @@ defmodule Archethic.Contracts.Contract.Constants do |> Enum.map(fn %NFTTransfer{ to: to, amount: amount, - nft: nft_address + nft: nft_address, + nft_id: nft_id } -> - %{"to" => to, "amount" => amount, "nft" => nft_address} + %{"to" => to, "amount" => amount, "nft" => nft_address, "nft_id" => nft_id} end) } end @@ -113,8 +114,8 @@ defmodule Archethic.Contracts.Contract.Constants do transfers: constants |> Map.get("nft_transfers", []) - |> Enum.map(fn %{"to" => to, "amount" => amount, "nft" => nft} -> - %NFTTransfer{to: to, amount: amount, nft: nft} + |> Enum.map(fn %{"to" => to, "amount" => amount, "nft" => nft, "nft_id" => nft_id} -> + %NFTTransfer{to: to, amount: amount, nft: nft, nft_id: nft_id} end) } } diff --git a/lib/archethic/contracts/interpreter.ex b/lib/archethic/contracts/interpreter.ex index e42e35a3b..c24416d32 100644 --- a/lib/archethic/contracts/interpreter.ex +++ b/lib/archethic/contracts/interpreter.ex @@ -696,7 +696,8 @@ defmodule Archethic.Contracts.Interpreter do node = [ {{:atom, "to"}, _to}, {{:atom, "amount"}, _amount}, - {{:atom, "nft"}, _nft_address} + {{:atom, "nft"}, _nft_address}, + {{:atom, "nft_id"}, _nft_id} ], acc = {:ok, %{scope: {:function, "add_nft_transfer", :actions}}} ) do @@ -707,7 +708,7 @@ defmodule Archethic.Contracts.Interpreter do node = {{:atom, arg}, _}, acc = {:ok, %{scope: {:function, "add_nft_transfer", :actions}}} ) - when arg in ["to", "amount", "nft"], + when arg in ["to", "amount", "nft", "nft_id"], do: {node, acc} # Whitelist the add_ownership argument list diff --git a/lib/archethic/contracts/interpreter/transaction_statements.ex b/lib/archethic/contracts/interpreter/transaction_statements.ex index de1e47614..d6d963d9d 100644 --- a/lib/archethic/contracts/interpreter/transaction_statements.ex +++ b/lib/archethic/contracts/interpreter/transaction_statements.ex @@ -59,7 +59,8 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do iex> TransactionStatements.add_nft_transfer(%Transaction{data: %TransactionData{}}, [ ...> {"to", "22368B50D3B2976787CFCC27508A8E8C67483219825F998FC9D6908D54D0FE10"}, ...> {"amount", 1_000_000_000}, - ...> {"nft", "70541604258A94B76DB1F1AF5A2FC2BEF165F3BD9C6B7DDB3F1ACC628465E528"} + ...> {"nft", "70541604258A94B76DB1F1AF5A2FC2BEF165F3BD9C6B7DDB3F1ACC628465E528"}, + ...> {"nft_id", 0} ...> ]) %Transaction{ data: %TransactionData{ @@ -71,7 +72,8 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do 103, 72, 50, 25, 130, 95, 153, 143, 201, 214, 144, 141, 84, 208, 254, 16>>, amount: 1_000_000_000, nft: <<112, 84, 22, 4, 37, 138, 148, 183, 109, 177, 241, 175, 90, 47, 194, 190, 241, 101, 243, - 189, 156, 107, 125, 219, 63, 26, 204, 98, 132, 101, 229, 40>> + 189, 156, 107, 125, 219, 63, 26, 204, 98, 132, 101, 229, 40>>, + nft_id: 0 } ] } @@ -81,12 +83,20 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do """ @spec add_nft_transfer(Transaction.t(), list()) :: Transaction.t() def add_nft_transfer(tx = %Transaction{}, args) when is_list(args) do - %{"to" => to, "amount" => amount, "nft" => nft} = Enum.into(args, %{}) + %{"to" => to, "amount" => amount, "nft" => nft, "nft_id" => nft_id} = Enum.into(args, %{}) update_in( tx, [Access.key(:data), Access.key(:ledger), Access.key(:nft), Access.key(:transfers)], - &[%NFTTransfer{to: decode_binary(to), amount: amount, nft: decode_binary(nft)} | &1] + &[ + %NFTTransfer{ + nft_id: nft_id, + to: decode_binary(to), + amount: amount, + nft: decode_binary(nft) + } + | &1 + ] ) end @@ -98,7 +108,7 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do iex> TransactionStatements.set_content(%Transaction{data: %TransactionData{}}, "hello") %Transaction{ data: %TransactionData{ - content: "hello" + content: "hello" } } """ @@ -116,7 +126,7 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do %Transaction{ data: %TransactionData{ code: "condition origin_family: biometric" - } + } } """ @spec set_code(Transaction.t(), binary()) :: Transaction.t() diff --git a/lib/archethic/contracts/worker.ex b/lib/archethic/contracts/worker.ex index 7cc4fc160..2c3be0054 100644 --- a/lib/archethic/contracts/worker.ex +++ b/lib/archethic/contracts/worker.ex @@ -401,8 +401,8 @@ defmodule Archethic.Contracts.Worker do %TransactionMovement{type: :UCO, amount: amount}, acc -> Map.update!(acc, :uco, &(&1 + amount)) - %TransactionMovement{type: {:NFT, nft_address}, amount: amount}, acc -> - Map.update!(acc, :nft, &Map.put(&1, nft_address, amount)) + %TransactionMovement{type: {:NFT, nft_address, nft_id}, amount: amount}, acc -> + Map.update!(acc, :nft, &Map.put(&1, {nft_address, nft_id}, amount)) end) %{uco: uco_balance, nft: nft_balances} = Account.get_balance(contract_address) @@ -420,9 +420,13 @@ defmodule Archethic.Contracts.Worker do with true <- uco_balance > uco_to_transfer + tx_fee, true <- - Enum.all?(nft_to_transfer, fn {nft_address, amount} -> - %{amount: balance} = Enum.find(nft_balances, &(Map.get(&1, :nft) == nft_address)) - balance > amount + Enum.all?(nft_to_transfer, fn {{t_nft_address, t_nft_id}, t_amount} -> + {{_nft_address, _nft_id}, balance} = + Enum.find(nft_balances, fn {{f_nft_address, f_nft_id}, _f_amount} -> + f_nft_address == t_nft_address and f_nft_id == t_nft_id + end) + + balance > t_amount end) do :ok else diff --git a/lib/archethic/p2p/message.ex b/lib/archethic/p2p/message.ex index a797a968c..658eee3b5 100644 --- a/lib/archethic/p2p/message.ex +++ b/lib/archethic/p2p/message.ex @@ -429,8 +429,8 @@ defmodule Archethic.P2P.Message do def encode(%Balance{uco: uco_balance, nft: nft_balances}) do nft_balances_binary = nft_balances - |> Enum.reduce([], fn {nft_address, amount}, acc -> - [<> | acc] + |> Enum.reduce([], fn {{nft_address, nft_id}, amount}, acc -> + [<> | acc] end) |> Enum.reverse() |> :erlang.list_to_binary() @@ -981,7 +981,8 @@ defmodule Archethic.P2P.Message do defp deserialize_nft_balances(rest, nb_nft_balances, acc) do {nft_address, <>} = Utils.deserialize_address(rest) - deserialize_nft_balances(rest, nb_nft_balances, Map.put(acc, nft_address, amount)) + <> = rest + deserialize_nft_balances(rest, nb_nft_balances, Map.put(acc, {nft_address, nft_id}, amount)) end defp deserialize_summaries(rest, 0, _), do: {[], rest} diff --git a/lib/archethic/p2p/message/balance.ex b/lib/archethic/p2p/message/balance.ex index 954782449..7f891b561 100644 --- a/lib/archethic/p2p/message/balance.ex +++ b/lib/archethic/p2p/message/balance.ex @@ -6,6 +6,6 @@ defmodule Archethic.P2P.Message.Balance do @type t :: %__MODULE__{ uco: non_neg_integer(), - nft: %{binary() => non_neg_integer()} + nft: %{{binary(), non_neg_integer()} => non_neg_integer()} } end diff --git a/lib/archethic/transaction_chain/transaction.ex b/lib/archethic/transaction_chain/transaction.ex index 81f3f63c1..b0326f707 100755 --- a/lib/archethic/transaction_chain/transaction.ex +++ b/lib/archethic/transaction_chain/transaction.ex @@ -376,7 +376,7 @@ defmodule Archethic.TransactionChain.Transaction do ...> }, ...> nft: %NFTLedger{ ...> transfers: [ - ...> %NFTLedger.Transfer{to: "@Alice1", amount: 3, nft: "@BobNFT"} + ...> %NFTLedger.Transfer{to: "@Alice1", amount: 3, nft: "@BobNFT", nft_id: 0} ...> ] ...> } ...> } @@ -387,7 +387,7 @@ defmodule Archethic.TransactionChain.Transaction do to: "@Alice1", amount: 10, type: :UCO, }, %TransactionMovement{ - to: "@Alice1", amount: 3, type: {:NFT, "@BobNFT"}, + to: "@Alice1", amount: 3, type: {:NFT, "@BobNFT", 0}, } ] """ @@ -403,7 +403,7 @@ defmodule Archethic.TransactionChain.Transaction do Enum.map(uco_transfers, &%TransactionMovement{to: &1.to, amount: &1.amount, type: :UCO}) ++ Enum.map( nft_transfers, - &%TransactionMovement{to: &1.to, amount: &1.amount, type: {:NFT, &1.nft}} + &%TransactionMovement{to: &1.to, amount: &1.amount, type: {:NFT, &1.nft, &1.nft_id}} ) end diff --git a/lib/archethic/transaction_chain/transaction/data/ledger.ex b/lib/archethic/transaction_chain/transaction/data/ledger.ex index f7a1688ce..0f890b119 100755 --- a/lib/archethic/transaction_chain/transaction/data/ledger.ex +++ b/lib/archethic/transaction_chain/transaction/data/ledger.ex @@ -36,7 +36,8 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, - ...> amount: 1_050_000_000 + ...> amount: 1_050_000_000, + ...> nft_id: 0 ...> } ...> ] ...> } @@ -59,7 +60,9 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do 0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53, # NFT transfer amount - 0, 0, 0, 0, 62, 149, 186, 128 + 0, 0, 0, 0, 62, 149, 186, 128, + # NFT ID + 0 >> """ @spec serialize(t()) :: binary() @@ -78,7 +81,7 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do ...> 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, ...> 0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53, - ...> 0, 0, 0, 0, 62, 149, 186, 128>> + ...> 0, 0, 0, 0, 62, 149, 186, 128, 0>> ...> |> Ledger.deserialize() { %Ledger{ @@ -98,7 +101,8 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, to: <<0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, - amount: 1_050_000_000 + amount: 1_050_000_000, + nft_id: 0 } ] } @@ -163,7 +167,8 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, - ...> amount: 1_050_000_000 + ...> amount: 1_050_000_000, + ...> nft_id: 0 ...> } ...> ] ...> } diff --git a/lib/archethic/transaction_chain/transaction/data/ledger/nft.ex b/lib/archethic/transaction_chain/transaction/data/ledger/nft.ex index d915b82e0..db3939d78 100755 --- a/lib/archethic/transaction_chain/transaction/data/ledger/nft.ex +++ b/lib/archethic/transaction_chain/transaction/data/ledger/nft.ex @@ -25,7 +25,8 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger do ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, - ...> amount: 1_050_000_000 + ...> amount: 1_050_000_000, + ...> nft_id: 0 ...> } ...> ]} ...> |> NFTLedger.serialize() @@ -39,7 +40,9 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger do 0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53, # NFT amount - 0, 0, 0, 0, 62, 149, 186, 128 + 0, 0, 0, 0, 62, 149, 186, 128, + # NFT_ID + 0 >> """ @spec serialize(t()) :: binary() @@ -56,7 +59,7 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger do iex> <<1, 0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, 0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53, - ...> 0, 0, 0, 0, 62, 149, 186, 128>> + ...> 0, 0, 0, 0, 62, 149, 186, 128, 0>> ...> |> NFTLedger.deserialize() { %NFTLedger{ @@ -66,7 +69,8 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger do 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, to: <<0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, - amount: 1_050_000_000 + amount: 1_050_000_000, + nft_id: 0 } ] }, @@ -130,14 +134,16 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger do ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, - ...> amount: 1_050_000_000 + ...> amount: 1_050_000_000, + ...> nft_id: 0, ...> }, ...> %Transfer{ ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 202, 39, 113, 5, 117, 133, 141, 107, 1, 202, 156, 250, 124, 22, 13, 183, 20, ...> 221, 181, 252, 153, 184, 2, 26, 115, 73, 148, 163, 119, 163, 86, 6>>, - ...> amount: 2_290_000_000 + ...> amount: 2_290_000_000, + ...> nft_id: 0, ...> } ...> ]} ...> |> NFTLedger.total_amount() diff --git a/lib/archethic/transaction_chain/transaction/data/ledger/nft/transfer.ex b/lib/archethic/transaction_chain/transaction/data/ledger/nft/transfer.ex index 2a2e42c76..2e942ca7c 100644 --- a/lib/archethic/transaction_chain/transaction/data/ledger/nft/transfer.ex +++ b/lib/archethic/transaction_chain/transaction/data/ledger/nft/transfer.ex @@ -2,9 +2,10 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do @moduledoc """ Represents a NFT ledger transfer """ - defstruct [:to, :amount, :nft, conditions: []] + defstruct [:to, :amount, :nft, conditions: [], nft_id: 0] alias Archethic.Utils + # impl nft_id @typedoc """ Transfer is composed from: @@ -12,12 +13,14 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do - to: receiver address of the asset - amount: specify the number of NFT to transfer to the recipients (in the smallest unit 10^-8) - conditions: specify to which address the NFT can be used + - nft_id: To uniquely identify a nft from a set a of nft(nft collection) """ @type t :: %__MODULE__{ nft: binary(), to: binary(), amount: non_neg_integer(), - conditions: list(binary()) + conditions: list(binary()), + nft_id: non_neg_integer() } @doc """ @@ -30,7 +33,8 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, ...> 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, - ...> amount: 1_050_000_000 + ...> amount: 1_050_000_000, + ...> nft_id: 0 ...> } ...> |> Transfer.serialize() << @@ -41,11 +45,13 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do 0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14, # Transfer amount - 0, 0, 0, 0, 62, 149, 186, 128 + 0, 0, 0, 0, 62, 149, 186, 128, + # NFT ID + 0 >> """ - def serialize(%__MODULE__{nft: nft, to: to, amount: amount}) do - <> + def serialize(%__MODULE__{nft: nft, to: to, amount: amount, nft_id: nft_id}) do + <> end @doc """ @@ -58,7 +64,7 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, ...> 0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, ...> 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14, - ...> 0, 0, 0, 0, 62, 149, 186, 128>> + ...> 0, 0, 0, 0, 62, 149, 186, 128, 0>> ...> |> Transfer.deserialize() { %Transfer{ @@ -66,7 +72,8 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, - amount: 1_050_000_000 + amount: 1_050_000_000, + nft_id: 0 }, "" } @@ -75,32 +82,81 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do def deserialize(data) do {nft_address, rest} = Utils.deserialize_address(data) {recipient_address, <>} = Utils.deserialize_address(rest) + <> = rest { %__MODULE__{ nft: nft_address, to: recipient_address, - amount: amount + amount: amount, + nft_id: nft_id }, rest } end + @doc """ + Forms NFT.Transfer Struct from a map + doc_test: To ensure addition of a feature don't cause regression + + ## Examples + + iex> %{ + ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, + ...> to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, + ...> 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, + ...> amount: 1_050_000_000, + ...> nft_id: 0 + ...> } + ...> |> Transfer.from_map() + %Transfer{ + nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, + to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, + 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, + amount: 1_050_000_000, + nft_id: 0 + } + """ @spec from_map(map()) :: t() def from_map(transfer = %{}) do %__MODULE__{ nft: Map.get(transfer, :nft), to: Map.get(transfer, :to), - amount: Map.get(transfer, :amount) + amount: Map.get(transfer, :amount), + nft_id: Map.get(transfer, :nft_id) } end + @doc """ + ## Examples + + iex> %Transfer{ + ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, + ...> to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, + ...> 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, + ...> amount: 1_050_000_000, + ...> nft_id: 0 + ...> } + ...> |> Transfer.to_map() + %{ + nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, + to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, + 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, + amount: 1_050_000_000, + nft_id: 0 + } + """ @spec to_map(t()) :: map() - def to_map(%__MODULE__{nft: nft, to: to, amount: amount}) do + def to_map(%__MODULE__{nft: nft, to: to, amount: amount, nft_id: nft_id}) do %{ nft: nft, to: to, - amount: amount + amount: amount, + nft_id: nft_id } end end diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex index f09345530..1c5be9843 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex @@ -44,9 +44,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation @doc """ Build some ledger operations from a specific transaction - ## Examples - iex> LedgerOperations.from_transaction(%LedgerOperations{}, ...> %Transaction{ ...> address: "@NFT2", @@ -55,8 +53,14 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> } ...> ) %LedgerOperations{ - unspent_outputs: [%UnspentOutput{from: "@NFT2", amount: 100_000_000_000, type: {:NFT, "@NFT2"}}] - } + unspent_outputs: [ + %UnspentOutput{ + from: "@NFT2", + amount: 100_000_000_000, + type: {:NFT, "@NFT2", 0} + } + ] + } """ @spec from_transaction(t(), Transaction.t()) :: t() def from_transaction(ops = %__MODULE__{}, %Transaction{ @@ -75,13 +79,41 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation %{ ops | unspent_outputs: [ - %UnspentOutput{from: address, amount: initial_supply * @unit_uco, type: {:NFT, address}} + %UnspentOutput{ + from: address, + amount: initial_supply * @unit_uco, + type: {:NFT, address, 0} + } ] } end def from_transaction(ops = %__MODULE__{}, %Transaction{}), do: ops + defp get_token_utxos(%{"type" => "fungible", "supply" => supply}, address) do + [ + %UnspentOutput{ + from: address, + amount: supply * @unit_uco, + type: {:NFT, address, 0} + } + ] + end + + defp get_token_utxos( + %{"type" => "non-fungible", "supply" => supply, "properties" => properties}, + address + ) + when length(properties) == supply do + properties + |> Enum.with_index() + |> Enum.map(fn {_item_properties, index} -> + %UnspentOutput{from: address, amount: 1 * @unit_uco, type: {:NFT, address, index}} + end) + end + + defp get_token_utxos(_, _), do: [] + @doc """ Returns the amount to spend from the transaction movements and the fee @@ -91,12 +123,13 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> transaction_movements: [ ...> %TransactionMovement{to: "@Bob4", amount: 1_040_000_000, type: :UCO}, ...> %TransactionMovement{to: "@Charlie2", amount: 217_000_000, type: :UCO}, - ...> %TransactionMovement{to: "@Charlie2", amount: 2_000_000_000, type: {:NFT, "@TomNFT"}}, + ...> %TransactionMovement{to: "@Charlie2", amount: 2_000_000_000, type: + ...> {:NFT, "@TomNFT", 0}}, ...> ], ...> fee: 40_000_000 ...> } ...> |> LedgerOperations.total_to_spend() - %{ uco: 1_297_000_000, nft: %{ "@TomNFT" => 2_000_000_000 } } + %{ uco: 1_297_000_000, nft: %{ {"@TomNFT",0} => 2_000_000_000 } } """ @spec total_to_spend(t()) :: %{ :uco => non_neg_integer(), @@ -113,8 +146,8 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation %{type: :UCO, amount: amount}, acc -> Map.update!(acc, :uco, &(&1 + amount)) - %{type: {:NFT, nft_address}, amount: amount}, acc -> - update_in(acc, [:nft, Access.key(nft_address, 0)], &(&1 + amount)) + %{type: {:NFT, nft_address, nft_id}, amount: amount}, acc -> + update_in(acc, [:nft, Access.key({nft_address, nft_id}, 0)], &(&1 + amount)) %{type: :call}, acc -> acc @@ -130,7 +163,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> transaction_movements: [ ...> %TransactionMovement{to: "@Bob4", amount: 1_040_000_000, type: :UCO}, ...> %TransactionMovement{to: "@Charlie2", amount: 217_000_000, type: :UCO}, - ...> %TransactionMovement{to: "@Tom4", amount: 500_000_000, type: {:NFT, "@BobNFT"}} + ...> %TransactionMovement{to: "@Tom4", amount: 500_000_000, type: {:NFT, "@BobNFT", 0}} ...> ], ...> fee: 40_000_000 ...> } @@ -141,13 +174,13 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> transaction_movements: [ ...> %TransactionMovement{to: "@Bob4", amount: 1_040_000_000, type: :UCO}, ...> %TransactionMovement{to: "@Charlie2", amount: 217_000_000, type: :UCO}, - ...> %TransactionMovement{to: "@Tom4", amount: 500_000_000, type: {:NFT, "@BobNFT"}} + ...> %TransactionMovement{to: "@Tom4", amount: 500_000_000, type: {:NFT, "@BobNFT", 0}} ...> ], ...> fee: 40_000_000 ...> } ...> |> LedgerOperations.sufficient_funds?([ ...> %UnspentOutput{from: "@Charlie5", amount: 3_000_000_000, type: :UCO}, - ...> %UnspentOutput{from: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@BobNFT"}} + ...> %UnspentOutput{from: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@BobNFT", 0}} ...> ]) true @@ -157,7 +190,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> } ...> |> LedgerOperations.sufficient_funds?([ ...> %UnspentOutput{from: "@Charlie5", amount: 3_000_000_000, type: :UCO}, - ...> %UnspentOutput{from: "@Bob4", amount: 10_000_000_000, type: {:NFT, "@BobNFT"}} + ...> %UnspentOutput{from: "@Bob4", amount: 10_000_000_000, type: {:NFT, "@BobNFT", 0}} ...> ]) true """ @@ -175,8 +208,8 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation defp sufficient_nfts?(_nfts_received, nfts_to_spend) when map_size(nfts_to_spend) == 0, do: true defp sufficient_nfts?(nfts_received, nfts_to_spend) do - Enum.all?(nfts_to_spend, fn {nft_address, amount_to_spend} -> - case Map.get(nfts_received, nft_address) do + Enum.all?(nfts_to_spend, fn {nft_key, amount_to_spend} -> + case Map.get(nfts_received, nft_key) do nil -> false @@ -242,52 +275,75 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ] } - # When using NFT unspent outputs are sufficient to satisfy the transaction movements + # When using NFT unspent outputs are sufficient to satisfy the transaction movements + + iex> %LedgerOperations{ + ...> transaction_movements: [ + ...> %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT", 0}} + ...> ], + ...> fee: 40_000_000 + ...> } + ...> |> LedgerOperations.consume_inputs("@Alice2", [ + ...> %UnspentOutput{from: "@Charlie1", amount: 200_000_000, type: :UCO}, + ...> %UnspentOutput{from: "@Bob3", amount: 1_200_000_000, type: {:NFT, "@CharlieNFT", 0}} + ...> ]) + %LedgerOperations{ + transaction_movements: [ + %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT", 0}} + ], + fee: 40_000_000, + unspent_outputs: [ + %UnspentOutput{from: "@Alice2", amount: 160_000_000, type: :UCO}, + %UnspentOutput{from: "@Alice2", amount: 200_000_000, type: {:NFT, "@CharlieNFT", 0}} + ] + } + + # When multiple NFT unspent outputs are sufficient to satisfy the transaction movements iex> %LedgerOperations{ ...> transaction_movements: [ - ...> %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT"}} + ...> %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT", 0}} ...> ], ...> fee: 40_000_000 ...> } ...> |> LedgerOperations.consume_inputs("@Alice2", [ ...> %UnspentOutput{from: "@Charlie1", amount: 200_000_000, type: :UCO}, - ...> %UnspentOutput{from: "@Bob3", amount: 1_200_000_000, type: {:NFT, "@CharlieNFT"}} + ...> %UnspentOutput{from: "@Bob3", amount: 500_000_000, type: {:NFT, "@CharlieNFT", 0}}, + ...> %UnspentOutput{from: "@Hugo5", amount: 700_000_000, type: {:NFT, "@CharlieNFT", 0}}, + ...> %UnspentOutput{from: "@Tom1", amount: 700_000_000, type: {:NFT, "@CharlieNFT", 0}} ...> ]) %LedgerOperations{ transaction_movements: [ - %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT"}} + %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT", 0}} ], fee: 40_000_000, unspent_outputs: [ %UnspentOutput{from: "@Alice2", amount: 160_000_000, type: :UCO}, - %UnspentOutput{from: "@Alice2", amount: 200_000_000, type: {:NFT, "@CharlieNFT"}} + %UnspentOutput{from: "@Alice2", amount: 900_000_000, type: {:NFT, "@CharlieNFT", 0}} ] } - # When multiple NFT unspent outputs are sufficient to satisfy the transaction movements - iex> %LedgerOperations{ - ...> transaction_movements: [ - ...> %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT"}} - ...> ], - ...> fee: 40_000_000 - ...> } - ...> |> LedgerOperations.consume_inputs("@Alice2", [ - ...> %UnspentOutput{from: "@Charlie1", amount: 200_000_000, type: :UCO}, - ...> %UnspentOutput{from: "@Bob3", amount: 500_000_000, type: {:NFT, "@CharlieNFT"}}, - ...> %UnspentOutput{from: "@Hugo5", amount: 700_000_000, type: {:NFT, "@CharlieNFT"}}, - ...> %UnspentOutput{from: "@Tom1", amount: 700_000_000, type: {:NFT, "@CharlieNFT"}} + ...> transaction_movements: [ + ...> %TransactionMovement{to: "@Bob4", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 1}} + ...> ], + ...> fee: 40_000_000 + ...> } |> LedgerOperations.consume_inputs("@Alice2", [ + ...> %UnspentOutput{from: "@Charlie1", amount: 200_000_000, type: :UCO}, + ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 0}}, + ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 1}}, + ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 2}} ...> ]) %LedgerOperations{ - transaction_movements: [ - %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT"}} - ], - fee: 40_000_000, - unspent_outputs: [ - %UnspentOutput{from: "@Alice2", amount: 160_000_000, type: :UCO}, - %UnspentOutput{from: "@Alice2", amount: 900_000_000, type: {:NFT, "@CharlieNFT"}} - ] + fee: 40_000_000, + transaction_movements: [ + %TransactionMovement{to: "@Bob4", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 1}} + ], + unspent_outputs: [ + %UnspentOutput{from: "@Alice2", amount: 160_000_000, type: :UCO}, + %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 0}}, + %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 2}} + ] } """ @spec consume_inputs( @@ -304,7 +360,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation new_unspent_outputs = [ %UnspentOutput{from: change_address, amount: uco_balance - uco_to_spend, type: :UCO} - | new_nft_unspent_outputs(nfts_received, nfts_to_spend, change_address) + | new_nft_unspent_outputs(nfts_received, nfts_to_spend, change_address, inputs) ] Map.update!(ops, :unspent_outputs, &(new_unspent_outputs ++ &1)) @@ -313,21 +369,34 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation end end - defp new_nft_unspent_outputs(nfts_received, nfts_to_spend, change_address) do - Enum.reduce(nfts_to_spend, [], fn {nft_address, amount_to_spend}, acc -> - case Map.get(nfts_received, nft_address) do + defp new_nft_unspent_outputs(nfts_received, nfts_to_spend, change_address, inputs) do + # Reject NFT not used to inject back in the new unspent outputs + nfts_not_used = + nfts_received + |> Enum.reject(&Map.has_key?(nfts_to_spend, elem(&1, 0))) + |> Enum.map(fn {{nft_address, nft_id}, amount} -> + Enum.find(inputs, fn input -> + input.type == {:NFT, nft_address, nft_id} and input.amount == amount + end) + end) + + Enum.reduce(nfts_to_spend, nfts_not_used, fn {{nft_address, nft_id}, amount_to_spend}, acc -> + case Map.get(nfts_received, {nft_address, nft_id}) do nil -> acc - recv_amount -> + recv_amount when recv_amount - amount_to_spend > 0 -> [ %UnspentOutput{ from: change_address, amount: recv_amount - amount_to_spend, - type: {:NFT, nft_address} + type: {:NFT, nft_address, nft_id} } | acc ] + + _ -> + acc end end) end diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement.ex index 311ca9424..3414e2e0b 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement.ex @@ -40,15 +40,14 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation # Amount 0, 0, 0, 0, 1, 201, 195, 128, # UCO type - 0 - >> + 0>> iex> %TransactionMovement{ ...> to: <<0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, ...> 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, ...> amount: 30_000_000, ...> type: {:NFT, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, - ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>} + ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} ...> } ...> |> TransactionMovement.serialize() << @@ -61,7 +60,9 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation 1, # NFT address 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, - 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175 + 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, + # NFT ID + 0 >> """ @spec serialize(t()) :: <<_::64, _::_*8>> @@ -76,8 +77,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation iex> <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, ...> 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186, - ...> 0, 0, 0, 0, 1, 201, 195, 128, 0 - ...> >> + ...> 0, 0, 0, 0, 1, 201, 195, 128, 0>> ...> |> TransactionMovement.deserialize() { %TransactionMovement{ @@ -92,7 +92,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation iex> <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, ...> 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186, ...> 0, 0, 0, 0, 1, 201, 195, 128, 1, 0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, - ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175 + ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, 0 ...> >> ...> |> TransactionMovement.deserialize() { @@ -101,7 +101,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, amount: 30_000_000, type: {:NFT, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, - 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>} + 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} }, "" } @@ -121,6 +121,41 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation } end + @doc """ + Convert a map to TransactionMovement Struct + + ## Examples + + iex> %{ + ...> to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + ...> amount: 30_000_000, + ...> type: "UCO" + ...> } + ...> |> TransactionMovement.from_map() + %TransactionMovement{ + to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + amount: 30_000_000, + type: :UCO + } + + iex> %{ + ...> to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, + ...> 194,159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + ...> amount: 30_000_000, + ...> type: "NFT", nft_address: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, + ...> 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, + ...> 143, 175>>, nft_id: 0 + ...> } + ...> |> TransactionMovement.from_map() + %TransactionMovement{ + to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, + 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + amount: 30_000_000, + type: {:NFT, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, + 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} + } + + """ @spec from_map(map()) :: t() def from_map(movement = %{}) do res = %__MODULE__{ @@ -129,14 +164,46 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation } case Map.get(movement, :type) do - :UCO -> - %{res | type: :UCO} + "NFT" -> + %{res | type: {:NFT, Map.get(movement, :nft_address), Map.get(movement, :nft_id)}} - {:NFT, nft_address} -> - %{res | type: {:NFT, nft_address}} + _ -> + %{res | type: :UCO} end end + @doc """ + Convert TransactionMovement Struct to a Map + + ## Examples + + iex> %TransactionMovement{ + ...> to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + ...> amount: 30_000_000, + ...> type: :UCO + ...> } + ...> |> TransactionMovement.to_map() + %{ + to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + amount: 30_000_000, + type: "UCO" + } + + iex> %TransactionMovement{ + ...> to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + ...> amount: 30_000_000, + ...> type: {:NFT, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} + ...> } + ...> |> TransactionMovement.to_map() + %{ + to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + amount: 30_000_000, + type: "NFT", + nft_address: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, + nft_id: 0 + } + + """ @spec to_map(t()) :: map() def to_map(%__MODULE__{to: to, amount: amount, type: :UCO}) do %{ @@ -146,12 +213,17 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation } end - def to_map(%__MODULE__{to: to, amount: amount, type: {:NFT, nft_address}}) do + def to_map(%__MODULE__{ + to: to, + amount: amount, + type: {:NFT, nft_address, nft_id} + }) do %{ to: to, amount: amount, type: "NFT", - nft_address: nft_address + nft_address: nft_address, + nft_id: nft_id } end end diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement/type.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement/type.ex index 05cf8331c..7509aa385 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement/type.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement/type.ex @@ -9,21 +9,21 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation @typedoc """ Transaction movement can be: - UCO transfers - - NFT transfers. When it's a NFT transfer, the type indicates the address of NFT to transfer + - NFT transfers. When it's a NFT transfer, the type indicates the address of NFT to transfer, followed by nft_id """ - @type t() :: :UCO | {:NFT, Crypto.versioned_hash()} + @type t() :: :UCO | {:NFT, Crypto.versioned_hash(), non_neg_integer()} def serialize(:UCO), do: <<0>> - def serialize({:NFT, address}) do - <<1::8, address::binary>> + def serialize({:NFT, address, nft_id}) do + <<1::8, address::binary, nft_id::8>> end def deserialize(<<0::8, rest::bitstring>>), do: {:UCO, rest} def deserialize(<<1::8, rest::bitstring>>) do {address, rest} = Utils.deserialize_address(rest) - - {{:NFT, address}, rest} + <> = rest + {{:NFT, address, nft_id}, rest} end end diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output.ex index a5acd79fe..818927cd3 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output.ex @@ -48,7 +48,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, ...> amount: 1_050_000_000, ...> type: {:NFT, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, - ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>} + ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} ...> } ...> |> UnspentOutput.serialize() << @@ -61,7 +61,9 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation 1, # NFT address 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, - 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175 + 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, + # NFT ID + 0 >> """ @spec serialize(__MODULE__.t()) :: <<_::64, _::_*8>> @@ -91,8 +93,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation iex> <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, ...> 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186, ...> 0, 0, 0, 0, 62, 149, 186, 128, 1, 0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, - ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175 - ...> >> + ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, 0>> ...> |> UnspentOutput.deserialize() { %UnspentOutput{ @@ -100,7 +101,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, amount: 1_050_000_000, type: {:NFT, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, - 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>} + 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0}, }, "" } @@ -120,37 +121,124 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation } end + @doc """ + Build %UnspentOutput struct from map + + ## Examples + + iex> %{ + ...> from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, + ...> 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + ...> amount: 1_050_000_000, + ...> type: :UCO + ...> } |> UnspentOutput.from_map() + %UnspentOutput{ + from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194,159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + amount: 1_050_000_000, + type: :UCO, + reward?: false, + timestamp: nil + } + + + iex> %{ + ...> from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, + ...> 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + ...> amount: 1_050_000_000, + ...> type: {:NFT, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} + ...> } |> UnspentOutput.from_map() + %UnspentOutput{ + from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194,159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + amount: 1_050_000_000, + type: {:NFT, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74,197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0 }, + reward?: false, + timestamp: nil + } + """ @spec from_map(map()) :: __MODULE__.t() def from_map(unspent_output = %{}) do - res = %__MODULE__{ + %__MODULE__{ from: Map.get(unspent_output, :from), - amount: Map.get(unspent_output, :amount) + amount: Map.get(unspent_output, :amount), + type: Map.get(unspent_output, :type) } + end - case Map.get(unspent_output, :type) do - "NFT" -> - %{res | type: {:NFT, Map.get(unspent_output, :nft_address)}} + @doc """ + Convert %UnspentOutput{} Struct to a Map + + ## Examples + + iex> %UnspentOutput{ + ...> from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194,159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + ...> amount: 1_050_000_000, + ...> type: :UCO, + ...> reward?: false, + ...> timestamp: nil + ...> }|> UnspentOutput.to_map() + %{ + from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194,159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + amount: 1_050_000_000, + type: "UCO", + reward?: false, + timestamp: nil + } + + + iex> %UnspentOutput{ + ...> from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, + ...> 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + ...> amount: 1_050_000_000, + ...> type: {:NFT, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, + ...> 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0 } + ...> } |> UnspentOutput.to_map() + %{ + from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, + 194,159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, + amount: 1_050_000_000, + type: "NFT", + nft_address: <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, + 140,74,197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, + nft_id: 0, + reward?: false, + timestamp: nil + } - _ -> - %{res | type: :UCO} - end - end + """ @spec to_map(t()) :: map() - def to_map(%__MODULE__{from: from, amount: amount, type: :UCO}) do + def to_map(%__MODULE__{ + from: from, + amount: amount, + type: :UCO, + reward?: reward, + timestamp: timestamp + }) do %{ from: from, amount: amount, - type: "UCO" + type: "UCO", + reward?: reward, + timestamp: timestamp } end - def to_map(%__MODULE__{from: from, amount: amount, type: {:NFT, nft_address}}) do + def to_map(%__MODULE__{ + from: from, + amount: amount, + type: {:NFT, nft_address, nft_id}, + reward?: reward, + timestamp: timestamp + }) do %{ from: from, amount: amount, type: "NFT", - nft_address: nft_address + nft_address: nft_address, + nft_id: nft_id, + reward?: reward, + timestamp: timestamp } end end diff --git a/lib/archethic/transaction_chain/transaction_input.ex b/lib/archethic/transaction_chain/transaction_input.ex index 7f439854c..37fe4b723 100644 --- a/lib/archethic/transaction_chain/transaction_input.ex +++ b/lib/archethic/transaction_chain/transaction_input.ex @@ -161,7 +161,7 @@ defmodule Archethic.TransactionChain.TransactionInput do res nft_address -> - %{res | type: {:NFT, nft_address}} + %{res | type: {:NFT, nft_address, Map.get(input, :nft_id)}} end :call -> @@ -195,7 +195,7 @@ defmodule Archethic.TransactionChain.TransactionInput do amount: amount, from: from, spent?: spent?, - type: {:NFT, nft_address}, + type: {:NFT, nft_address, nft_id}, timestamp: timestamp }) do %{ @@ -203,6 +203,7 @@ defmodule Archethic.TransactionChain.TransactionInput do from: from, type: :NFT, nft_address: nft_address, + nft_id: nft_id, spent: spent?, reward: false, timestamp: timestamp diff --git a/lib/archethic/utils/regression/playbook.ex b/lib/archethic/utils/regression/playbook.ex index 1d474c570..a8c550dab 100644 --- a/lib/archethic/utils/regression/playbook.ex +++ b/lib/archethic/utils/regression/playbook.ex @@ -287,9 +287,15 @@ defmodule Archethic.Utils.Regression.Playbook do Enum.map(nft_transfers, fn %NFTTransfer{ to: to, amount: amount, - nft: nft_address + nft: nft_address, + nft_id: nft_id } -> - %{"to" => Base.encode16(to), "amount" => amount, "nft" => nft_address} + %{ + "to" => Base.encode16(to), + "amount" => amount, + "nft" => nft_address, + "nft_id" => nft_id + } end) } }, diff --git a/lib/archethic_web/controllers/api/schema/nft_ledger.ex b/lib/archethic_web/controllers/api/schema/nft_ledger.ex index 7865f29ec..eaa9fde05 100644 --- a/lib/archethic_web/controllers/api/schema/nft_ledger.ex +++ b/lib/archethic_web/controllers/api/schema/nft_ledger.ex @@ -11,6 +11,7 @@ defmodule ArchethicWeb.API.Schema.NFTLedger do field(:to, Address) field(:amount, :integer) field(:nft, Address) + field(:nft_id, :integer) end end @@ -26,8 +27,9 @@ defmodule ArchethicWeb.API.Schema.NFTLedger do defp changeset_transfers(changeset, params) do changeset - |> cast(params, [:to, :amount, :nft]) - |> validate_required([:to, :amount, :nft]) + |> cast(params, [:to, :amount, :nft, :nft_id]) + |> validate_required([:to, :amount, :nft, :nft_id]) |> validate_number(:amount, greater_than: 0) + |> validate_inclusion(:nft_id, 0..255) end end diff --git a/lib/archethic_web/graphql_schema/resolver.ex b/lib/archethic_web/graphql_schema/resolver.ex index 2d127c8dc..0c2f46710 100644 --- a/lib/archethic_web/graphql_schema/resolver.ex +++ b/lib/archethic_web/graphql_schema/resolver.ex @@ -20,7 +20,9 @@ defmodule ArchethicWeb.GraphQLSchema.Resolver do uco: uco, nft: nft_balances - |> Enum.map(fn {address, amount} -> %{address: address, amount: amount} end) + |> Enum.map(fn {{address, nft_id}, amount} -> + %{address: address, amount: amount, nft_id: nft_id} + end) |> Enum.sort_by(& &1.amount) } diff --git a/lib/archethic_web/graphql_schema/transaction_type.ex b/lib/archethic_web/graphql_schema/transaction_type.ex index f8a6e81ad..4c4f95e20 100644 --- a/lib/archethic_web/graphql_schema/transaction_type.ex +++ b/lib/archethic_web/graphql_schema/transaction_type.ex @@ -78,6 +78,7 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do field(:to, :address) field(:amount, :amount) field(:nft, :address) + field(:nft_id, :integer) end @desc "[UCOLedger] represents the transfers to perform on the UCO ledger" @@ -158,6 +159,7 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do field(:amount, :amount) field(:type, :string) field(:nft_address, :address) + field(:nft_id, :integer) end @desc """ @@ -177,6 +179,7 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do field(:nft_address, :address) field(:spent, :boolean) field(:timestamp, :timestamp) + field(:nft_id, :integer) end @desc """ @@ -192,6 +195,7 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do field(:amount, :amount) field(:type, :string) field(:nft_address, :address) + field(:nft_id, :integer) end @desc """ @@ -225,6 +229,7 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do object :nft_balance do field(:address, :address) field(:amount, :amount) + field(:nft_id, :integer) end @desc """ diff --git a/lib/archethic_web/templates/explorer/transaction_details.html.leex b/lib/archethic_web/templates/explorer/transaction_details.html.leex index 4fccfac1a..5ad1273a0 100644 --- a/lib/archethic_web/templates/explorer/transaction_details.html.leex +++ b/lib/archethic_web/templates/explorer/transaction_details.html.leex @@ -333,7 +333,7 @@ <%= if movement.amount > 0 do %> (<%= format_full_usd_amount(movement.amount, @uco_price_at_time[:usd], @uco_price_now[:usd]) %>) <% end %> - <% {:NFT, nft_address} -> %> + <% {:NFT, nft_address, _nft_id} -> %> <%= to_float(movement.amount) %> NFT <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(nft_address)) do %> @@ -370,7 +370,7 @@ <%= if unspent_output.amount > 0 do %> (<%= format_full_usd_amount(unspent_output.amount, @uco_price_at_time[:usd], @uco_price_now[:usd]) %>) <% end %> - <% {:NFT, nft_address} -> %> + <% {:NFT, nft_address, _nft_id} -> %> <%= to_float(unspent_output.amount) %> NFT <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(nft_address)) do %> @@ -509,7 +509,7 @@ <%= if input.reward? do %> Mining reward <% end %> - <% {:NFT, nft_address} -> %> + <% {:NFT, nft_address, _nft_id} -> %>
NFT
diff --git a/test/archethic/account/mem_tables/nft_ledger_test.exs b/test/archethic/account/mem_tables/nft_ledger_test.exs index 4ec98ea4c..0a9ccc2fa 100644 --- a/test/archethic/account/mem_tables/nft_ledger_test.exs +++ b/test/archethic/account/mem_tables/nft_ledger_test.exs @@ -1,4 +1,5 @@ defmodule Archethic.Account.MemTables.NFTLedgerTest do + @moduledoc false use ExUnit.Case alias Archethic.Account.MemTables.NFTLedger diff --git a/test/archethic/account/mem_tables_loader_test.exs b/test/archethic/account/mem_tables_loader_test.exs index 77cbbc9c0..418c62590 100644 --- a/test/archethic/account/mem_tables_loader_test.exs +++ b/test/archethic/account/mem_tables_loader_test.exs @@ -1,4 +1,5 @@ defmodule Archethic.Account.MemTablesLoaderTest do + @moduledoc false use ArchethicCase alias Archethic.Account.MemTables.NFTLedger @@ -50,7 +51,7 @@ defmodule Archethic.Account.MemTablesLoaderTest do %UnspentOutput{ from: "@Charlie3", amount: 1_000_000_000, - type: {:NFT, "@CharlieNFT"} + type: {:NFT, "@CharlieNFT", 0} } ] = NFTLedger.get_unspent_outputs("@Bob3") end @@ -79,7 +80,7 @@ defmodule Archethic.Account.MemTablesLoaderTest do %UnspentOutput{ from: "@Charlie3", amount: 1_000_000_000, - type: {:NFT, "@CharlieNFT"} + type: {:NFT, "@CharlieNFT", 0} } ] = NFTLedger.get_unspent_outputs("@Bob3") end @@ -94,7 +95,11 @@ defmodule Archethic.Account.MemTablesLoaderTest do ledger_operations: %LedgerOperations{ transaction_movements: [ %TransactionMovement{to: "@Tom4", amount: 3_400_000_000, type: :UCO}, - %TransactionMovement{to: "@Bob3", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT"}} + %TransactionMovement{ + to: "@Bob3", + amount: 1_000_000_000, + type: {:NFT, "@CharlieNFT", 0} + } ], unspent_outputs: [ %UnspentOutput{ diff --git a/test/archethic/account_test.exs b/test/archethic/account_test.exs index 3100d7f51..40764c9df 100644 --- a/test/archethic/account_test.exs +++ b/test/archethic/account_test.exs @@ -1,4 +1,5 @@ defmodule Archethic.AccountTest do + @moduledoc false use ExUnit.Case alias Archethic.Account @@ -40,12 +41,12 @@ defmodule Archethic.AccountTest do %UnspentOutput{ from: "@Charlie2", amount: 10_000_000_000, - type: {:NFT, "@CharlieNFT"} + type: {:NFT, "@CharlieNFT", 0} }, ~U[2021-03-05 13:41:34Z] ) - assert %{uco: 400_000_000, nft: %{"@CharlieNFT" => 10_000_000_000}} == + assert %{uco: 400_000_000, nft: %{{"@CharlieNFT", 0} => 10_000_000_000}} == Account.get_balance("@Alice2") end @@ -71,7 +72,7 @@ defmodule Archethic.AccountTest do %UnspentOutput{ from: "@Charlie2", amount: 10_000_000_000, - type: {:NFT, "@CharlieNFT"} + type: {:NFT, "@CharlieNFT", 0} }, ~U[2021-03-05 13:41:34Z] ) diff --git a/test/archethic/contracts/interpreter/transaction_statements_test.exs b/test/archethic/contracts/interpreter/transaction_statements_test.exs index 4479cb8aa..53877c7f5 100644 --- a/test/archethic/contracts/interpreter/transaction_statements_test.exs +++ b/test/archethic/contracts/interpreter/transaction_statements_test.exs @@ -1,4 +1,5 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatementsTest do + @moduledoc false use ExUnit.Case alias Archethic.Contracts.Interpreter.TransactionStatements diff --git a/test/archethic/contracts/interpreter_test.exs b/test/archethic/contracts/interpreter_test.exs index 9deac884a..040debaec 100644 --- a/test/archethic/contracts/interpreter_test.exs +++ b/test/archethic/contracts/interpreter_test.exs @@ -1,4 +1,5 @@ defmodule Archethic.Contracts.InterpreterTest do + @moduledoc false use ArchethicCase alias Archethic.Contracts.Contract @@ -141,7 +142,8 @@ defmodule Archethic.Contracts.InterpreterTest do {"nft", <<174, 180, 166, 245, 171, 109, 130, 190, 34, 60, 88, 103, 235, 165, 254, 97, 111, 82, 244, 16, 220, 248, - 59, 69, 175, 241, 88, 221, 64, 174, 138, 195>>} + 59, 69, 175, 241, 88, 221, 64, 174, 138, 195>>}, + {"nft_id", 0} ] ] } @@ -275,7 +277,7 @@ defmodule Archethic.Contracts.InterpreterTest do actions triggered_by: transaction do set_type transfer add_uco_transfer to: \"7F6661ACE282F947ACA2EF947D01BDDC90C65F09EE828BDADE2E3ED4258470B3\", amount: 1040000000 - add_nft_transfer to: \"30670455713E2CBECF94591226A903651ED8625635181DDA236FECC221D1E7E4\", amount: 20000000000, nft: \"AEB4A6F5AB6D82BE223C5867EBA5FE616F52F410DCF83B45AFF158DD40AE8AC3\" + add_nft_transfer to: \"30670455713E2CBECF94591226A903651ED8625635181DDA236FECC221D1E7E4\", amount: 20000000000, nft: \"AEB4A6F5AB6D82BE223C5867EBA5FE616F52F410DCF83B45AFF158DD40AE8AC3\", nft_id: 0 set_content \"Receipt\" add_ownership secret: \"MyEncryptedSecret\", secret_key: \"MySecretKey\", authorized_public_keys: ["70C245E5D970B59DF65638BDD5D963EE22E6D892EA224D8809D0FB75D0B1907A"] add_recipient \"78273C5CBCEB8617F54380CC2F173DF2404DB676C9F10D546B6F395E6F3BDDEE\" @@ -540,7 +542,7 @@ defmodule Archethic.Contracts.InterpreterTest do # Send the new transaction set_type transfer - add_nft_transfer to: transaction.address, nft: contract.address, amount: token_to_credit + add_nft_transfer to: transaction.address, nft: contract.address, amount: token_to_credit, nft_id: nft_id end end """ @@ -578,7 +580,7 @@ defmodule Archethic.Contracts.InterpreterTest do {:ok, %Contract{conditions: %{transaction: conditions}}} = ~s""" condition transaction: [ - address: get_genesis_address() == "64F05F5236088FC64D1BB19BD13BC548F1C49A42432AF02AD9024D8A2990B2B4" + address: get_genesis_address() == "64F05F5236088FC64D1BB19BD13BC548F1C49A42432AF02AD9024D8A2990B2B4" ] """ diff --git a/test/archethic/p2p/messages_test.exs b/test/archethic/p2p/messages_test.exs index c718ef70c..366051cb1 100644 --- a/test/archethic/p2p/messages_test.exs +++ b/test/archethic/p2p/messages_test.exs @@ -600,17 +600,18 @@ defmodule Archethic.P2P.MessageTest do test "Balance message" do nft_address = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> + nft_id = 0 assert %Balance{ uco: 1_050_000_000, nft: %{ - nft_address => 1_000_000_000 + {nft_address, nft_id} => 1_000_000_000 } } == %Balance{ uco: 1_050_000_000, nft: %{ - nft_address => 1_000_000_000 + {nft_address, nft_id} => 1_000_000_000 } } |> Message.encode() diff --git a/test/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output_test.exs b/test/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output_test.exs index 1eea2fa43..efaae4bbe 100644 --- a/test/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output_test.exs +++ b/test/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output_test.exs @@ -1,4 +1,5 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutputTest do + @moduledoc false use ExUnit.Case alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput doctest UnspentOutput diff --git a/test/archethic/transaction_chain/transaction/validation_stamp_test.exs b/test/archethic/transaction_chain/transaction/validation_stamp_test.exs index 9eae86433..4ab081ad3 100644 --- a/test/archethic/transaction_chain/transaction/validation_stamp_test.exs +++ b/test/archethic/transaction_chain/transaction/validation_stamp_test.exs @@ -55,7 +55,10 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStampTest do type <- StreamData.one_of([ StreamData.constant(:UCO), - StreamData.tuple({StreamData.constant(:NFT), StreamData.binary(length: 33)}) + StreamData.tuple( + {StreamData.constant(:NFT), StreamData.binary(length: 33), + StreamData.positive_integer()} + ) ]) ) do %TransactionMovement{to: to, amount: amount, type: type} @@ -69,7 +72,10 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStampTest do type <- StreamData.one_of([ StreamData.constant(:UCO), - StreamData.tuple({StreamData.constant(:NFT), StreamData.binary(length: 33)}) + StreamData.tuple( + {StreamData.constant(:NFT), StreamData.binary(length: 33), + StreamData.positive_integer()} + ) ]) ) do %UnspentOutput{from: from, amount: amount, type: type} diff --git a/test/archethic/transaction_chain/transaction_data/ledger/nft/transfer_test.exs b/test/archethic/transaction_chain/transaction_data/ledger/nft/transfer_test.exs index 2512be7e1..7a17b730b 100644 --- a/test/archethic/transaction_chain/transaction_data/ledger/nft/transfer_test.exs +++ b/test/archethic/transaction_chain/transaction_data/ledger/nft/transfer_test.exs @@ -1,4 +1,5 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.TransferTest do + @moduledoc false use ExUnit.Case alias Archethic.TransactionChain.TransactionData.NFTLedger.Transfer diff --git a/test/archethic/transaction_chain/transaction_data/ledger/nft_test.exs b/test/archethic/transaction_chain/transaction_data/ledger/nft_test.exs index b145d69ff..6a4d61957 100644 --- a/test/archethic/transaction_chain/transaction_data/ledger/nft_test.exs +++ b/test/archethic/transaction_chain/transaction_data/ledger/nft_test.exs @@ -1,4 +1,5 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedgerTest do + @moduledoc false use ExUnit.Case use ExUnitProperties @@ -12,15 +13,17 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedgerTest do transfers <- StreamData.map_of( StreamData.binary(length: 32), - {StreamData.binary(length: 32), StreamData.positive_integer()} + {StreamData.binary(length: 32), StreamData.positive_integer(), + StreamData.positive_integer()} ) ) do transfers = - Enum.map(transfers, fn {nft, {to, amount}} -> + Enum.map(transfers, fn {nft, {to, amount, nft_id}} -> %Transfer{ nft: <<0::8, 0::8>> <> nft, to: <<0::8, 0::8>> <> to, - amount: amount + amount: amount, + nft_id: nft_id } end) diff --git a/test/archethic/transaction_chain/transaction_data/ledger_test.exs b/test/archethic/transaction_chain/transaction_data/ledger_test.exs index 0e466a1a5..6e49b43dc 100644 --- a/test/archethic/transaction_chain/transaction_data/ledger_test.exs +++ b/test/archethic/transaction_chain/transaction_data/ledger_test.exs @@ -1,4 +1,5 @@ defmodule Archethic.TransactionChain.TransactionData.LedgerTest do + @moduledoc false use ExUnit.Case alias Archethic.TransactionChain.TransactionData.Ledger diff --git a/test/archethic/transaction_chain/transaction_test.exs b/test/archethic/transaction_chain/transaction_test.exs index 972a3d578..32ee6e6e8 100644 --- a/test/archethic/transaction_chain/transaction_test.exs +++ b/test/archethic/transaction_chain/transaction_test.exs @@ -1,4 +1,5 @@ defmodule Archethic.TransactionChain.TransactionTest do + @moduledoc false use ArchethicCase, async: false alias Archethic.Crypto diff --git a/test/archethic_web/controllers/api/transaction_payload_test.exs b/test/archethic_web/controllers/api/transaction_payload_test.exs index 1f9e89e2e..831accbd3 100644 --- a/test/archethic_web/controllers/api/transaction_payload_test.exs +++ b/test/archethic_web/controllers/api/transaction_payload_test.exs @@ -253,7 +253,8 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do %{ "to" => "abc", "amount" => 10.0, - "nft" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>) + "nft" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), + "nft_id" => 0 } ] } @@ -286,7 +287,8 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do %{ "to" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), "amount" => "abc", - "nft" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>) + "nft" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), + "nft_id" => 0 } ] } @@ -324,7 +326,8 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do %{ "to" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), "amount" => 10.0, - "nft" => "abc" + "nft" => "abc", + "nft_id" => 0 } ] } @@ -361,7 +364,8 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), "amount" => Enum.random(1..100), "nft" => - Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>) + Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), + "nft_id" => Enum.random(0..255) } end) } diff --git a/test/archethic_web/graphql_schema_test.exs b/test/archethic_web/graphql_schema_test.exs index 35b1db34e..ad6e92da8 100644 --- a/test/archethic_web/graphql_schema_test.exs +++ b/test/archethic_web/graphql_schema_test.exs @@ -1,4 +1,5 @@ defmodule ArchethicWeb.GraphQLSchemaTest do + @moduledoc false use ArchethicCase use ArchethicWeb.ConnCase use ArchethicWeb.GraphQLSubscriptionCase @@ -294,9 +295,9 @@ defmodule ArchethicWeb.GraphQLSchemaTest do {:ok, %Balance{ nft: %{ - "@NFT1" => 200_000_000, - "@NFT2" => 500_000_000, - "@NFT3" => 1_000_000_000 + {"@NFT1", 0} => 200_000_000, + {"@NFT2", 0} => 500_000_000, + {"@NFT3", 0} => 1_000_000_000 } }} end) From 2601305379e80273224daac6a9a1623e08737c04 Mon Sep 17 00:00:00 2001 From: Samuel Date: Mon, 20 Jun 2022 09:53:43 +0200 Subject: [PATCH 2/7] Mint tokens based on the JSON content --- .../validation_stamp/ledger_operations.ex | 60 ++++++++++--------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex index 1c5be9843..a32dfec7e 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex @@ -42,25 +42,41 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation @spec burning_address() :: Crypto.versioned_hash() def burning_address, do: @burning_address - @doc """ + @doc ~S""" Build some ledger operations from a specific transaction ## Examples iex> LedgerOperations.from_transaction(%LedgerOperations{}, ...> %Transaction{ ...> address: "@NFT2", ...> type: :nft, - ...> data: %TransactionData{content: "initial supply: 1000"} + ...> data: %TransactionData{content: "{\"supply\": 10, \"type\": \"fungible\" }"} ...> } ...> ) %LedgerOperations{ - unspent_outputs: [ - %UnspentOutput{ - from: "@NFT2", - amount: 100_000_000_000, - type: {:NFT, "@NFT2", 0} - } - ] - } + unspent_outputs: [%UnspentOutput{from: "@NFT2", amount: 1_000_000_000, type: {:NFT, "@NFT2", 0}}] + } + + iex> LedgerOperations.from_transaction(%LedgerOperations{}, + ...> %Transaction{ + ...> address: "@NFT2", + ...> type: :nft, + ...> data: %TransactionData{content: "{\"supply\": 10, \"type\": \"non-fungible\", \"properties\": [[],[],[],[],[],[],[],[],[],[]]}"} + ...> } + ...> ) + %LedgerOperations{ + unspent_outputs: [ + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 0}}, + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 1}}, + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 2}}, + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 3}}, + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 4}}, + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 5}}, + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 6}}, + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 7}}, + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 8}}, + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 9}} + ] + } """ @spec from_transaction(t(), Transaction.t()) :: t() def from_transaction(ops = %__MODULE__{}, %Transaction{ @@ -68,24 +84,14 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation type: :nft, data: %TransactionData{content: content} }) do - [[match | _]] = Regex.scan(~r/(?<=initial supply:).*\d/mi, content) - - {initial_supply, _} = - match - |> String.trim() - |> String.replace(" ", "") - |> Integer.parse() + case Jason.decode(content) do + {:ok, json} -> + utxos = get_token_utxos(json, address) + Map.update(ops, :unspent_outputs, utxos, &(utxos ++ &1)) - %{ - ops - | unspent_outputs: [ - %UnspentOutput{ - from: address, - amount: initial_supply * @unit_uco, - type: {:NFT, address, 0} - } - ] - } + _ -> + ops + end end def from_transaction(ops = %__MODULE__{}, %Transaction{}), do: ops From dacb2cd720124370d9e7d3f794d2da5040729688 Mon Sep 17 00:00:00 2001 From: Samuel Date: Mon, 20 Jun 2022 12:00:10 +0200 Subject: [PATCH 3/7] Add json schema --- .../mining/pending_transaction_validation.ex | 23 ++++++++- priv/json-schemas/token-core.json | 51 +++++++++++++++++++ .../pending_transaction_validation_test.exs | 33 ++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 priv/json-schemas/token-core.json diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 383861bcc..e799a2f03 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -307,10 +307,29 @@ defmodule Archethic.Mining.PendingTransactionValidation do type: :nft, data: %TransactionData{content: content} }) do - if Regex.match?(~r/(?<=initial supply:).*\d/mi, content) do + schema = + :archethic + |> Application.app_dir("priv/json-schemas/token-core.json") + |> File.read!() + |> Jason.decode!() + |> ExJsonSchema.Schema.resolve() + + with {:ok, json_token} <- Jason.decode(content), + :ok <- ExJsonSchema.Validator.validate(schema, json_token), + %{"type" => "non-fungible", "supply" => supply, "properties" => properties} + when length(properties) == supply <- json_token do :ok else - {:error, "Invalid NFT content"} + {:error, reason} -> + Logger.debug("Invalid NFT token specification: #{inspect(reason)}") + {:error, "Invalid NFT transaction - Invalid specification"} + + %{"type" => "fungible"} -> + :ok + + %{"type" => "non-fungible"} -> + {:error, + "Invalid NFT transaction - Supply should match properties for non-fungible tokens"} end end diff --git a/priv/json-schemas/token-core.json b/priv/json-schemas/token-core.json new file mode 100644 index 000000000..3bb21ca31 --- /dev/null +++ b/priv/json-schemas/token-core.json @@ -0,0 +1,51 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "supply": { + "type": "integer", + "description": "Number of tokens to create", + "exclusiveMinimum": 0 + }, + "type": { + "type": "string", + "pattern": "fungible|non-fungible", + "description": "Type of token to create" + }, + "name": { + "type": "string", + "description": "Name of the token" + }, + "symbol": { + "type": "string", + "description": "Symbol of the token" + }, + "properties": { + "description": "List of all the token properties (each one being for a token item)", + "type": "array", + "items": { "$ref": "#/$defs/property-list" } + } + }, + "required": [ "supply", "type" ], + "$defs": { + "property-list": { + "type": "array", + "description": "List of the properties for a given token item", + "items": { "$ref": "#/$defs/property" } + }, + "property": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the property" + }, + "value": { + "type": ["number", "string"], + "description": "Value of the property" + } + }, + "required": ["name", "value"] + } + } +} diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index c97b83d6d..7a627c770 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -314,5 +314,38 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do assert :ok = PendingTransactionValidation.validate(tx) end + + test "should return :ok when a transaction contains valid fields for NFT creation" do + tx_seed = :crypto.strong_rand_bytes(32) + + tx = + Transaction.new( + :nft, + %TransactionData{ + content: + Jason.encode!(%{ + supply: 3, + name: "MyToken", + type: "fungible", + symbol: "MTK", + properties: [ + [ + %{name: "image", value: "link"} + ], + [ + %{name: "image", value: "link"} + ], + [ + %{name: "image", value: "link"} + ] + ] + }) + }, + tx_seed, + 0 + ) + + assert :ok = PendingTransactionValidation.validate(tx) + end end end From d2e88a4ecc2aa63bcf1601e8d06cde7340f630ef Mon Sep 17 00:00:00 2001 From: Samuel Date: Mon, 20 Jun 2022 20:20:38 +0200 Subject: [PATCH 4/7] Add NFT transaction additional fee --- lib/archethic/mining/fee.ex | 67 +++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/lib/archethic/mining/fee.ex b/lib/archethic/mining/fee.ex index ae6e3dc44..c8d6760aa 100644 --- a/lib/archethic/mining/fee.ex +++ b/lib/archethic/mining/fee.ex @@ -46,17 +46,50 @@ defmodule Archethic.Mining.Fee do nb_bytes = get_transaction_size(tx) nb_storage_nodes = get_number_replicas(tx) - trunc( - do_calculate( + # TODO: determine the fee for smart contract execution + + storage_cost = + fee_for_storage( uco_price_in_usd, nb_bytes, - nb_storage_nodes, - nb_recipients - ) * @unit_uco - ) + nb_storage_nodes + ) + + replication_cost = cost_per_recipients(nb_recipients, uco_price_in_usd) + + fee = + minimum_fee(uco_price_in_usd) + storage_cost + replication_cost + + get_additional_fee(tx, uco_price_in_usd) + + trunc(fee * @unit_uco) + end + end + + defp get_additional_fee( + %Transaction{type: :nft, data: %TransactionData{content: content}}, + uco_price_in_usd + ) do + with {:ok, json} <- Jason.decode(content), + "non-fungible" <- Map.get(json, "type", "fungible"), + utxos = [_ | _] <- Map.get(json, "properties", []) do + nb_utxos = length(utxos) + base_fee = minimum_fee(uco_price_in_usd) + (:math.log10(nb_utxos) + 1) * nb_utxos * base_fee + else + {:error, _} -> + 0 + + "fungible" -> + 1 * minimum_fee(uco_price_in_usd) + + [] -> + # Invalid non-fungible definition + 0 end end + defp get_additional_fee(_tx, _uco_price_usd), do: 0 + defp get_transaction_size(tx = %Transaction{}) do tx |> Transaction.to_pending() @@ -84,26 +117,8 @@ defmodule Archethic.Mining.Fee do |> length() end - defp do_calculate( - uco_price_in_usd, - nb_bytes, - nb_storage_nodes, - nb_recipients - ) do - # TODO: determine the fee for smart contract execution - - minimum_fee = 0.01 / uco_price_in_usd - - storage_cost = - fee_for_storage( - uco_price_in_usd, - nb_bytes, - nb_storage_nodes - ) - - replication_cost = cost_per_recipients(nb_recipients, uco_price_in_usd) - - minimum_fee + storage_cost + replication_cost + defp minimum_fee(uco_price_in_usd) do + 0.01 / uco_price_in_usd end defp fee_for_storage(uco_price_in_usd, nb_bytes, nb_storage_nodes) do From 6ea1e83ab2272247368618a50bf9b132cbae9886 Mon Sep 17 00:00:00 2001 From: Samuel Date: Wed, 22 Jun 2022 15:13:55 +0200 Subject: [PATCH 5/7] Set NFT id to 0 for fungible --- .../validation_stamp/ledger_operations.ex | 26 ++++++++++++------- .../explorer/transaction_details.html.leex | 25 +++++++++++++----- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex index a32dfec7e..8dc3c4383 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex @@ -65,7 +65,6 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> ) %LedgerOperations{ unspent_outputs: [ - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 0}}, %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 1}}, %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 2}}, %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 3}}, @@ -74,7 +73,8 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 6}}, %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 7}}, %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 8}}, - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 9}} + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 9}}, + %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 10}} ] } """ @@ -114,7 +114,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation properties |> Enum.with_index() |> Enum.map(fn {_item_properties, index} -> - %UnspentOutput{from: address, amount: 1 * @unit_uco, type: {:NFT, address, index}} + %UnspentOutput{from: address, amount: 1 * @unit_uco, type: {:NFT, address, index + 1}} end) end @@ -329,26 +329,28 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ] } + # When non-fungible tokens are used as input but want to consume only a single input + iex> %LedgerOperations{ ...> transaction_movements: [ - ...> %TransactionMovement{to: "@Bob4", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 1}} + ...> %TransactionMovement{to: "@Bob4", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 2}} ...> ], ...> fee: 40_000_000 ...> } |> LedgerOperations.consume_inputs("@Alice2", [ ...> %UnspentOutput{from: "@Charlie1", amount: 200_000_000, type: :UCO}, - ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 0}}, ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 1}}, - ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 2}} + ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 2}}, + ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 3}} ...> ]) %LedgerOperations{ fee: 40_000_000, transaction_movements: [ - %TransactionMovement{to: "@Bob4", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 1}} + %TransactionMovement{to: "@Bob4", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 2}} ], unspent_outputs: [ %UnspentOutput{from: "@Alice2", amount: 160_000_000, type: :UCO}, - %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 0}}, - %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 2}} + %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 1}}, + %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 3}} ] } """ @@ -395,7 +397,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation [ %UnspentOutput{ from: change_address, - amount: recv_amount - amount_to_spend, + amount: recv_amount - trunc_nft_amount(nft_id, amount_to_spend), type: {:NFT, nft_address, nft_id} } | acc @@ -407,6 +409,10 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation end) end + # We prevent part of non-fungible token to be spent + defp trunc_nft_amount(0, amount), do: amount + defp trunc_nft_amount(_nft_id, amount), do: trunc(amount / @unit_uco) * @unit_uco + @doc """ List all the addresses from transaction movements """ diff --git a/lib/archethic_web/templates/explorer/transaction_details.html.leex b/lib/archethic_web/templates/explorer/transaction_details.html.leex index 5ad1273a0..8bc49789f 100644 --- a/lib/archethic_web/templates/explorer/transaction_details.html.leex +++ b/lib/archethic_web/templates/explorer/transaction_details.html.leex @@ -333,9 +333,14 @@ <%= if movement.amount > 0 do %> (<%= format_full_usd_amount(movement.amount, @uco_price_at_time[:usd], @uco_price_now[:usd]) %>) <% end %> - <% {:NFT, nft_address, _nft_id} -> %> + <% {:NFT, nft_address, nft_id} -> %> <%= to_float(movement.amount) %> - NFT + NFT + <%= if nft_id >= 1 do %> + (#<%= nft_id %>) + <% end %> + + <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(nft_address)) do %> <%= Base.encode16(:binary.part(nft_address, 0, 13)) %>... <% end %> @@ -370,9 +375,13 @@ <%= if unspent_output.amount > 0 do %> (<%= format_full_usd_amount(unspent_output.amount, @uco_price_at_time[:usd], @uco_price_now[:usd]) %>) <% end %> - <% {:NFT, nft_address, _nft_id} -> %> + <% {:NFT, nft_address, nft_id} -> %> <%= to_float(unspent_output.amount) %> - NFT + NFT + <%= if nft_id >= 1 do %> + (#<%= nft_id %>) + <% end %> + <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(nft_address)) do %> <%= Base.encode16(:binary.part(nft_address, 0, 13)) %>... <% end %> @@ -509,9 +518,13 @@ <%= if input.reward? do %> Mining reward <% end %> - <% {:NFT, nft_address, _nft_id} -> %> + <% {:NFT, nft_address, nft_id} -> %>
-
NFT
+
NFT + <%= if nft_id >= 1 do %> + (#<%= nft_id %>) + <% end %> +
<%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(nft_address)) do %> <%= Base.encode16(:binary.part(nft_address, 0, 6)) %>... From 6ec2882e930cd6b6a09ce2c7405959badbaa6fe3 Mon Sep 17 00:00:00 2001 From: Samuel Date: Thu, 23 Jun 2022 12:23:25 +0200 Subject: [PATCH 6/7] Rename NFT to token --- lib/archethic.ex | 4 +- lib/archethic/account.ex | 16 +- .../account/mem_tables/nft_ledger.ex | 203 ----------------- .../account/mem_tables/token_ledger.ex | 206 ++++++++++++++++++ lib/archethic/account/mem_tables_loader.ex | 14 +- .../contracts/contract/conditions.ex | 6 +- lib/archethic/contracts/contract/constants.ex | 33 +-- lib/archethic/contracts/interpreter.ex | 22 +- .../interpreter/transaction_statements.ex | 33 +-- lib/archethic/contracts/worker.ex | 20 +- lib/archethic/db/embedded_impl/encoding.ex | 12 +- lib/archethic/mining/fee.ex | 8 +- .../mining/pending_transaction_validation.ex | 8 +- lib/archethic/p2p/message.ex | 40 ++-- lib/archethic/p2p/message/balance.ex | 4 +- .../transaction_chain/transaction.ex | 26 +-- .../transaction/data/ledger.ex | 50 ++--- .../data/ledger/{nft.ex => token.ex} | 58 ++--- .../data/ledger/{nft => token}/transfer.ex | 77 ++++--- .../validation_stamp/ledger_operations.ex | 149 ++++++------- .../ledger_operations/transaction_movement.ex | 38 ++-- .../transaction_movement/type.ex | 12 +- .../ledger_operations/unspent_output.ex | 32 +-- lib/archethic/utils/regression/playbook.ex | 24 +- .../controllers/api/schema/ledger.ex | 6 +- .../schema/{nft_ledger.ex => token_ledger.ex} | 14 +- .../controllers/api/types/transaction_type.ex | 2 +- lib/archethic_web/graphql_schema/resolver.ex | 10 +- .../graphql_schema/transaction_type.ex | 55 ++--- .../explorer/transaction_details.html.leex | 58 ++--- ..._ledger_test.exs => token_ledger_test.exs} | 6 +- .../account/mem_tables_loader_test.exs | 12 +- test/archethic/account_test.exs | 20 +- test/archethic/bootstrap/sync_test.exs | 2 +- .../transaction_statements_test.exs | 4 +- test/archethic/contracts/interpreter_test.exs | 19 +- .../pending_transaction_validation_test.exs | 4 +- test/archethic/p2p/messages_test.exs | 12 +- .../transaction/validation_stamp_test.exs | 4 +- .../ledger/nft/transfer_test.exs | 4 +- .../transaction_data/ledger/nft_test.exs | 38 ---- .../transaction_data/ledger_test.exs | 2 +- .../transaction_data/token_test.exs | 38 ++++ .../transaction_chain/transaction_test.exs | 2 +- .../api/transaction_payload_test.exs | 48 ++-- test/archethic_web/graphql_schema_test.exs | 14 +- test/support/template.ex | 4 +- 47 files changed, 748 insertions(+), 725 deletions(-) delete mode 100644 lib/archethic/account/mem_tables/nft_ledger.ex create mode 100644 lib/archethic/account/mem_tables/token_ledger.ex rename lib/archethic/transaction_chain/transaction/data/ledger/{nft.ex => token.ex} (75%) rename lib/archethic/transaction_chain/transaction/data/ledger/{nft => token}/transfer.ex (67%) rename lib/archethic_web/controllers/api/schema/{nft_ledger.ex => token_ledger.ex} (63%) rename test/archethic/account/mem_tables/{nft_ledger_test.exs => token_ledger_test.exs} (59%) delete mode 100644 test/archethic/transaction_chain/transaction_data/ledger/nft_test.exs create mode 100644 test/archethic/transaction_chain/transaction_data/token_test.exs diff --git a/lib/archethic.ex b/lib/archethic.ex index 6a8c4e062..54ef7cb6f 100644 --- a/lib/archethic.ex +++ b/lib/archethic.ex @@ -151,8 +151,8 @@ defmodule Archethic do defp get_balance([node | rest], address) do case P2P.send_message(node, %GetBalance{address: address}) do - {:ok, %Balance{uco: uco, nft: nft}} -> - {:ok, %{uco: uco, nft: nft}} + {:ok, %Balance{uco: uco, token: token}} -> + {:ok, %{uco: uco, token: token}} {:error, _} -> get_balance(rest, address) diff --git a/lib/archethic/account.ex b/lib/archethic/account.ex index bdc895b92..4d8ce7440 100644 --- a/lib/archethic/account.ex +++ b/lib/archethic/account.ex @@ -1,7 +1,7 @@ defmodule Archethic.Account do @moduledoc false - alias __MODULE__.MemTables.NFTLedger + alias __MODULE__.MemTables.TokenLedger alias __MODULE__.MemTables.UCOLedger alias __MODULE__.MemTablesLoader @@ -13,7 +13,9 @@ defmodule Archethic.Account do @type balance :: %{ uco: amount :: pos_integer(), - nft: %{{address :: binary(), nft_id :: non_neg_integer()} => amount :: pos_integer()} + token: %{ + {address :: binary(), token_id :: non_neg_integer()} => amount :: pos_integer() + } } @doc """ @@ -23,12 +25,12 @@ defmodule Archethic.Account do def get_balance(address) when is_binary(address) do address |> get_unspent_outputs() - |> Enum.reduce(%{uco: 0, nft: %{}}, fn + |> Enum.reduce(%{uco: 0, token: %{}}, fn %UnspentOutput{type: :UCO, amount: amount}, acc -> Map.update!(acc, :uco, &(&1 + amount)) - %UnspentOutput{type: {:NFT, nft_address, nft_id}, amount: amount}, acc -> - update_in(acc, [:nft, Access.key({nft_address, nft_id}, 0)], &(&1 + amount)) + %UnspentOutput{type: {:token, token_address, token_id}, amount: amount}, acc -> + update_in(acc, [:token, Access.key({token_address, token_id}, 0)], &(&1 + amount)) end) end @@ -37,7 +39,7 @@ defmodule Archethic.Account do """ @spec get_unspent_outputs(binary()) :: list(UnspentOutput.t()) def get_unspent_outputs(address) do - UCOLedger.get_unspent_outputs(address) ++ NFTLedger.get_unspent_outputs(address) + UCOLedger.get_unspent_outputs(address) ++ TokenLedger.get_unspent_outputs(address) end @doc """ @@ -45,7 +47,7 @@ defmodule Archethic.Account do """ @spec get_inputs(binary()) :: list(TransactionInput.t()) def get_inputs(address) do - UCOLedger.get_inputs(address) ++ NFTLedger.get_inputs(address) + UCOLedger.get_inputs(address) ++ TokenLedger.get_inputs(address) end @doc """ diff --git a/lib/archethic/account/mem_tables/nft_ledger.ex b/lib/archethic/account/mem_tables/nft_ledger.ex deleted file mode 100644 index 71fe04723..000000000 --- a/lib/archethic/account/mem_tables/nft_ledger.ex +++ /dev/null @@ -1,203 +0,0 @@ -defmodule Archethic.Account.MemTables.NFTLedger do - @moduledoc false - - @ledger_table :archethic_nft_ledger - @unspent_output_index_table :archethic_nft_unspent_output_index - - alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput - alias Archethic.TransactionChain.TransactionInput - - use GenServer - - require Logger - - @doc """ - Initialize the NFT ledger tables: - - Main NFT ledger as ETS set ({nft, to, from, nft_id}, amount, spent?) - - NFT Unspent Output Index as ETS bag (to, {from, nft, nft_id}) - - ## Examples - - iex> {:ok, _} = NFTLedger.start_link() - iex> { :ets.info(:archethic_nft_ledger)[:type], :ets.info(:archethic_nft_unspent_output_index)[:type] } - { :set, :bag } - """ - def start_link(args \\ []) do - GenServer.start_link(__MODULE__, args) - end - - def init(_) do - Logger.info("Initialize InMemory NFT Ledger...") - - :ets.new(@ledger_table, [:set, :named_table, :public, read_concurrency: true]) - - :ets.new(@unspent_output_index_table, [ - :bag, - :named_table, - :public, - read_concurrency: true - ]) - - {:ok, - %{ - ledger_table: @ledger_table, - unspent_outputs_index_table: @unspent_output_index_table - }} - end - - @doc """ - Add an unspent output to the ledger for the recipient address - - ## Examples - - iex> {:ok, _pid} = NFTLedger.start_link() - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}}, ~U[2021-03-05 13:41:34Z]) - iex> { :ets.tab2list(:archethic_nft_ledger), :ets.tab2list(:archethic_nft_unspent_output_index) } - { - [ - {{"@Alice2", "@Charlie10", "@NFT1", 1}, 100_000_000, false, ~U[2021-03-05 13:41:34Z]}, - {{"@Alice2", "@Bob3", "@NFT1", 0}, 300_000_000, false, ~U[2021-03-05 13:41:34Z]} - ], - [ - {"@Alice2", "@Bob3", "@NFT1", 0}, - {"@Alice2", "@Charlie10", "@NFT1", 1} - ] - } - - """ - @spec add_unspent_output(binary(), UnspentOutput.t(), DateTime.t()) :: :ok - def add_unspent_output( - to_address, - %UnspentOutput{ - from: from_address, - amount: amount, - type: {:NFT, nft_address, nft_id} - }, - timestamp = %DateTime{} - ) - when is_binary(to_address) and is_binary(from_address) and is_integer(amount) and amount > 0 and - is_binary(nft_address) and is_integer(nft_id) and nft_id >= 0 do - true = - :ets.insert( - @ledger_table, - {{to_address, from_address, nft_address, nft_id}, amount, false, timestamp} - ) - - true = - :ets.insert(@unspent_output_index_table, {to_address, from_address, nft_address, nft_id}) - - Logger.info( - "#{amount} unspent NFT (#{Base.encode16(nft_address)}) added for #{Base.encode16(to_address)}", - transaction_address: Base.encode16(from_address) - ) - - :ok - end - - @doc """ - Get the unspent outputs for a given transaction address - - ## Examples - - iex> {:ok, _pid} = NFTLedger.start_link() - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}}, ~U[2021-03-05 13:41:34Z]) - iex> NFTLedger.get_unspent_outputs("@Alice2") - [ - %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}}, - %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}} - ] - - iex> {:ok, _pid} = NFTLedger.start_link() - iex> NFTLedger.get_unspent_outputs("@Alice2") - [] - """ - @spec get_unspent_outputs(binary()) :: list(UnspentOutput.t()) - def get_unspent_outputs(address) when is_binary(address) do - @unspent_output_index_table - |> :ets.lookup(address) - |> Enum.reduce([], fn {_, from, nft_address, nft_id}, acc -> - case :ets.lookup(@ledger_table, {address, from, nft_address, nft_id}) do - [{_, amount, false, _}] -> - [ - %UnspentOutput{ - from: from, - amount: amount, - type: {:NFT, nft_address, nft_id} - } - | acc - ] - - _ -> - acc - end - end) - end - - @doc """ - Spend all the unspent outputs for the given address - - ## Examples - - iex> {:ok, _pid} = NFTLedger.start_link() - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1",0}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1",1}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.spend_all_unspent_outputs("@Alice2") - iex> NFTLedger.get_unspent_outputs("@Alice2") - [] - - """ - @spec spend_all_unspent_outputs(binary()) :: :ok - def spend_all_unspent_outputs(address) do - @unspent_output_index_table - |> :ets.lookup(address) - |> Enum.each(fn {_, from, nft_address, nft_id} -> - :ets.update_element(@ledger_table, {address, from, nft_address, nft_id}, {3, true}) - end) - - :ok - end - - @doc """ - Retrieve the entire inputs for a given address (spent or unspent) - - ## Examples - - iex> {:ok, _pid} = NFTLedger.start_link() - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}}, ~U[2021-03-05 13:41:34Z]) - iex> NFTLedger.get_inputs("@Alice2") - [ - %TransactionInput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}, spent?: false, timestamp: ~U[2021-03-05 13:41:34Z]}, - %TransactionInput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}, spent?: false, timestamp: ~U[2021-03-05 13:41:34Z]} - ] - - iex> {:ok, _pid} = NFTLedger.start_link() - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}}, ~U[2021-03-05 13:41:34Z]) - iex> :ok = NFTLedger.spend_all_unspent_outputs("@Alice2") - iex> NFTLedger.get_inputs("@Alice2") - [ - %TransactionInput{from: "@Bob3", amount: 300_000_000, type: {:NFT, "@NFT1", 0}, spent?: true, timestamp: ~U[2021-03-05 13:41:34Z]}, - %TransactionInput{from: "@Charlie10", amount: 100_000_000, type: {:NFT, "@NFT1", 1}, spent?: true, timestamp: ~U[2021-03-05 13:41:34Z]} - ] - """ - @spec get_inputs(binary()) :: list(TransactionInput.t()) - def get_inputs(address) when is_binary(address) do - @unspent_output_index_table - |> :ets.lookup(address) - |> Enum.map(fn {_, from, nft_address, nft_id} -> - [{_, amount, spent?, timestamp}] = - :ets.lookup(@ledger_table, {address, from, nft_address, nft_id}) - - %TransactionInput{ - from: from, - amount: amount, - type: {:NFT, nft_address, nft_id}, - spent?: spent?, - timestamp: timestamp - } - end) - end -end diff --git a/lib/archethic/account/mem_tables/token_ledger.ex b/lib/archethic/account/mem_tables/token_ledger.ex new file mode 100644 index 000000000..803a704e6 --- /dev/null +++ b/lib/archethic/account/mem_tables/token_ledger.ex @@ -0,0 +1,206 @@ +defmodule Archethic.Account.MemTables.TokenLedger do + @moduledoc false + + @ledger_table :archethic_token_ledger + @unspent_output_index_table :archethic_token_unspent_output_index + + alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput + alias Archethic.TransactionChain.TransactionInput + + use GenServer + + require Logger + + @doc """ + Initialize the Token ledger tables: + - Main Token ledger as ETS set ({token, to, from, token_id}, amount, spent?) + - Token Unspent Output Index as ETS bag (to, {from, token, token_id}) + + ## Examples + + iex> {:ok, _} = TokenLedger.start_link() + iex> { :ets.info(:archethic_token_ledger)[:type], :ets.info(:archethic_token_unspent_output_index)[:type] } + { :set, :bag } + """ + def start_link(args \\ []) do + GenServer.start_link(__MODULE__, args) + end + + def init(_) do + Logger.info("Initialize InMemory Token Ledger...") + + :ets.new(@ledger_table, [:set, :named_table, :public, read_concurrency: true]) + + :ets.new(@unspent_output_index_table, [ + :bag, + :named_table, + :public, + read_concurrency: true + ]) + + {:ok, + %{ + ledger_table: @ledger_table, + unspent_outputs_index_table: @unspent_output_index_table + }} + end + + @doc """ + Add an unspent output to the ledger for the recipient address + + ## Examples + + iex> {:ok, _pid} = TokenLedger.start_link() + iex> :ok = TokenLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:token, "@Token1", 0}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = TokenLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:token, "@Token1", 1}}, ~U[2021-03-05 13:41:34Z]) + iex> { :ets.tab2list(:archethic_token_ledger), :ets.tab2list(:archethic_token_unspent_output_index) } + { + [ + {{"@Alice2", "@Bob3", "@Token1", 0}, 300_000_000, false, ~U[2021-03-05 13:41:34Z]}, + {{"@Alice2", "@Charlie10", "@Token1", 1}, 100_000_000, false, ~U[2021-03-05 13:41:34Z]} + ], + [ + {"@Alice2", "@Bob3", "@Token1", 0}, + {"@Alice2", "@Charlie10", "@Token1", 1} + ] + } + + """ + @spec add_unspent_output(binary(), UnspentOutput.t(), DateTime.t()) :: :ok + def add_unspent_output( + to_address, + %UnspentOutput{ + from: from_address, + amount: amount, + type: {:token, token_address, token_id} + }, + timestamp = %DateTime{} + ) + when is_binary(to_address) and is_binary(from_address) and is_integer(amount) and amount > 0 and + is_binary(token_address) and is_integer(token_id) and token_id >= 0 do + true = + :ets.insert( + @ledger_table, + {{to_address, from_address, token_address, token_id}, amount, false, timestamp} + ) + + true = + :ets.insert( + @unspent_output_index_table, + {to_address, from_address, token_address, token_id} + ) + + Logger.info( + "#{amount} unspent Token (#{Base.encode16(token_address)}) added for #{Base.encode16(to_address)}", + transaction_address: Base.encode16(from_address) + ) + + :ok + end + + @doc """ + Get the unspent outputs for a given transaction address + + ## Examples + + iex> {:ok, _pid} = TokenLedger.start_link() + iex> :ok = TokenLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:token, "@Token1", 0}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = TokenLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:token, "@Token1", 1}}, ~U[2021-03-05 13:41:34Z]) + iex> TokenLedger.get_unspent_outputs("@Alice2") + [ + %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:token, "@Token1", 1}}, + %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:token, "@Token1", 0}} + ] + + iex> {:ok, _pid} = TokenLedger.start_link() + iex> TokenLedger.get_unspent_outputs("@Alice2") + [] + """ + @spec get_unspent_outputs(binary()) :: list(UnspentOutput.t()) + def get_unspent_outputs(address) when is_binary(address) do + @unspent_output_index_table + |> :ets.lookup(address) + |> Enum.reduce([], fn {_, from, token_address, token_id}, acc -> + case :ets.lookup(@ledger_table, {address, from, token_address, token_id}) do + [{_, amount, false, _}] -> + [ + %UnspentOutput{ + from: from, + amount: amount, + type: {:token, token_address, token_id} + } + | acc + ] + + _ -> + acc + end + end) + end + + @doc """ + Spend all the unspent outputs for the given address + + ## Examples + + iex> {:ok, _pid} = TokenLedger.start_link() + iex> :ok = TokenLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:token, "@Token1",0}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = TokenLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:token, "@Token1",1}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = TokenLedger.spend_all_unspent_outputs("@Alice2") + iex> TokenLedger.get_unspent_outputs("@Alice2") + [] + + """ + @spec spend_all_unspent_outputs(binary()) :: :ok + def spend_all_unspent_outputs(address) do + @unspent_output_index_table + |> :ets.lookup(address) + |> Enum.each(fn {_, from, token_address, token_id} -> + :ets.update_element(@ledger_table, {address, from, token_address, token_id}, {3, true}) + end) + + :ok + end + + @doc """ + Retrieve the entire inputs for a given address (spent or unspent) + + ## Examples + + iex> {:ok, _pid} = TokenLedger.start_link() + iex> :ok = TokenLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:token, "@Token1", 0}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = TokenLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:token, "@Token1", 1}}, ~U[2021-03-05 13:41:34Z]) + iex> TokenLedger.get_inputs("@Alice2") + [ + %TransactionInput{from: "@Bob3", amount: 300_000_000, type: {:token, "@Token1", 0}, spent?: false, timestamp: ~U[2021-03-05 13:41:34Z]}, + %TransactionInput{from: "@Charlie10", amount: 100_000_000, type: {:token, "@Token1", 1}, spent?: false, timestamp: ~U[2021-03-05 13:41:34Z]} + ] + + iex> {:ok, _pid} = TokenLedger.start_link() + iex> :ok = TokenLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Bob3", amount: 300_000_000, type: {:token, "@Token1", 0}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = TokenLedger.add_unspent_output("@Alice2", %UnspentOutput{from: "@Charlie10", amount: 100_000_000, type: {:token, "@Token1", 1}}, ~U[2021-03-05 13:41:34Z]) + iex> :ok = TokenLedger.spend_all_unspent_outputs("@Alice2") + iex> TokenLedger.get_inputs("@Alice2") + [ + %TransactionInput{from: "@Bob3", amount: 300_000_000, type: {:token, "@Token1", 0}, spent?: true, timestamp: ~U[2021-03-05 13:41:34Z]}, + %TransactionInput{from: "@Charlie10", amount: 100_000_000, type: {:token, "@Token1", 1}, spent?: true, timestamp: ~U[2021-03-05 13:41:34Z]} + ] + """ + @spec get_inputs(binary()) :: list(TransactionInput.t()) + def get_inputs(address) when is_binary(address) do + @unspent_output_index_table + |> :ets.lookup(address) + |> Enum.map(fn {_, from, token_address, token_id} -> + [{_, amount, spent?, timestamp}] = + :ets.lookup(@ledger_table, {address, from, token_address, token_id}) + + %TransactionInput{ + from: from, + amount: amount, + type: {:token, token_address, token_id}, + spent?: spent?, + timestamp: timestamp + } + end) + end +end diff --git a/lib/archethic/account/mem_tables_loader.ex b/lib/archethic/account/mem_tables_loader.ex index 16d45fd66..414ebc60b 100644 --- a/lib/archethic/account/mem_tables_loader.ex +++ b/lib/archethic/account/mem_tables_loader.ex @@ -3,7 +3,7 @@ defmodule Archethic.Account.MemTablesLoader do use GenServer - alias Archethic.Account.MemTables.NFTLedger + alias Archethic.Account.MemTables.TokenLedger alias Archethic.Account.MemTables.UCOLedger alias Archethic.Crypto @@ -72,7 +72,7 @@ defmodule Archethic.Account.MemTablesLoader do previous_address = Crypto.derive_address(previous_public_key) UCOLedger.spend_all_unspent_outputs(previous_address) - NFTLedger.spend_all_unspent_outputs(previous_address) + TokenLedger.spend_all_unspent_outputs(previous_address) :ok = set_transaction_movements(address, transaction_movements, timestamp) :ok = set_unspent_outputs(address, unspent_outputs, timestamp) @@ -95,13 +95,13 @@ defmodule Archethic.Account.MemTablesLoader do timestamp ) - %TransactionMovement{to: to, amount: amount, type: {:NFT, nft_address, nft_id}} -> - NFTLedger.add_unspent_output( + %TransactionMovement{to: to, amount: amount, type: {:token, token_address, token_id}} -> + TokenLedger.add_unspent_output( to, %UnspentOutput{ amount: amount, from: address, - type: {:NFT, nft_address, nft_id} + type: {:token, token_address, token_id} }, timestamp ) @@ -115,8 +115,8 @@ defmodule Archethic.Account.MemTablesLoader do unspent_output = %UnspentOutput{type: :UCO} -> UCOLedger.add_unspent_output(address, unspent_output, timestamp) - unspent_output = %UnspentOutput{type: {:NFT, _nft_address, _nft_id}} -> - NFTLedger.add_unspent_output(address, unspent_output, timestamp) + unspent_output = %UnspentOutput{type: {:token, _token_address, _token_id}} -> + TokenLedger.add_unspent_output(address, unspent_output, timestamp) end) end end diff --git a/lib/archethic/contracts/contract/conditions.ex b/lib/archethic/contracts/contract/conditions.ex index 30362c8a2..81b9b97fa 100644 --- a/lib/archethic/contracts/contract/conditions.ex +++ b/lib/archethic/contracts/contract/conditions.ex @@ -10,7 +10,7 @@ defmodule Archethic.Contracts.Contract.Conditions do :authorized_keys, :secrets, :uco_transfers, - :nft_transfers, + :token_transfers, :previous_public_key, origin_family: :all ] @@ -25,7 +25,7 @@ defmodule Archethic.Contracts.Contract.Conditions do authorized_keys: map() | Macro.t() | nil, secrets: list(binary()) | Macro.t() | nil, uco_transfers: map() | Macro.t() | nil, - nft_transfers: map() | Macro.t() | nil, + token_transfers: map() | Macro.t() | nil, previous_public_key: binary() | Macro.t() | nil, origin_family: SharedSecrets.origin_family() | :all } @@ -37,7 +37,7 @@ defmodule Archethic.Contracts.Contract.Conditions do authorized_keys: nil, secrets: nil, uco_transfers: nil, - nft_transfers: nil, + token_transfers: nil, previous_public_key: nil }), do: true diff --git a/lib/archethic/contracts/contract/constants.ex b/lib/archethic/contracts/contract/constants.ex index d40bac3a8..b4c060159 100644 --- a/lib/archethic/contracts/contract/constants.ex +++ b/lib/archethic/contracts/contract/constants.ex @@ -13,8 +13,8 @@ defmodule Archethic.Contracts.Contract.Constants do alias Archethic.TransactionChain.Transaction alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.Ledger - alias Archethic.TransactionChain.TransactionData.NFTLedger - alias Archethic.TransactionChain.TransactionData.NFTLedger.Transfer, as: NFTTransfer + alias Archethic.TransactionChain.TransactionData.TokenLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger.Transfer, as: TokenTransfer alias Archethic.TransactionChain.TransactionData.Ownership alias Archethic.TransactionChain.TransactionData.UCOLedger alias Archethic.TransactionChain.TransactionData.UCOLedger.Transfer, as: UCOTransfer @@ -35,8 +35,8 @@ defmodule Archethic.Contracts.Contract.Constants do uco: %UCOLedger{ transfers: uco_transfers }, - nft: %NFTLedger{ - transfers: nft_transfers + token: %TokenLedger{ + transfers: token_transfers } }, recipients: recipients @@ -61,15 +61,15 @@ defmodule Archethic.Contracts.Contract.Constants do |> Enum.map(fn %UCOTransfer{to: to, amount: amount} -> %{"to" => to, "amount" => amount} end), - "nft_transfers" => - nft_transfers - |> Enum.map(fn %NFTTransfer{ + "token_transfers" => + token_transfers + |> Enum.map(fn %TokenTransfer{ to: to, amount: amount, - nft: nft_address, - nft_id: nft_id + token: token_address, + token_id: token_id } -> - %{"to" => to, "amount" => amount, "nft" => nft_address, "nft_id" => nft_id} + %{"to" => to, "amount" => amount, "token" => token_address, "token_id" => token_id} end) } end @@ -110,12 +110,17 @@ defmodule Archethic.Contracts.Contract.Constants do %UCOTransfer{to: to, amount: amount} end) }, - nft: %NFTLedger{ + token: %TokenLedger{ transfers: constants - |> Map.get("nft_transfers", []) - |> Enum.map(fn %{"to" => to, "amount" => amount, "nft" => nft, "nft_id" => nft_id} -> - %NFTTransfer{to: to, amount: amount, nft: nft, nft_id: nft_id} + |> Map.get("token_transfers", []) + |> Enum.map(fn %{ + "to" => to, + "amount" => amount, + "token" => token, + "token_id" => token_id + } -> + %TokenTransfer{to: to, amount: amount, token: token, token_id: token_id} end) } } diff --git a/lib/archethic/contracts/interpreter.ex b/lib/archethic/contracts/interpreter.ex index c24416d32..a4a821890 100644 --- a/lib/archethic/contracts/interpreter.ex +++ b/lib/archethic/contracts/interpreter.ex @@ -33,9 +33,9 @@ defmodule Archethic.Contracts.Interpreter do "keys", "code", "uco_ledger", - "nft_ledger", + "token_ledger", "uco_transfers", - "nft_transfers", + "token_transfers", "authorized_public_keys", "secrets", "recipients" @@ -95,7 +95,7 @@ defmodule Archethic.Contracts.Interpreter do }, authorized_keys: nil, code: nil, - nft_transfers: nil, + token_transfers: nil, origin_family: :all, previous_public_key: nil, type: nil, @@ -119,7 +119,7 @@ defmodule Archethic.Contracts.Interpreter do }, authorized_keys: nil, code: nil, - nft_transfers: nil, + token_transfers: nil, origin_family: :all, previous_public_key: nil, type: nil, @@ -137,7 +137,7 @@ defmodule Archethic.Contracts.Interpreter do origin_family: :biometric, authorized_keys: nil, code: nil, - nft_transfers: nil, + token_transfers: nil, previous_public_key: nil, type: nil, uco_transfers: nil @@ -691,24 +691,24 @@ defmodule Archethic.Contracts.Interpreter do when arg in ["to", "amount"], do: {node, acc} - # Whitelist the add_nft_tranfser argument list + # Whitelist the add_token_tranfser argument list defp prewalk( node = [ {{:atom, "to"}, _to}, {{:atom, "amount"}, _amount}, - {{:atom, "nft"}, _nft_address}, - {{:atom, "nft_id"}, _nft_id} + {{:atom, "token"}, _token_address}, + {{:atom, "token_id"}, _token_id} ], - acc = {:ok, %{scope: {:function, "add_nft_transfer", :actions}}} + acc = {:ok, %{scope: {:function, "add_token_transfer", :actions}}} ) do {node, acc} end defp prewalk( node = {{:atom, arg}, _}, - acc = {:ok, %{scope: {:function, "add_nft_transfer", :actions}}} + acc = {:ok, %{scope: {:function, "add_token_transfer", :actions}}} ) - when arg in ["to", "amount", "nft", "nft_id"], + when arg in ["to", "amount", "token", "token_id"], do: {node, acc} # Whitelist the add_ownership argument list diff --git a/lib/archethic/contracts/interpreter/transaction_statements.ex b/lib/archethic/contracts/interpreter/transaction_statements.ex index d6d963d9d..39d58cf0f 100644 --- a/lib/archethic/contracts/interpreter/transaction_statements.ex +++ b/lib/archethic/contracts/interpreter/transaction_statements.ex @@ -2,7 +2,7 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do @moduledoc false alias Archethic.TransactionChain.Transaction - alias Archethic.TransactionChain.TransactionData.NFTLedger.Transfer, as: NFTTransfer + alias Archethic.TransactionChain.TransactionData.TokenLedger.Transfer, as: TokenTransfer alias Archethic.TransactionChain.TransactionData.Ownership alias Archethic.TransactionChain.TransactionData.UCOLedger.Transfer, as: UCOTransfer @@ -52,28 +52,28 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do end @doc """ - Add a NFT transfer + Add a token transfer ## Examples - iex> TransactionStatements.add_nft_transfer(%Transaction{data: %TransactionData{}}, [ + iex> TransactionStatements.add_token_transfer(%Transaction{data: %TransactionData{}}, [ ...> {"to", "22368B50D3B2976787CFCC27508A8E8C67483219825F998FC9D6908D54D0FE10"}, ...> {"amount", 1_000_000_000}, - ...> {"nft", "70541604258A94B76DB1F1AF5A2FC2BEF165F3BD9C6B7DDB3F1ACC628465E528"}, - ...> {"nft_id", 0} + ...> {"token", "70541604258A94B76DB1F1AF5A2FC2BEF165F3BD9C6B7DDB3F1ACC628465E528"}, + ...> {"token_id", 0} ...> ]) %Transaction{ data: %TransactionData{ ledger: %Ledger{ - nft: %NFTLedger{ + token: %TokenLedger{ transfers: [ - %NFTTransfer{ + %TokenTransfer{ to: <<34, 54, 139, 80, 211, 178, 151, 103, 135, 207, 204, 39, 80, 138, 142, 140, 103, 72, 50, 25, 130, 95, 153, 143, 201, 214, 144, 141, 84, 208, 254, 16>>, amount: 1_000_000_000, - nft: <<112, 84, 22, 4, 37, 138, 148, 183, 109, 177, 241, 175, 90, 47, 194, 190, 241, 101, 243, + token: <<112, 84, 22, 4, 37, 138, 148, 183, 109, 177, 241, 175, 90, 47, 194, 190, 241, 101, 243, 189, 156, 107, 125, 219, 63, 26, 204, 98, 132, 101, 229, 40>>, - nft_id: 0 + token_id: 0 } ] } @@ -81,19 +81,20 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do } } """ - @spec add_nft_transfer(Transaction.t(), list()) :: Transaction.t() - def add_nft_transfer(tx = %Transaction{}, args) when is_list(args) do - %{"to" => to, "amount" => amount, "nft" => nft, "nft_id" => nft_id} = Enum.into(args, %{}) + @spec add_token_transfer(Transaction.t(), list()) :: Transaction.t() + def add_token_transfer(tx = %Transaction{}, args) when is_list(args) do + %{"to" => to, "amount" => amount, "token" => token, "token_id" => token_id} = + Enum.into(args, %{}) update_in( tx, - [Access.key(:data), Access.key(:ledger), Access.key(:nft), Access.key(:transfers)], + [Access.key(:data), Access.key(:ledger), Access.key(:token), Access.key(:transfers)], &[ - %NFTTransfer{ - nft_id: nft_id, + %TokenTransfer{ + token_id: token_id, to: decode_binary(to), amount: amount, - nft: decode_binary(nft) + token: decode_binary(token) } | &1 ] diff --git a/lib/archethic/contracts/worker.ex b/lib/archethic/contracts/worker.ex index 2c3be0054..5c40d1b0c 100644 --- a/lib/archethic/contracts/worker.ex +++ b/lib/archethic/contracts/worker.ex @@ -394,18 +394,18 @@ defmodule Archethic.Contracts.Worker do end defp ensure_enough_funds(next_transaction, contract_address) do - %{uco: uco_to_transfer, nft: nft_to_transfer} = + %{uco: uco_to_transfer, token: token_to_transfer} = next_transaction |> Transaction.get_movements() - |> Enum.reduce(%{uco: 0, nft: %{}}, fn + |> Enum.reduce(%{uco: 0, token: %{}}, fn %TransactionMovement{type: :UCO, amount: amount}, acc -> Map.update!(acc, :uco, &(&1 + amount)) - %TransactionMovement{type: {:NFT, nft_address, nft_id}, amount: amount}, acc -> - Map.update!(acc, :nft, &Map.put(&1, {nft_address, nft_id}, amount)) + %TransactionMovement{type: {:token, token_address, token_id}, amount: amount}, acc -> + Map.update!(acc, :token, &Map.put(&1, {token_address, token_id}, amount)) end) - %{uco: uco_balance, nft: nft_balances} = Account.get_balance(contract_address) + %{uco: uco_balance, token: token_balances} = Account.get_balance(contract_address) uco_usd_price = DateTime.utc_now() @@ -420,10 +420,10 @@ defmodule Archethic.Contracts.Worker do with true <- uco_balance > uco_to_transfer + tx_fee, true <- - Enum.all?(nft_to_transfer, fn {{t_nft_address, t_nft_id}, t_amount} -> - {{_nft_address, _nft_id}, balance} = - Enum.find(nft_balances, fn {{f_nft_address, f_nft_id}, _f_amount} -> - f_nft_address == t_nft_address and f_nft_id == t_nft_id + Enum.all?(token_to_transfer, fn {{t_token_address, t_token_id}, t_amount} -> + {{_token_address, _token_id}, balance} = + Enum.find(token_balances, fn {{f_token_address, f_token_id}, _f_amount} -> + f_token_address == t_token_address and f_token_id == t_token_id end) balance > t_amount @@ -432,7 +432,7 @@ defmodule Archethic.Contracts.Worker do else false -> Logger.debug( - "Not enough funds to submit the transaction - expected %{ UCO: #{uco_to_transfer + tx_fee}, nft: #{inspect(nft_to_transfer)}} - got: %{ UCO: #{uco_balance}, nft: #{inspect(nft_balances)}}", + "Not enough funds to submit the transaction - expected %{ UCO: #{uco_to_transfer + tx_fee}, token: #{inspect(token_to_transfer)}} - got: %{ UCO: #{uco_balance}, token: #{inspect(token_balances)}}", contract: Base.encode16(contract_address) ) diff --git a/lib/archethic/db/embedded_impl/encoding.ex b/lib/archethic/db/embedded_impl/encoding.ex index b1a9bce17..61c0ee700 100644 --- a/lib/archethic/db/embedded_impl/encoding.ex +++ b/lib/archethic/db/embedded_impl/encoding.ex @@ -8,7 +8,7 @@ defmodule Archethic.DB.EmbeddedImpl.Encoding do alias Archethic.TransactionChain.TransactionData.Ledger alias Archethic.TransactionChain.TransactionData.Ownership alias Archethic.TransactionChain.TransactionData.UCOLedger - alias Archethic.TransactionChain.TransactionData.NFTLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger alias Archethic.TransactionChain.Transaction.ValidationStamp alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput @@ -31,7 +31,7 @@ defmodule Archethic.DB.EmbeddedImpl.Encoding do content: content, code: code, ownerships: ownerships, - ledger: %Ledger{uco: uco_ledger, nft: nft_ledger}, + ledger: %Ledger{uco: uco_ledger, token: token_ledger}, recipients: recipients }, previous_public_key: previous_public_key, @@ -79,7 +79,7 @@ defmodule Archethic.DB.EmbeddedImpl.Encoding do {"data.content", content}, {"data.code", code}, {"data.ledger.uco", UCOLedger.serialize(uco_ledger)}, - {"data.ledger.nft", NFTLedger.serialize(nft_ledger)}, + {"data.ledger.token", TokenLedger.serialize(token_ledger)}, {"data.ownerships", <>}, {"data.recipients", <>}, @@ -131,9 +131,9 @@ defmodule Archethic.DB.EmbeddedImpl.Encoding do put_in(acc, [Access.key(:data, %{}), Access.key(:ledger, %{}), :uco], uco_ledger) end - def decode(_version, "data.ledger.nft", data, acc) do - {nft_ledger, _} = NFTLedger.deserialize(data) - put_in(acc, [Access.key(:data, %{}), Access.key(:ledger, %{}), :nft], nft_ledger) + def decode(_version, "data.ledger.token", data, acc) do + {token_ledger, _} = TokenLedger.deserialize(data) + put_in(acc, [Access.key(:data, %{}), Access.key(:ledger, %{}), :token], token_ledger) end def decode(_version, "data.recipients", <<0>>, acc), do: acc diff --git a/lib/archethic/mining/fee.ex b/lib/archethic/mining/fee.ex index c8d6760aa..e55c6a1b5 100644 --- a/lib/archethic/mining/fee.ex +++ b/lib/archethic/mining/fee.ex @@ -11,7 +11,7 @@ defmodule Archethic.Mining.Fee do alias Archethic.TransactionChain.Transaction alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.Ledger - alias Archethic.TransactionChain.TransactionData.NFTLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger alias Archethic.TransactionChain.TransactionData.UCOLedger @unit_uco 100_000_000 @@ -66,7 +66,7 @@ defmodule Archethic.Mining.Fee do end defp get_additional_fee( - %Transaction{type: :nft, data: %TransactionData{content: content}}, + %Transaction{type: :token, data: %TransactionData{content: content}}, uco_price_in_usd ) do with {:ok, json} <- Jason.decode(content), @@ -101,11 +101,11 @@ defmodule Archethic.Mining.Fee do data: %TransactionData{ ledger: %Ledger{ uco: %UCOLedger{transfers: uco_transfers}, - nft: %NFTLedger{transfers: nft_transfers} + token: %TokenLedger{transfers: token_transfers} } } }) do - (uco_transfers ++ nft_transfers) + (uco_transfers ++ token_transfers) |> Enum.uniq_by(& &1.to) |> length() end diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index e799a2f03..3063cb638 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -304,7 +304,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do end defp do_accept_transaction(%Transaction{ - type: :nft, + type: :token, data: %TransactionData{content: content} }) do schema = @@ -321,15 +321,15 @@ defmodule Archethic.Mining.PendingTransactionValidation do :ok else {:error, reason} -> - Logger.debug("Invalid NFT token specification: #{inspect(reason)}") - {:error, "Invalid NFT transaction - Invalid specification"} + Logger.debug("Invalid token token specification: #{inspect(reason)}") + {:error, "Invalid token transaction - Invalid specification"} %{"type" => "fungible"} -> :ok %{"type" => "non-fungible"} -> {:error, - "Invalid NFT transaction - Supply should match properties for non-fungible tokens"} + "Invalid token transaction - Supply should match properties for non-fungible tokens"} end end diff --git a/lib/archethic/p2p/message.ex b/lib/archethic/p2p/message.ex index 658eee3b5..79e68d796 100644 --- a/lib/archethic/p2p/message.ex +++ b/lib/archethic/p2p/message.ex @@ -426,16 +426,16 @@ defmodule Archethic.P2P.Message do <<247::8, byte_size(digest)::8, digest::binary>> end - def encode(%Balance{uco: uco_balance, nft: nft_balances}) do - nft_balances_binary = - nft_balances - |> Enum.reduce([], fn {{nft_address, nft_id}, amount}, acc -> - [<> | acc] + def encode(%Balance{uco: uco_balance, token: token_balances}) do + token_balances_binary = + token_balances + |> Enum.reduce([], fn {{token_address, token_id}, amount}, acc -> + [<> | acc] end) |> Enum.reverse() |> :erlang.list_to_binary() - <<248::8, uco_balance::float, map_size(nft_balances)::16, nft_balances_binary::binary>> + <<248::8, uco_balance::float, map_size(token_balances)::16, token_balances_binary::binary>> end def encode(%NodeList{nodes: nodes}) do @@ -876,12 +876,12 @@ defmodule Archethic.P2P.Message do }, rest} end - def decode(<<248::8, uco_balance::float, nb_nft_balances::16, rest::bitstring>>) do - {nft_balances, rest} = deserialize_nft_balances(rest, nb_nft_balances, %{}) + def decode(<<248::8, uco_balance::float, nb_token_balances::16, rest::bitstring>>) do + {token_balances, rest} = deserialize_token_balances(rest, nb_token_balances, %{}) {%Balance{ uco: uco_balance, - nft: nft_balances + token: token_balances }, rest} end @@ -973,16 +973,22 @@ defmodule Archethic.P2P.Message do deserialize_transaction_inputs(rest, nb_inputs, [input | acc]) end - defp deserialize_nft_balances(rest, 0, _acc), do: {%{}, rest} + defp deserialize_token_balances(rest, 0, _acc), do: {%{}, rest} - defp deserialize_nft_balances(rest, nft_balances, acc) when map_size(acc) == nft_balances do + defp deserialize_token_balances(rest, token_balances, acc) + when map_size(acc) == token_balances do {acc, rest} end - defp deserialize_nft_balances(rest, nb_nft_balances, acc) do - {nft_address, <>} = Utils.deserialize_address(rest) - <> = rest - deserialize_nft_balances(rest, nb_nft_balances, Map.put(acc, {nft_address, nft_id}, amount)) + defp deserialize_token_balances(rest, nb_token_balances, acc) do + {token_address, <>} = Utils.deserialize_address(rest) + <> = rest + + deserialize_token_balances( + rest, + nb_token_balances, + Map.put(acc, {token_address, token_id}, amount) + ) end defp deserialize_summaries(rest, 0, _), do: {[], rest} @@ -1190,11 +1196,11 @@ defmodule Archethic.P2P.Message do end def process(%GetBalance{address: address}) do - %{uco: uco, nft: nft} = Account.get_balance(address) + %{uco: uco, token: token} = Account.get_balance(address) %Balance{ uco: uco, - nft: nft + token: token } end diff --git a/lib/archethic/p2p/message/balance.ex b/lib/archethic/p2p/message/balance.ex index 7f891b561..6648f4f29 100644 --- a/lib/archethic/p2p/message/balance.ex +++ b/lib/archethic/p2p/message/balance.ex @@ -2,10 +2,10 @@ defmodule Archethic.P2P.Message.Balance do @moduledoc """ Represents a message with the balance of a transaction """ - defstruct uco: 0, nft: %{} + defstruct uco: 0, token: %{} @type t :: %__MODULE__{ uco: non_neg_integer(), - nft: %{{binary(), non_neg_integer()} => non_neg_integer()} + token: %{{binary(), non_neg_integer()} => non_neg_integer()} } end diff --git a/lib/archethic/transaction_chain/transaction.ex b/lib/archethic/transaction_chain/transaction.ex index b0326f707..2444779ab 100755 --- a/lib/archethic/transaction_chain/transaction.ex +++ b/lib/archethic/transaction_chain/transaction.ex @@ -13,7 +13,7 @@ defmodule Archethic.TransactionChain.Transaction do alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.Ledger - alias Archethic.TransactionChain.TransactionData.NFTLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger alias Archethic.TransactionChain.TransactionData.UCOLedger alias Archethic.Utils @@ -72,7 +72,7 @@ defmodule Archethic.TransactionChain.Transaction do | :keychain | :keychain_access | :transfer - | :nft + | :token | :hosting | :origin @@ -90,7 +90,7 @@ defmodule Archethic.TransactionChain.Transaction do :keychain_access, :transfer, :hosting, - :nft, + :token, :origin ] @@ -314,7 +314,7 @@ defmodule Archethic.TransactionChain.Transaction do def serialize_type(:keychain_access), do: 254 def serialize_type(:transfer), do: 253 def serialize_type(:hosting), do: 252 - def serialize_type(:nft), do: 251 + def serialize_type(:token), do: 251 @doc """ Parse a serialize transaction type @@ -337,7 +337,7 @@ defmodule Archethic.TransactionChain.Transaction do def parse_type(254), do: :keychain_access def parse_type(253), do: :transfer def parse_type(252), do: :hosting - def parse_type(251), do: :nft + def parse_type(251), do: :token @doc """ Determines if a transaction type is a network one @@ -374,9 +374,9 @@ defmodule Archethic.TransactionChain.Transaction do ...> %UCOLedger.Transfer{to: "@Alice1", amount: 10} ...> ] ...> }, - ...> nft: %NFTLedger{ + ...> token: %TokenLedger{ ...> transfers: [ - ...> %NFTLedger.Transfer{to: "@Alice1", amount: 3, nft: "@BobNFT", nft_id: 0} + ...> %TokenLedger.Transfer{to: "@Alice1", amount: 3, token: "@BobToken", token_id: 0} ...> ] ...> } ...> } @@ -387,7 +387,7 @@ defmodule Archethic.TransactionChain.Transaction do to: "@Alice1", amount: 10, type: :UCO, }, %TransactionMovement{ - to: "@Alice1", amount: 3, type: {:NFT, "@BobNFT", 0}, + to: "@Alice1", amount: 3, type: {:Token, "@BobToken", 0}, } ] """ @@ -396,14 +396,14 @@ defmodule Archethic.TransactionChain.Transaction do data: %TransactionData{ ledger: %Ledger{ uco: %UCOLedger{transfers: uco_transfers}, - nft: %NFTLedger{transfers: nft_transfers} + token: %TokenLedger{transfers: token_transfers} } } }) do Enum.map(uco_transfers, &%TransactionMovement{to: &1.to, amount: &1.amount, type: :UCO}) ++ Enum.map( - nft_transfers, - &%TransactionMovement{to: &1.to, amount: &1.amount, type: {:NFT, &1.nft, &1.nft_id}} + token_transfers, + &%TransactionMovement{to: &1.to, amount: &1.amount, type: {:Token, &1.token, &1.token_id}} ) end @@ -499,7 +499,7 @@ defmodule Archethic.TransactionChain.Transaction do ...> 124, 88, 206, 36, 125, 163, 108, 229, 219, 181, 143, 253, 246, 237, 238, ...> 21, 79, 9, 230, 172, 0, 95, 0, 0, 0, 0, 0>>, ...> ledger: %Archethic.TransactionChain.TransactionData.Ledger{ - ...> nft: %Archethic.TransactionChain.TransactionData.NFTLedger{transfers: []}, + ...> token: %Archethic.TransactionChain.TransactionData.TokenLedger{transfers: []}, ...> uco: %Archethic.TransactionChain.TransactionData.UCOLedger{transfers: []} ...> }, ...> ownerships: [], @@ -697,7 +697,7 @@ defmodule Archethic.TransactionChain.Transaction do 124, 88, 206, 36, 125, 163, 108, 229, 219, 181, 143, 253, 246, 237, 238, 21, 79, 9, 230, 172, 0, 95, 0, 0, 0, 0, 0>>, ledger: %Archethic.TransactionChain.TransactionData.Ledger{ - nft: %Archethic.TransactionChain.TransactionData.NFTLedger{transfers: []}, + token: %Archethic.TransactionChain.TransactionData.TokenLedger{transfers: []}, uco: %Archethic.TransactionChain.TransactionData.UCOLedger{transfers: []} }, ownerships: [], diff --git a/lib/archethic/transaction_chain/transaction/data/ledger.ex b/lib/archethic/transaction_chain/transaction/data/ledger.ex index 0f890b119..c0519571b 100755 --- a/lib/archethic/transaction_chain/transaction/data/ledger.ex +++ b/lib/archethic/transaction_chain/transaction/data/ledger.ex @@ -2,10 +2,10 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do @moduledoc """ Represents transaction ledger movements """ - alias Archethic.TransactionChain.TransactionData.NFTLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger alias Archethic.TransactionChain.TransactionData.UCOLedger - defstruct uco: %UCOLedger{}, nft: %NFTLedger{} + defstruct uco: %UCOLedger{}, token: %TokenLedger{} @typedoc """ Ledger movements are composed from: @@ -13,7 +13,7 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do """ @type t :: %__MODULE__{ uco: UCOLedger.t(), - nft: NFTLedger.t() + token: TokenLedger.t() } @doc """ @@ -29,15 +29,15 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do ...> amount: 1_050_000_000 ...> } ...> ]}, - ...> nft: %NFTLedger{ + ...> token: %TokenLedger{ ...> transfers: [ - ...> %NFTLedger.Transfer{ - ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> %TokenLedger.Transfer{ + ...> token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, ...> amount: 1_050_000_000, - ...> nft_id: 0 + ...> token_id: 0 ...> } ...> ] ...> } @@ -66,8 +66,8 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do >> """ @spec serialize(t()) :: binary() - def serialize(%__MODULE__{uco: uco_ledger, nft: nft_ledger}) do - <> + def serialize(%__MODULE__{uco: uco_ledger, token: token_ledger}) do + <> end @doc """ @@ -94,15 +94,15 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do } ] }, - nft: %NFTLedger{ + token: %TokenLedger{ transfers: [ - %NFTLedger.Transfer{ - nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + %TokenLedger.Transfer{ + token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, to: <<0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, amount: 1_050_000_000, - nft_id: 0 + token_id: 0 } ] } @@ -113,12 +113,12 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do @spec deserialize(bitstring()) :: {t(), bitstring()} def deserialize(binary) when is_bitstring(binary) do {uco_ledger, rest} = UCOLedger.deserialize(binary) - {nft_ledger, rest} = NFTLedger.deserialize(rest) + {token_ledger, rest} = TokenLedger.deserialize(rest) { %__MODULE__{ uco: uco_ledger, - nft: nft_ledger + token: token_ledger }, rest } @@ -128,7 +128,7 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do def from_map(ledger = %{}) do %__MODULE__{ uco: Map.get(ledger, :uco, %UCOLedger{}) |> UCOLedger.from_map(), - nft: Map.get(ledger, :nft, %NFTLedger{}) |> NFTLedger.from_map() + token: Map.get(ledger, :token, %TokenLedger{}) |> TokenLedger.from_map() } end @@ -136,14 +136,14 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do def to_map(nil) do %{ uco: UCOLedger.to_map(nil), - nft: NFTLedger.to_map(nil) + token: TokenLedger.to_map(nil) } end - def to_map(%__MODULE__{uco: uco, nft: nft}) do + def to_map(%__MODULE__{uco: uco, token: token}) do %{ uco: UCOLedger.to_map(uco), - nft: NFTLedger.to_map(nft) + token: TokenLedger.to_map(token) } end @@ -160,15 +160,15 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do ...> amount: 1_050_000_000 ...> }, ...> ]}, - ...> nft: %NFTLedger{ + ...> token: %TokenLedger{ ...> transfers: [ - ...> %NFTLedger.Transfer{ - ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> %TokenLedger.Transfer{ + ...> token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, ...> amount: 1_050_000_000, - ...> nft_id: 0 + ...> token_id: 0 ...> } ...> ] ...> } @@ -177,7 +177,7 @@ defmodule Archethic.TransactionChain.TransactionData.Ledger do 2_100_000_000 """ @spec total_amount(t()) :: non_neg_integer() - def total_amount(%__MODULE__{uco: uco_ledger, nft: nft_ledger}) do - UCOLedger.total_amount(uco_ledger) + NFTLedger.total_amount(nft_ledger) + def total_amount(%__MODULE__{uco: uco_ledger, token: token_ledger}) do + UCOLedger.total_amount(uco_ledger) + TokenLedger.total_amount(token_ledger) end end diff --git a/lib/archethic/transaction_chain/transaction/data/ledger/nft.ex b/lib/archethic/transaction_chain/transaction/data/ledger/token.ex similarity index 75% rename from lib/archethic/transaction_chain/transaction/data/ledger/nft.ex rename to lib/archethic/transaction_chain/transaction/data/ledger/token.ex index db3939d78..d890e8767 100755 --- a/lib/archethic/transaction_chain/transaction/data/ledger/nft.ex +++ b/lib/archethic/transaction_chain/transaction/data/ledger/token.ex @@ -1,6 +1,6 @@ -defmodule Archethic.TransactionChain.TransactionData.NFTLedger do +defmodule Archethic.TransactionChain.TransactionData.TokenLedger do @moduledoc """ - Represents a NFT ledger movement + Represents token ledger movements """ defstruct transfers: [] @@ -8,40 +8,40 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger do @typedoc """ UCO movement is composed from: - - Transfers: List of NFT transfers + - Transfers: List of token transfers """ @type t :: %__MODULE__{ transfers: list(Transfer.t()) } @doc """ - Serialize a NFT ledger into binary format + Serialize a Token ledger into binary format ## Examples - iex> %NFTLedger{transfers: [ + iex> %TokenLedger{transfers: [ ...> %Transfer{ - ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, ...> amount: 1_050_000_000, - ...> nft_id: 0 + ...> token_id: 0 ...> } ...> ]} - ...> |> NFTLedger.serialize() + ...> |> TokenLedger.serialize() << - # Number of NFT transfers + # Number of Token transfers 1, - # NFT address + # Token address 0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, - # NFT recipient + # Token recipient 0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53, - # NFT amount + # Token amount 0, 0, 0, 0, 62, 149, 186, 128, - # NFT_ID + # Token_ID 0 >> """ @@ -52,7 +52,7 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger do end @doc """ - Deserialize an encoded NFT ledger + Deserialize an encoded Token ledger ## Examples @@ -60,17 +60,17 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger do ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, 0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53, ...> 0, 0, 0, 0, 62, 149, 186, 128, 0>> - ...> |> NFTLedger.deserialize() + ...> |> TokenLedger.deserialize() { - %NFTLedger{ + %TokenLedger{ transfers: [ %Transfer{ - nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, to: <<0, 0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, amount: 1_050_000_000, - nft_id: 0 + token_id: 0 } ] }, @@ -105,48 +105,48 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger do end @spec from_map(map()) :: t() - def from_map(nft_ledger = %{}) do + def from_map(token_ledger = %{}) do %__MODULE__{ - transfers: Map.get(nft_ledger, :transfers, []) |> Enum.map(&Transfer.from_map/1) + transfers: Map.get(token_ledger, :transfers, []) |> Enum.map(&Transfer.from_map/1) } end @spec to_map(t() | nil) :: map() def to_map(nil), do: %{transfers: []} - def to_map(nft_ledger = %__MODULE__{}) do + def to_map(token_ledger = %__MODULE__{}) do %{ transfers: - nft_ledger + token_ledger |> Map.get(:transfers, []) |> Enum.map(&Transfer.to_map/1) } end @doc """ - Return the total of uco transferred + Return the total of tokens transferred ## Examples - iex> %NFTLedger{transfers: [ + iex> %TokenLedger{transfers: [ ...> %Transfer{ - ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 59, 140, 2, 130, 52, 88, 206, 176, 29, 10, 173, 95, 179, 27, 166, 66, 52, ...> 165, 11, 146, 194, 246, 89, 73, 85, 202, 120, 242, 136, 136, 63, 53>>, ...> amount: 1_050_000_000, - ...> nft_id: 0, + ...> token_id: 0, ...> }, ...> %Transfer{ - ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 202, 39, 113, 5, 117, 133, 141, 107, 1, 202, 156, 250, 124, 22, 13, 183, 20, ...> 221, 181, 252, 153, 184, 2, 26, 115, 73, 148, 163, 119, 163, 86, 6>>, ...> amount: 2_290_000_000, - ...> nft_id: 0, + ...> token_id: 0, ...> } ...> ]} - ...> |> NFTLedger.total_amount() + ...> |> TokenLedger.total_amount() 3_340_000_000 """ @spec total_amount(t()) :: non_neg_integer() diff --git a/lib/archethic/transaction_chain/transaction/data/ledger/nft/transfer.ex b/lib/archethic/transaction_chain/transaction/data/ledger/token/transfer.ex similarity index 67% rename from lib/archethic/transaction_chain/transaction/data/ledger/nft/transfer.ex rename to lib/archethic/transaction_chain/transaction/data/ledger/token/transfer.ex index 2e942ca7c..85ddd57db 100644 --- a/lib/archethic/transaction_chain/transaction/data/ledger/nft/transfer.ex +++ b/lib/archethic/transaction_chain/transaction/data/ledger/token/transfer.ex @@ -1,44 +1,44 @@ -defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do +defmodule Archethic.TransactionChain.TransactionData.TokenLedger.Transfer do @moduledoc """ - Represents a NFT ledger transfer + Represents a Token ledger transfer """ - defstruct [:to, :amount, :nft, conditions: [], nft_id: 0] + defstruct [:to, :amount, :token, conditions: [], token_id: 0] alias Archethic.Utils - # impl nft_id + # impl token_id @typedoc """ Transfer is composed from: - - nft: NFT address + - token: Token address - to: receiver address of the asset - - amount: specify the number of NFT to transfer to the recipients (in the smallest unit 10^-8) - - conditions: specify to which address the NFT can be used - - nft_id: To uniquely identify a nft from a set a of nft(nft collection) + - amount: specify the number of Token to transfer to the recipients (in the smallest unit 10^-8) + - conditions: specify to which address the Token can be used + - token_id: To uniquely identify a token from a set a of token(token collection) """ @type t :: %__MODULE__{ - nft: binary(), + token: binary(), to: binary(), amount: non_neg_integer(), conditions: list(binary()), - nft_id: non_neg_integer() + token_id: non_neg_integer() } @doc """ - Serialize NFT transfer into binary format + Serialize Token transfer into binary format ## Examples iex> %Transfer{ - ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, ...> 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, ...> amount: 1_050_000_000, - ...> nft_id: 0 + ...> token_id: 0 ...> } ...> |> Transfer.serialize() << - # NFT address + # Token address 0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, # Transfer recipient @@ -46,16 +46,16 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14, # Transfer amount 0, 0, 0, 0, 62, 149, 186, 128, - # NFT ID + # Token ID 0 >> """ - def serialize(%__MODULE__{nft: nft, to: to, amount: amount, nft_id: nft_id}) do - <> + def serialize(%__MODULE__{token: token, to: to, amount: amount, token_id: token_id}) do + <> end @doc """ - Deserialize an encoded NFT transfer + Deserialize an encoded Token transfer ## Examples @@ -68,64 +68,63 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do ...> |> Transfer.deserialize() { %Transfer{ - nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, amount: 1_050_000_000, - nft_id: 0 + token_id: 0 }, "" } """ @spec deserialize(bitstring()) :: {t(), bitstring} def deserialize(data) do - {nft_address, rest} = Utils.deserialize_address(data) + {token_address, rest} = Utils.deserialize_address(data) {recipient_address, <>} = Utils.deserialize_address(rest) - <> = rest + <> = rest { %__MODULE__{ - nft: nft_address, + token: token_address, to: recipient_address, amount: amount, - nft_id: nft_id + token_id: token_id }, rest } end @doc """ - Forms NFT.Transfer Struct from a map - doc_test: To ensure addition of a feature don't cause regression + Forms Token.Transfer Struct from a map ## Examples iex> %{ - ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, ...> 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, ...> amount: 1_050_000_000, - ...> nft_id: 0 + ...> token_id: 0 ...> } ...> |> Transfer.from_map() %Transfer{ - nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, amount: 1_050_000_000, - nft_id: 0 + token_id: 0 } """ @spec from_map(map()) :: t() def from_map(transfer = %{}) do %__MODULE__{ - nft: Map.get(transfer, :nft), + token: Map.get(transfer, :token), to: Map.get(transfer, :to), amount: Map.get(transfer, :amount), - nft_id: Map.get(transfer, :nft_id) + token_id: Map.get(transfer, :token_id) } end @@ -133,30 +132,30 @@ defmodule Archethic.TransactionChain.TransactionData.NFTLedger.Transfer do ## Examples iex> %Transfer{ - ...> nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, ...> to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, ...> 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, ...> amount: 1_050_000_000, - ...> nft_id: 0 + ...> token_id: 0 ...> } ...> |> Transfer.to_map() %{ - nft: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + token: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, to: <<0, 0, 104, 134, 142, 120, 40, 59, 99, 108, 63, 166, 143, 250, 93, 186, 216, 117, 85, 106, 43, 26, 120, 35, 44, 137, 243, 184, 160, 251, 223, 0, 93, 14>>, amount: 1_050_000_000, - nft_id: 0 + token_id: 0 } """ @spec to_map(t()) :: map() - def to_map(%__MODULE__{nft: nft, to: to, amount: amount, nft_id: nft_id}) do + def to_map(%__MODULE__{token: token, to: to, amount: amount, token_id: token_id}) do %{ - nft: nft, + token: token, to: to, amount: amount, - nft_id: nft_id + token_id: token_id } end end diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex index 8dc3c4383..59a939863 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex @@ -2,7 +2,6 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation @moduledoc """ Represents the ledger operations defined during the transaction mining regarding the network movements: - transaction movements - - node rewards - unspent outputs - transaction fee """ @@ -47,41 +46,41 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ## Examples iex> LedgerOperations.from_transaction(%LedgerOperations{}, ...> %Transaction{ - ...> address: "@NFT2", - ...> type: :nft, + ...> address: "@Token2", + ...> type: :token, ...> data: %TransactionData{content: "{\"supply\": 10, \"type\": \"fungible\" }"} ...> } ...> ) %LedgerOperations{ - unspent_outputs: [%UnspentOutput{from: "@NFT2", amount: 1_000_000_000, type: {:NFT, "@NFT2", 0}}] + unspent_outputs: [%UnspentOutput{from: "@Token2", amount: 1_000_000_000, type: {:token, "@Token2", 0}}] } iex> LedgerOperations.from_transaction(%LedgerOperations{}, ...> %Transaction{ - ...> address: "@NFT2", - ...> type: :nft, + ...> address: "@Token2", + ...> type: :token, ...> data: %TransactionData{content: "{\"supply\": 10, \"type\": \"non-fungible\", \"properties\": [[],[],[],[],[],[],[],[],[],[]]}"} ...> } ...> ) %LedgerOperations{ unspent_outputs: [ - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 1}}, - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 2}}, - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 3}}, - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 4}}, - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 5}}, - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 6}}, - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 7}}, - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 8}}, - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 9}}, - %UnspentOutput{from: "@NFT2", amount: 100_000_000, type: {:NFT, "@NFT2", 10}} + %UnspentOutput{from: "@Token2", amount: 100_000_000, type: {:token, "@Token2", 1}}, + %UnspentOutput{from: "@Token2", amount: 100_000_000, type: {:token, "@Token2", 2}}, + %UnspentOutput{from: "@Token2", amount: 100_000_000, type: {:token, "@Token2", 3}}, + %UnspentOutput{from: "@Token2", amount: 100_000_000, type: {:token, "@Token2", 4}}, + %UnspentOutput{from: "@Token2", amount: 100_000_000, type: {:token, "@Token2", 5}}, + %UnspentOutput{from: "@Token2", amount: 100_000_000, type: {:token, "@Token2", 6}}, + %UnspentOutput{from: "@Token2", amount: 100_000_000, type: {:token, "@Token2", 7}}, + %UnspentOutput{from: "@Token2", amount: 100_000_000, type: {:token, "@Token2", 8}}, + %UnspentOutput{from: "@Token2", amount: 100_000_000, type: {:token, "@Token2", 9}}, + %UnspentOutput{from: "@Token2", amount: 100_000_000, type: {:token, "@Token2", 10}} ] } """ @spec from_transaction(t(), Transaction.t()) :: t() def from_transaction(ops = %__MODULE__{}, %Transaction{ address: address, - type: :nft, + type: :token, data: %TransactionData{content: content} }) do case Jason.decode(content) do @@ -101,7 +100,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation %UnspentOutput{ from: address, amount: supply * @unit_uco, - type: {:NFT, address, 0} + type: {:token, address, 0} } ] end @@ -114,7 +113,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation properties |> Enum.with_index() |> Enum.map(fn {_item_properties, index} -> - %UnspentOutput{from: address, amount: 1 * @unit_uco, type: {:NFT, address, index + 1}} + %UnspentOutput{from: address, amount: 1 * @unit_uco, type: {:token, address, index + 1}} end) end @@ -130,30 +129,30 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> %TransactionMovement{to: "@Bob4", amount: 1_040_000_000, type: :UCO}, ...> %TransactionMovement{to: "@Charlie2", amount: 217_000_000, type: :UCO}, ...> %TransactionMovement{to: "@Charlie2", amount: 2_000_000_000, type: - ...> {:NFT, "@TomNFT", 0}}, + ...> {:token, "@TomToken", 0}}, ...> ], ...> fee: 40_000_000 ...> } ...> |> LedgerOperations.total_to_spend() - %{ uco: 1_297_000_000, nft: %{ {"@TomNFT",0} => 2_000_000_000 } } + %{ uco: 1_297_000_000, token: %{ {"@TomToken",0} => 2_000_000_000 } } """ @spec total_to_spend(t()) :: %{ :uco => non_neg_integer(), - :nft => %{binary() => non_neg_integer()} + :token => %{binary() => non_neg_integer()} } def total_to_spend(%__MODULE__{transaction_movements: transaction_movements, fee: fee}) do transaction_movements |> Enum.reject(&(&1.to == @burning_address)) - |> ledger_balances(%{uco: fee, nft: %{}}) + |> ledger_balances(%{uco: fee, token: %{}}) end - defp ledger_balances(movements, acc \\ %{uco: 0, nft: %{}}) do + defp ledger_balances(movements, acc \\ %{uco: 0, token: %{}}) do Enum.reduce(movements, acc, fn %{type: :UCO, amount: amount}, acc -> Map.update!(acc, :uco, &(&1 + amount)) - %{type: {:NFT, nft_address, nft_id}, amount: amount}, acc -> - update_in(acc, [:nft, Access.key({nft_address, nft_id}, 0)], &(&1 + amount)) + %{type: {:token, token_address, token_id}, amount: amount}, acc -> + update_in(acc, [:token, Access.key({token_address, token_id}, 0)], &(&1 + amount)) %{type: :call}, acc -> acc @@ -169,7 +168,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> transaction_movements: [ ...> %TransactionMovement{to: "@Bob4", amount: 1_040_000_000, type: :UCO}, ...> %TransactionMovement{to: "@Charlie2", amount: 217_000_000, type: :UCO}, - ...> %TransactionMovement{to: "@Tom4", amount: 500_000_000, type: {:NFT, "@BobNFT", 0}} + ...> %TransactionMovement{to: "@Tom4", amount: 500_000_000, type: {:token, "@BobToken", 0}} ...> ], ...> fee: 40_000_000 ...> } @@ -180,13 +179,13 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> transaction_movements: [ ...> %TransactionMovement{to: "@Bob4", amount: 1_040_000_000, type: :UCO}, ...> %TransactionMovement{to: "@Charlie2", amount: 217_000_000, type: :UCO}, - ...> %TransactionMovement{to: "@Tom4", amount: 500_000_000, type: {:NFT, "@BobNFT", 0}} + ...> %TransactionMovement{to: "@Tom4", amount: 500_000_000, type: {:token, "@BobToken", 0}} ...> ], ...> fee: 40_000_000 ...> } ...> |> LedgerOperations.sufficient_funds?([ ...> %UnspentOutput{from: "@Charlie5", amount: 3_000_000_000, type: :UCO}, - ...> %UnspentOutput{from: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@BobNFT", 0}} + ...> %UnspentOutput{from: "@Bob4", amount: 1_000_000_000, type: {:token, "@BobToken", 0}} ...> ]) true @@ -196,26 +195,27 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> } ...> |> LedgerOperations.sufficient_funds?([ ...> %UnspentOutput{from: "@Charlie5", amount: 3_000_000_000, type: :UCO}, - ...> %UnspentOutput{from: "@Bob4", amount: 10_000_000_000, type: {:NFT, "@BobNFT", 0}} + ...> %UnspentOutput{from: "@Bob4", amount: 10_000_000_000, type: {:token, "@BobToken", 0}} ...> ]) true """ @spec sufficient_funds?(t(), list(UnspentOutput.t() | TransactionInput.t())) :: boolean() def sufficient_funds?(operations = %__MODULE__{}, inputs) when is_list(inputs) do - %{uco: uco_balance, nft: nfts_received} = ledger_balances(inputs) - %{uco: uco_to_spend, nft: nfts_to_spend} = total_to_spend(operations) - uco_balance >= uco_to_spend and sufficient_nfts?(nfts_received, nfts_to_spend) + %{uco: uco_balance, token: tokens_received} = ledger_balances(inputs) + %{uco: uco_to_spend, token: tokens_to_spend} = total_to_spend(operations) + uco_balance >= uco_to_spend and sufficient_tokens?(tokens_received, tokens_to_spend) end - defp sufficient_nfts?(nfts_received = %{}, nft_to_spend = %{}) - when map_size(nfts_received) == 0 and map_size(nft_to_spend) > 0, + defp sufficient_tokens?(tokens_received = %{}, token_to_spend = %{}) + when map_size(tokens_received) == 0 and map_size(token_to_spend) > 0, do: false - defp sufficient_nfts?(_nfts_received, nfts_to_spend) when map_size(nfts_to_spend) == 0, do: true + defp sufficient_tokens?(_tokens_received, tokens_to_spend) when map_size(tokens_to_spend) == 0, + do: true - defp sufficient_nfts?(nfts_received, nfts_to_spend) do - Enum.all?(nfts_to_spend, fn {nft_key, amount_to_spend} -> - case Map.get(nfts_received, nft_key) do + defp sufficient_tokens?(tokens_received, tokens_to_spend) do + Enum.all?(tokens_to_spend, fn {token_key, amount_to_spend} -> + case Map.get(tokens_received, token_key) do nil -> false @@ -281,51 +281,51 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ] } - # When using NFT unspent outputs are sufficient to satisfy the transaction movements + # When using Token unspent outputs are sufficient to satisfy the transaction movements iex> %LedgerOperations{ ...> transaction_movements: [ - ...> %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT", 0}} + ...> %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:token, "@CharlieToken", 0}} ...> ], ...> fee: 40_000_000 ...> } ...> |> LedgerOperations.consume_inputs("@Alice2", [ ...> %UnspentOutput{from: "@Charlie1", amount: 200_000_000, type: :UCO}, - ...> %UnspentOutput{from: "@Bob3", amount: 1_200_000_000, type: {:NFT, "@CharlieNFT", 0}} + ...> %UnspentOutput{from: "@Bob3", amount: 1_200_000_000, type: {:token, "@CharlieToken", 0}} ...> ]) %LedgerOperations{ transaction_movements: [ - %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT", 0}} + %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:token, "@CharlieToken", 0}} ], fee: 40_000_000, unspent_outputs: [ %UnspentOutput{from: "@Alice2", amount: 160_000_000, type: :UCO}, - %UnspentOutput{from: "@Alice2", amount: 200_000_000, type: {:NFT, "@CharlieNFT", 0}} + %UnspentOutput{from: "@Alice2", amount: 200_000_000, type: {:token, "@CharlieToken", 0}} ] } - # When multiple NFT unspent outputs are sufficient to satisfy the transaction movements + # When multiple Token unspent outputs are sufficient to satisfy the transaction movements iex> %LedgerOperations{ ...> transaction_movements: [ - ...> %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT", 0}} + ...> %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:token, "@CharlieToken", 0}} ...> ], ...> fee: 40_000_000 ...> } ...> |> LedgerOperations.consume_inputs("@Alice2", [ ...> %UnspentOutput{from: "@Charlie1", amount: 200_000_000, type: :UCO}, - ...> %UnspentOutput{from: "@Bob3", amount: 500_000_000, type: {:NFT, "@CharlieNFT", 0}}, - ...> %UnspentOutput{from: "@Hugo5", amount: 700_000_000, type: {:NFT, "@CharlieNFT", 0}}, - ...> %UnspentOutput{from: "@Tom1", amount: 700_000_000, type: {:NFT, "@CharlieNFT", 0}} + ...> %UnspentOutput{from: "@Bob3", amount: 500_000_000, type: {:token, "@CharlieToken", 0}}, + ...> %UnspentOutput{from: "@Hugo5", amount: 700_000_000, type: {:token, "@CharlieToken", 0}}, + ...> %UnspentOutput{from: "@Tom1", amount: 700_000_000, type: {:token, "@CharlieToken", 0}} ...> ]) %LedgerOperations{ transaction_movements: [ - %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:NFT, "@CharlieNFT", 0}} + %TransactionMovement{to: "@Bob4", amount: 1_000_000_000, type: {:token, "@CharlieToken", 0}} ], fee: 40_000_000, unspent_outputs: [ %UnspentOutput{from: "@Alice2", amount: 160_000_000, type: :UCO}, - %UnspentOutput{from: "@Alice2", amount: 900_000_000, type: {:NFT, "@CharlieNFT", 0}} + %UnspentOutput{from: "@Alice2", amount: 900_000_000, type: {:token, "@CharlieToken", 0}} ] } @@ -333,24 +333,24 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation iex> %LedgerOperations{ ...> transaction_movements: [ - ...> %TransactionMovement{to: "@Bob4", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 2}} + ...> %TransactionMovement{to: "@Bob4", amount: 100_000_000, type: {:token, "@CharlieToken", 2}} ...> ], ...> fee: 40_000_000 ...> } |> LedgerOperations.consume_inputs("@Alice2", [ ...> %UnspentOutput{from: "@Charlie1", amount: 200_000_000, type: :UCO}, - ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 1}}, - ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 2}}, - ...> %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 3}} + ...> %UnspentOutput{from: "@CharlieToken", amount: 100_000_000, type: {:token, "@CharlieToken", 1}}, + ...> %UnspentOutput{from: "@CharlieToken", amount: 100_000_000, type: {:token, "@CharlieToken", 2}}, + ...> %UnspentOutput{from: "@CharlieToken", amount: 100_000_000, type: {:token, "@CharlieToken", 3}} ...> ]) %LedgerOperations{ fee: 40_000_000, transaction_movements: [ - %TransactionMovement{to: "@Bob4", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 2}} + %TransactionMovement{to: "@Bob4", amount: 100_000_000, type: {:token, "@CharlieToken", 2}} ], unspent_outputs: [ %UnspentOutput{from: "@Alice2", amount: 160_000_000, type: :UCO}, - %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 1}}, - %UnspentOutput{from: "@CharlieNFT", amount: 100_000_000, type: {:NFT, "@CharlieNFT", 3}} + %UnspentOutput{from: "@CharlieToken", amount: 100_000_000, type: {:token, "@CharlieToken", 1}}, + %UnspentOutput{from: "@CharlieToken", amount: 100_000_000, type: {:token, "@CharlieToken", 3}} ] } """ @@ -363,12 +363,12 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation def consume_inputs(ops = %__MODULE__{}, change_address, inputs) when is_binary(change_address) and is_list(inputs) do if sufficient_funds?(ops, inputs) do - %{uco: uco_balance, nft: nfts_received} = ledger_balances(inputs) - %{uco: uco_to_spend, nft: nfts_to_spend} = total_to_spend(ops) + %{uco: uco_balance, token: tokens_received} = ledger_balances(inputs) + %{uco: uco_to_spend, token: tokens_to_spend} = total_to_spend(ops) new_unspent_outputs = [ %UnspentOutput{from: change_address, amount: uco_balance - uco_to_spend, type: :UCO} - | new_nft_unspent_outputs(nfts_received, nfts_to_spend, change_address, inputs) + | new_token_unspent_outputs(tokens_received, tokens_to_spend, change_address, inputs) ] Map.update!(ops, :unspent_outputs, &(new_unspent_outputs ++ &1)) @@ -377,19 +377,20 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation end end - defp new_nft_unspent_outputs(nfts_received, nfts_to_spend, change_address, inputs) do - # Reject NFT not used to inject back in the new unspent outputs - nfts_not_used = - nfts_received - |> Enum.reject(&Map.has_key?(nfts_to_spend, elem(&1, 0))) - |> Enum.map(fn {{nft_address, nft_id}, amount} -> + defp new_token_unspent_outputs(tokens_received, tokens_to_spend, change_address, inputs) do + # Reject Token not used to inject back in the new unspent outputs + tokens_not_used = + tokens_received + |> Enum.reject(&Map.has_key?(tokens_to_spend, elem(&1, 0))) + |> Enum.map(fn {{token_address, token_id}, amount} -> Enum.find(inputs, fn input -> - input.type == {:NFT, nft_address, nft_id} and input.amount == amount + input.type == {:token, token_address, token_id} and input.amount == amount end) end) - Enum.reduce(nfts_to_spend, nfts_not_used, fn {{nft_address, nft_id}, amount_to_spend}, acc -> - case Map.get(nfts_received, {nft_address, nft_id}) do + Enum.reduce(tokens_to_spend, tokens_not_used, fn {{token_address, token_id}, amount_to_spend}, + acc -> + case Map.get(tokens_received, {token_address, token_id}) do nil -> acc @@ -397,8 +398,8 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation [ %UnspentOutput{ from: change_address, - amount: recv_amount - trunc_nft_amount(nft_id, amount_to_spend), - type: {:NFT, nft_address, nft_id} + amount: recv_amount - trunc_token_amount(token_id, amount_to_spend), + type: {:token, token_address, token_id} } | acc ] @@ -410,8 +411,8 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation end # We prevent part of non-fungible token to be spent - defp trunc_nft_amount(0, amount), do: amount - defp trunc_nft_amount(_nft_id, amount), do: trunc(amount / @unit_uco) * @unit_uco + defp trunc_token_amount(0, amount), do: amount + defp trunc_token_amount(_token_id, amount), do: trunc(amount / @unit_uco) * @unit_uco @doc """ List all the addresses from transaction movements diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement.ex index 3414e2e0b..a4df34978 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement.ex @@ -13,7 +13,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation TransactionMovement is composed from: - to: receiver address of the movement - amount: specify the number assets to transfer to the recipients (smallest unit of uco 10^-8) - - type: asset type (ie. UCO or NFT) + - type: asset type (ie. UCO or Token) """ @type t() :: %__MODULE__{ to: Crypto.versioned_hash(), @@ -46,7 +46,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> to: <<0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, ...> 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, ...> amount: 30_000_000, - ...> type: {:NFT, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> type: {:token, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} ...> } ...> |> TransactionMovement.serialize() @@ -56,12 +56,12 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186, # Amount 0, 0, 0, 0, 1, 201, 195, 128, - # NFT type + # Token type 1, - # NFT address + # Token address 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, - # NFT ID + # Token ID 0 >> """ @@ -100,7 +100,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, amount: 30_000_000, - type: {:NFT, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + type: {:token, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} }, "" @@ -142,16 +142,16 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, ...> 194,159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, ...> amount: 30_000_000, - ...> type: "NFT", nft_address: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, + ...> type: "Token", token_address: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, ...> 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, - ...> 143, 175>>, nft_id: 0 + ...> 143, 175>>, token_id: 0 ...> } ...> |> TransactionMovement.from_map() %TransactionMovement{ to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, amount: 30_000_000, - type: {:NFT, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, + type: {:token, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} } @@ -164,8 +164,8 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation } case Map.get(movement, :type) do - "NFT" -> - %{res | type: {:NFT, Map.get(movement, :nft_address), Map.get(movement, :nft_id)}} + "Token" -> + %{res | type: {:token, Map.get(movement, :token_address), Map.get(movement, :token_id)}} _ -> %{res | type: :UCO} @@ -192,15 +192,15 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation iex> %TransactionMovement{ ...> to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, ...> amount: 30_000_000, - ...> type: {:NFT, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} + ...> type: {:token, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} ...> } ...> |> TransactionMovement.to_map() %{ to: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, amount: 30_000_000, - type: "NFT", - nft_address: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, - nft_id: 0 + type: "token", + token_address: <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, + token_id: 0 } """ @@ -216,14 +216,14 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation def to_map(%__MODULE__{ to: to, amount: amount, - type: {:NFT, nft_address, nft_id} + type: {:token, token_address, token_id} }) do %{ to: to, amount: amount, - type: "NFT", - nft_address: nft_address, - nft_id: nft_id + type: "token", + token_address: token_address, + token_id: token_id } end end diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement/type.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement/type.ex index 7509aa385..192b0f557 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement/type.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/transaction_movement/type.ex @@ -9,21 +9,21 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation @typedoc """ Transaction movement can be: - UCO transfers - - NFT transfers. When it's a NFT transfer, the type indicates the address of NFT to transfer, followed by nft_id + - Token transfers. When it's a token transfer, the type indicates the address of token to transfer, followed by a token id to identify non-fungible asset """ - @type t() :: :UCO | {:NFT, Crypto.versioned_hash(), non_neg_integer()} + @type t() :: :UCO | {:token, Crypto.versioned_hash(), non_neg_integer()} def serialize(:UCO), do: <<0>> - def serialize({:NFT, address, nft_id}) do - <<1::8, address::binary, nft_id::8>> + def serialize({:token, address, token_id}) do + <<1::8, address::binary, token_id::8>> end def deserialize(<<0::8, rest::bitstring>>), do: {:UCO, rest} def deserialize(<<1::8, rest::bitstring>>) do {address, rest} = Utils.deserialize_address(rest) - <> = rest - {{:NFT, address, nft_id}, rest} + <> = rest + {{:token, address, token_id}, rest} end end diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output.ex index 818927cd3..e44a70573 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations/unspent_output.ex @@ -41,13 +41,13 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation 0 >> - With NFT movements: + With Token movements: iex> %UnspentOutput{ ...> from: <<0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, ...> 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, ...> amount: 1_050_000_000, - ...> type: {:NFT, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> type: {:token, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} ...> } ...> |> UnspentOutput.serialize() @@ -57,12 +57,12 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186, # Amount 0, 0, 0, 0, 62, 149, 186, 128, - # NFT Unspent Output + # Token Unspent Output 1, - # NFT address + # Token address 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175, - # NFT ID + # Token ID 0 >> """ @@ -100,7 +100,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, amount: 1_050_000_000, - type: {:NFT, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + type: {:token, <<0, 0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0}, }, "" @@ -145,13 +145,13 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, ...> 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, ...> amount: 1_050_000_000, - ...> type: {:NFT, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, + ...> type: {:token, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74, ...> 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0} ...> } |> UnspentOutput.from_map() %UnspentOutput{ from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194,159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, amount: 1_050_000_000, - type: {:NFT, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74,197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0 }, + type: {:token, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140, 74,197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0 }, reward?: false, timestamp: nil } @@ -190,17 +190,17 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, ...> 68, 194, 159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, ...> amount: 1_050_000_000, - ...> type: {:NFT, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, + ...> type: {:token, <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, ...> 71, 140, 74, 197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, 0 } ...> } |> UnspentOutput.to_map() %{ from: <<0, 0, 214, 107, 17, 107, 227, 11, 17, 43, 204, 48, 78, 129, 145, 126, 45, 68, 194,159, 19, 92, 240, 29, 37, 105, 183, 232, 56, 42, 163, 236, 251, 186>>, amount: 1_050_000_000, - type: "NFT", - nft_address: <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, + type: "token", + token_address: <<0, 49, 101, 72, 154, 152, 3, 174, 47, 2, 35, 7, 92, 122, 206, 185, 71, 140,74,197, 46, 99, 117, 89, 96, 100, 20, 0, 34, 181, 215, 143, 175>>, - nft_id: 0, + token_id: 0, reward?: false, timestamp: nil } @@ -227,16 +227,16 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation def to_map(%__MODULE__{ from: from, amount: amount, - type: {:NFT, nft_address, nft_id}, + type: {:token, token_address, token_id}, reward?: reward, timestamp: timestamp }) do %{ from: from, amount: amount, - type: "NFT", - nft_address: nft_address, - nft_id: nft_id, + type: "token", + token_address: token_address, + token_id: token_id, reward?: reward, timestamp: timestamp } diff --git a/lib/archethic/utils/regression/playbook.ex b/lib/archethic/utils/regression/playbook.ex index a8c550dab..9271ad602 100644 --- a/lib/archethic/utils/regression/playbook.ex +++ b/lib/archethic/utils/regression/playbook.ex @@ -10,8 +10,8 @@ defmodule Archethic.Utils.Regression.Playbook do alias Archethic.TransactionChain.Transaction alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.Ledger - alias Archethic.TransactionChain.TransactionData.NFTLedger - alias Archethic.TransactionChain.TransactionData.NFTLedger.Transfer, as: NFTTransfer + alias Archethic.TransactionChain.TransactionData.TokenLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger.Transfer, as: TokenTransfer alias Archethic.TransactionChain.TransactionData.Ownership alias Archethic.TransactionChain.TransactionData.UCOLedger alias Archethic.TransactionChain.TransactionData.UCOLedger.Transfer, as: UCOTransfer @@ -256,7 +256,7 @@ defmodule Archethic.Utils.Regression.Playbook do data: %TransactionData{ ledger: %Ledger{ uco: %UCOLedger{transfers: uco_transfers}, - nft: %NFTLedger{transfers: nft_transfers} + token: %TokenLedger{transfers: token_transfers} }, code: code, content: content, @@ -282,19 +282,19 @@ defmodule Archethic.Utils.Regression.Playbook do %{"to" => Base.encode16(to), "amount" => amount} end) }, - "nft" => %{ + "token" => %{ "transfers" => - Enum.map(nft_transfers, fn %NFTTransfer{ - to: to, - amount: amount, - nft: nft_address, - nft_id: nft_id - } -> + Enum.map(token_transfers, fn %TokenTransfer{ + to: to, + amount: amount, + token: token_address, + token_id: token_id + } -> %{ "to" => Base.encode16(to), "amount" => amount, - "nft" => nft_address, - "nft_id" => nft_id + "token" => token_address, + "token_id" => token_id } end) } diff --git a/lib/archethic_web/controllers/api/schema/ledger.ex b/lib/archethic_web/controllers/api/schema/ledger.ex index a804df32e..bbbf962b0 100644 --- a/lib/archethic_web/controllers/api/schema/ledger.ex +++ b/lib/archethic_web/controllers/api/schema/ledger.ex @@ -4,18 +4,18 @@ defmodule ArchethicWeb.API.Schema.Ledger do use Ecto.Schema import Ecto.Changeset - alias ArchethicWeb.API.Schema.NFTLedger + alias ArchethicWeb.API.Schema.TokenLedger alias ArchethicWeb.API.Schema.UCOLedger embedded_schema do embeds_one(:uco, UCOLedger) - embeds_one(:nft, NFTLedger) + embeds_one(:token, TokenLedger) end def changeset(changeset = %__MODULE__{}, params = %{}) do changeset |> cast(params, []) |> cast_embed(:uco) - |> cast_embed(:nft) + |> cast_embed(:token) end end diff --git a/lib/archethic_web/controllers/api/schema/nft_ledger.ex b/lib/archethic_web/controllers/api/schema/token_ledger.ex similarity index 63% rename from lib/archethic_web/controllers/api/schema/nft_ledger.ex rename to lib/archethic_web/controllers/api/schema/token_ledger.ex index eaa9fde05..de732188c 100644 --- a/lib/archethic_web/controllers/api/schema/nft_ledger.ex +++ b/lib/archethic_web/controllers/api/schema/token_ledger.ex @@ -1,4 +1,4 @@ -defmodule ArchethicWeb.API.Schema.NFTLedger do +defmodule ArchethicWeb.API.Schema.TokenLedger do @moduledoc false use Ecto.Schema @@ -10,8 +10,8 @@ defmodule ArchethicWeb.API.Schema.NFTLedger do embeds_many :transfers, Transfer do field(:to, Address) field(:amount, :integer) - field(:nft, Address) - field(:nft_id, :integer) + field(:token, Address) + field(:token_id, :integer) end end @@ -21,15 +21,15 @@ defmodule ArchethicWeb.API.Schema.NFTLedger do |> cast_embed(:transfers, with: &changeset_transfers/2) |> validate_length(:transfers, max: 256, - message: "maximum nft transfers in a transaction can be 256" + message: "maximum token transfers in a transaction can be 256" ) end defp changeset_transfers(changeset, params) do changeset - |> cast(params, [:to, :amount, :nft, :nft_id]) - |> validate_required([:to, :amount, :nft, :nft_id]) + |> cast(params, [:to, :amount, :token, :token_id]) + |> validate_required([:to, :amount, :token, :token_id]) |> validate_number(:amount, greater_than: 0) - |> validate_inclusion(:nft_id, 0..255) + |> validate_inclusion(:token_id, 0..255) end end diff --git a/lib/archethic_web/controllers/api/types/transaction_type.ex b/lib/archethic_web/controllers/api/types/transaction_type.ex index b1468f6f7..2e7796c49 100644 --- a/lib/archethic_web/controllers/api/types/transaction_type.ex +++ b/lib/archethic_web/controllers/api/types/transaction_type.ex @@ -10,7 +10,7 @@ defmodule ArchethicWeb.API.Types.TransactionType do "keychain", "transfer", "hosting", - "nft", + "token", "code_proposal", "code_approval" ] diff --git a/lib/archethic_web/graphql_schema/resolver.ex b/lib/archethic_web/graphql_schema/resolver.ex index 0c2f46710..2b2b1a16a 100644 --- a/lib/archethic_web/graphql_schema/resolver.ex +++ b/lib/archethic_web/graphql_schema/resolver.ex @@ -15,13 +15,13 @@ defmodule ArchethicWeb.GraphQLSchema.Resolver do def get_balance(address) do case Archethic.get_balance(address) do - {:ok, %{uco: uco, nft: nft_balances}} -> + {:ok, %{uco: uco, token: token_balances}} -> balance = %{ uco: uco, - nft: - nft_balances - |> Enum.map(fn {{address, nft_id}, amount} -> - %{address: address, amount: amount, nft_id: nft_id} + token: + token_balances + |> Enum.map(fn {{address, token_id}, amount} -> + %{address: address, amount: amount, token_id: token_id} end) |> Enum.sort_by(& &1.amount) } diff --git a/lib/archethic_web/graphql_schema/transaction_type.ex b/lib/archethic_web/graphql_schema/transaction_type.ex index 4c4f95e20..f4be1ac85 100644 --- a/lib/archethic_web/graphql_schema/transaction_type.ex +++ b/lib/archethic_web/graphql_schema/transaction_type.ex @@ -64,7 +64,7 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do @desc "[Ledger] represents the ledger operations to perform" object :ledger do field(:uco, :uco_ledger) - field(:nft, :nft_ledger) + field(:token, :token_ledger) end @desc "[UCOTransfer] represents the an asset transfer" @@ -73,12 +73,12 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do field(:amount, :amount) end - @desc "[NFTTransfer] represents the an asset transfer" - object :nft_transfer do + @desc "[TokenTransfer] represents the an asset transfer" + object :token_transfer do field(:to, :address) field(:amount, :amount) - field(:nft, :address) - field(:nft_id, :integer) + field(:token, :address) + field(:token_id, :integer) end @desc "[UCOLedger] represents the transfers to perform on the UCO ledger" @@ -86,9 +86,9 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do field(:transfers, list_of(:uco_transfer)) end - @desc "[NFTLedger] represents the transfers to perform on the UCO ledger" - object :nft_ledger do - field(:transfers, list_of(:nft_transfer)) + @desc "[TokenLedger] represents the transfers to perform on the UCO ledger" + object :token_ledger do + field(:transfers, list_of(:token_transfer)) end @desc "[Ownership] represents a block to set a secret and the authorized public keys able to decrypt the secret" @@ -151,15 +151,15 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do It includes: - From: transaction which send the amount of assets - Amount: asset amount - - Type: UCO/NFT - - NFT address: address of the NFT if the type is NFT + - Type: UCO/token + - token address: address of the token if the type is token """ object :unspent_output do field(:from, :address) field(:amount, :amount) field(:type, :string) - field(:nft_address, :address) - field(:nft_id, :integer) + field(:token_address, :address) + field(:token_id, :integer) end @desc """ @@ -167,8 +167,8 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do It includes: - From: transaction which send the amount of assets - Amount: asset amount - - Type: UCO/NFT/Call - - NFT address: address of the NFT if the type is NFT + - Type: UCO/token/Call + - token address: address of the token if the type is token - Spent: determines if the input has been spent - Timestamp: Date time when the inputs was generated """ @@ -176,10 +176,10 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do field(:from, :address) field(:amount, :amount) field(:type, :string) - field(:nft_address, :address) + field(:token_address, :address) field(:spent, :boolean) field(:timestamp, :timestamp) - field(:nft_id, :integer) + field(:token_id, :integer) end @desc """ @@ -187,15 +187,15 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do It includes: - TO: asset transfer recipient - Amount: asset amount - - Type: UCO/NFT - - NFT address: address of the NFT if the type is NFT + - Type: UCO/token + - token address: address of the token if the type is token """ object :transaction_movement do field(:to, :address) field(:amount, :amount) field(:type, :string) - field(:nft_address, :address) - field(:nft_id, :integer) + field(:token_address, :address) + field(:token_id, :integer) end @desc """ @@ -213,23 +213,24 @@ defmodule ArchethicWeb.GraphQLSchema.TransactionType do [Balance] represents a ledger balance. It includes: - UCO: uco balance - - NFT: NFT balances + - token: token balances """ object :balance do field(:uco, :amount) - field(:nft, list_of(:nft_balance)) + field(:token, list_of(:token_balance)) end @desc """ - [NftBalance] represents a NFT ledger balance. + [tokenBalance] represents a token ledger balance. It includes: - - NFT: address of the NFT - - Amount: amount of NFT + - Token: address of the token + - Amount: amount of token + - Token ID: ID of the token """ - object :nft_balance do + object :token_balance do field(:address, :address) field(:amount, :amount) - field(:nft_id, :integer) + field(:token_id, :integer) end @desc """ diff --git a/lib/archethic_web/templates/explorer/transaction_details.html.leex b/lib/archethic_web/templates/explorer/transaction_details.html.leex index 8bc49789f..835d040d4 100644 --- a/lib/archethic_web/templates/explorer/transaction_details.html.leex +++ b/lib/archethic_web/templates/explorer/transaction_details.html.leex @@ -96,9 +96,9 @@
  • - - NFT + + Token
  • @@ -169,12 +169,12 @@
    <% end %>
    -
    -

    NFT Ledger

    - <%= if Enum.count(@transaction.data.ledger.nft.transfers) == 0 do %> +
    +

    Token Ledger

    + <%= if Enum.count(@transaction.data.ledger.token.transfers) == 0 do %> N/A <% end %> - <%= for transfer <- @transaction.data.ledger.nft.transfers do %> + <%= for transfer <- @transaction.data.ledger.token.transfers do %>
    To
    @@ -189,10 +189,10 @@
    <%= to_float(transfer.amount) %>
    -
    NFT
    +
    Token
    - <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(transfer.nft)) do%> - <%= Base.encode16(transfer.nft) %> + <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(transfer.token_address)) do%> + <%= Base.encode16(transfer.token_address) %> <% end %>
    @@ -333,16 +333,16 @@ <%= if movement.amount > 0 do %> (<%= format_full_usd_amount(movement.amount, @uco_price_at_time[:usd], @uco_price_now[:usd]) %>) <% end %> - <% {:NFT, nft_address, nft_id} -> %> + <% {:token, token_address, token_id} -> %> <%= to_float(movement.amount) %> - NFT - <%= if nft_id >= 1 do %> - (#<%= nft_id %>) + Token + <%= if token_id >= 1 do %> + (#<%= token_id %>) <% end %> - <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(nft_address)) do %> - <%= Base.encode16(:binary.part(nft_address, 0, 13)) %>... + <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(token_address)) do %> + <%= Base.encode16(:binary.part(token_address, 0, 13)) %>... <% end %>
    <% end %> @@ -375,15 +375,15 @@ <%= if unspent_output.amount > 0 do %> (<%= format_full_usd_amount(unspent_output.amount, @uco_price_at_time[:usd], @uco_price_now[:usd]) %>) <% end %> - <% {:NFT, nft_address, nft_id} -> %> + <% {:token, token_address, token_id} -> %> <%= to_float(unspent_output.amount) %> - NFT - <%= if nft_id >= 1 do %> - (#<%= nft_id %>) + Token + <%= if token_id >= 1 do %> + (#<%= token_id %>) <% end %> - <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(nft_address)) do %> - <%= Base.encode16(:binary.part(nft_address, 0, 13)) %>... + <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(token_address)) do %> + <%= Base.encode16(:binary.part(token_address, 0, 13)) %>... <% end %> <% end %>
    @@ -518,17 +518,17 @@ <%= if input.reward? do %> Mining reward <% end %> - <% {:NFT, nft_address, nft_id} -> %> + <% {:token, token_address, token_id} -> %>
    -
    NFT - <%= if nft_id >= 1 do %> - (#<%= nft_id %>) +
    Token + <%= if token_id >= 1 do %> + (#<%= token_id %>) <% end %>
    - <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(nft_address)) do %> - <%= Base.encode16(:binary.part(nft_address, 0, 6)) %>... - <%= Base.encode16(:binary.part(nft_address, 0, 12)) %>... + <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(token_address)) do %> + <%= Base.encode16(:binary.part(token_address, 0, 6)) %>... + <%= Base.encode16(:binary.part(token_address, 0, 12)) %>... <% end %>
    diff --git a/test/archethic/account/mem_tables/nft_ledger_test.exs b/test/archethic/account/mem_tables/token_ledger_test.exs similarity index 59% rename from test/archethic/account/mem_tables/nft_ledger_test.exs rename to test/archethic/account/mem_tables/token_ledger_test.exs index 0a9ccc2fa..6ae415e49 100644 --- a/test/archethic/account/mem_tables/nft_ledger_test.exs +++ b/test/archethic/account/mem_tables/token_ledger_test.exs @@ -1,11 +1,11 @@ -defmodule Archethic.Account.MemTables.NFTLedgerTest do +defmodule Archethic.Account.MemTables.TokenLedgerTest do @moduledoc false use ExUnit.Case - alias Archethic.Account.MemTables.NFTLedger + alias Archethic.Account.MemTables.TokenLedger alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput alias Archethic.TransactionChain.TransactionInput - doctest NFTLedger + doctest TokenLedger end diff --git a/test/archethic/account/mem_tables_loader_test.exs b/test/archethic/account/mem_tables_loader_test.exs index 418c62590..97299bdf1 100644 --- a/test/archethic/account/mem_tables_loader_test.exs +++ b/test/archethic/account/mem_tables_loader_test.exs @@ -2,7 +2,7 @@ defmodule Archethic.Account.MemTablesLoaderTest do @moduledoc false use ArchethicCase - alias Archethic.Account.MemTables.NFTLedger + alias Archethic.Account.MemTables.TokenLedger alias Archethic.Account.MemTables.UCOLedger alias Archethic.Account.MemTablesLoader @@ -51,9 +51,9 @@ defmodule Archethic.Account.MemTablesLoaderTest do %UnspentOutput{ from: "@Charlie3", amount: 1_000_000_000, - type: {:NFT, "@CharlieNFT", 0} + type: {:token, "@CharlieToken", 0} } - ] = NFTLedger.get_unspent_outputs("@Bob3") + ] = TokenLedger.get_unspent_outputs("@Bob3") end end @@ -80,9 +80,9 @@ defmodule Archethic.Account.MemTablesLoaderTest do %UnspentOutput{ from: "@Charlie3", amount: 1_000_000_000, - type: {:NFT, "@CharlieNFT", 0} + type: {:token, "@CharlieToken", 0} } - ] = NFTLedger.get_unspent_outputs("@Bob3") + ] = TokenLedger.get_unspent_outputs("@Bob3") end end @@ -98,7 +98,7 @@ defmodule Archethic.Account.MemTablesLoaderTest do %TransactionMovement{ to: "@Bob3", amount: 1_000_000_000, - type: {:NFT, "@CharlieNFT", 0} + type: {:token, "@CharlieToken", 0} } ], unspent_outputs: [ diff --git a/test/archethic/account_test.exs b/test/archethic/account_test.exs index 40764c9df..5f4d29ef9 100644 --- a/test/archethic/account_test.exs +++ b/test/archethic/account_test.exs @@ -3,7 +3,7 @@ defmodule Archethic.AccountTest do use ExUnit.Case alias Archethic.Account - alias Archethic.Account.MemTables.NFTLedger + alias Archethic.Account.MemTables.TokenLedger alias Archethic.Account.MemTables.UCOLedger alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput @@ -11,7 +11,7 @@ defmodule Archethic.AccountTest do describe "get_balance/1" do setup do start_supervised!(UCOLedger) - start_supervised!(NFTLedger) + start_supervised!(TokenLedger) :ok end @@ -36,22 +36,22 @@ defmodule Archethic.AccountTest do ~U[2021-03-05 13:41:34Z] ) - NFTLedger.add_unspent_output( + TokenLedger.add_unspent_output( "@Alice2", %UnspentOutput{ from: "@Charlie2", amount: 10_000_000_000, - type: {:NFT, "@CharlieNFT", 0} + type: {:token, "@CharlieToken", 0} }, ~U[2021-03-05 13:41:34Z] ) - assert %{uco: 400_000_000, nft: %{{"@CharlieNFT", 0} => 10_000_000_000}} == + assert %{uco: 400_000_000, token: %{{"@CharlieToken", 0} => 10_000_000_000}} == Account.get_balance("@Alice2") end test "should return 0 when no unspent outputs associated" do - assert %{uco: 0, nft: %{}} == Account.get_balance("@Alice2") + assert %{uco: 0, token: %{}} == Account.get_balance("@Alice2") end test "should return 0 when all the unspent outputs have been spent" do @@ -67,20 +67,20 @@ defmodule Archethic.AccountTest do ~U[2021-03-05 13:41:34Z] ) - NFTLedger.add_unspent_output( + TokenLedger.add_unspent_output( "@Alice2", %UnspentOutput{ from: "@Charlie2", amount: 10_000_000_000, - type: {:NFT, "@CharlieNFT", 0} + type: {:token, "@CharlieToken", 0} }, ~U[2021-03-05 13:41:34Z] ) UCOLedger.spend_all_unspent_outputs("@Alice2") - NFTLedger.spend_all_unspent_outputs("@Alice2") + TokenLedger.spend_all_unspent_outputs("@Alice2") - assert %{uco: 0, nft: %{}} == Account.get_balance("@Alice2") + assert %{uco: 0, token: %{}} == Account.get_balance("@Alice2") end end end diff --git a/test/archethic/bootstrap/sync_test.exs b/test/archethic/bootstrap/sync_test.exs index 04e6a4ade..6128f8b33 100644 --- a/test/archethic/bootstrap/sync_test.exs +++ b/test/archethic/bootstrap/sync_test.exs @@ -282,7 +282,7 @@ defmodule Archethic.Bootstrap.SyncTest do Application.get_env(:archethic, Archethic.Bootstrap.NetworkInit)[:genesis_pools] |> Enum.each(fn %{address: address, amount: amount} -> - assert %{uco: amount, nft: %{}} == Account.get_balance(address) + assert %{uco: amount, token: %{}} == Account.get_balance(address) end) end end diff --git a/test/archethic/contracts/interpreter/transaction_statements_test.exs b/test/archethic/contracts/interpreter/transaction_statements_test.exs index 53877c7f5..6ed7abec4 100644 --- a/test/archethic/contracts/interpreter/transaction_statements_test.exs +++ b/test/archethic/contracts/interpreter/transaction_statements_test.exs @@ -7,8 +7,8 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatementsTest do alias Archethic.TransactionChain.Transaction alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.Ledger - alias Archethic.TransactionChain.TransactionData.NFTLedger - alias Archethic.TransactionChain.TransactionData.NFTLedger.Transfer, as: NFTTransfer + alias Archethic.TransactionChain.TransactionData.TokenLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger.Transfer, as: TokenTransfer alias Archethic.TransactionChain.TransactionData.Ownership alias Archethic.TransactionChain.TransactionData.UCOLedger alias Archethic.TransactionChain.TransactionData.UCOLedger.Transfer, as: UCOTransfer diff --git a/test/archethic/contracts/interpreter_test.exs b/test/archethic/contracts/interpreter_test.exs index 040debaec..3ade27964 100644 --- a/test/archethic/contracts/interpreter_test.exs +++ b/test/archethic/contracts/interpreter_test.exs @@ -128,7 +128,7 @@ defmodule Archethic.Contracts.InterpreterTest do alias: Archethic.Contracts.Interpreter.TransactionStatements ], [:TransactionStatements]}, - :add_nft_transfer + :add_token_transfer ]}, [line: 4], [ @@ -139,11 +139,11 @@ defmodule Archethic.Contracts.InterpreterTest do 38, 169, 3, 101, 30, 216, 98, 86, 53, 24, 29, 218, 35, 111, 236, 194, 33, 209, 231, 228>>}, {"amount", 20_000_000_000}, - {"nft", + {"token", <<174, 180, 166, 245, 171, 109, 130, 190, 34, 60, 88, 103, 235, 165, 254, 97, 111, 82, 244, 16, 220, 248, 59, 69, 175, 241, 88, 221, 64, 174, 138, 195>>}, - {"nft_id", 0} + {"token_id", 0} ] ] } @@ -277,7 +277,7 @@ defmodule Archethic.Contracts.InterpreterTest do actions triggered_by: transaction do set_type transfer add_uco_transfer to: \"7F6661ACE282F947ACA2EF947D01BDDC90C65F09EE828BDADE2E3ED4258470B3\", amount: 1040000000 - add_nft_transfer to: \"30670455713E2CBECF94591226A903651ED8625635181DDA236FECC221D1E7E4\", amount: 20000000000, nft: \"AEB4A6F5AB6D82BE223C5867EBA5FE616F52F410DCF83B45AFF158DD40AE8AC3\", nft_id: 0 + add_token_transfer to: \"30670455713E2CBECF94591226A903651ED8625635181DDA236FECC221D1E7E4\", amount: 20000000000, token: \"AEB4A6F5AB6D82BE223C5867EBA5FE616F52F410DCF83B45AFF158DD40AE8AC3\", token_id: 0 set_content \"Receipt\" add_ownership secret: \"MyEncryptedSecret\", secret_key: \"MySecretKey\", authorized_public_keys: ["70C245E5D970B59DF65638BDD5D963EE22E6D892EA224D8809D0FB75D0B1907A"] add_recipient \"78273C5CBCEB8617F54380CC2F173DF2404DB676C9F10D546B6F395E6F3BDDEE\" @@ -482,11 +482,11 @@ defmodule Archethic.Contracts.InterpreterTest do test "should execute complex condition with if statements" do code = ~S""" condition inherit: [ - type: in?([transfer, nft]), + type: in?([transfer, token]), content: if type == transfer do regex_match?("reason transfer: (.*)") else - regex_match?("reason nft creation: (.*)") + regex_match?("reason token creation: (.*)") end, ] @@ -511,7 +511,10 @@ defmodule Archethic.Contracts.InterpreterTest do assert true == Interpreter.valid_conditions?(inherit_conditions, %{ - "next" => %{"type" => "nft", "content" => "reason nft creation: new super token"} + "next" => %{ + "type" => "token", + "content" => "reason token creation: new super token" + } }) assert true == @@ -542,7 +545,7 @@ defmodule Archethic.Contracts.InterpreterTest do # Send the new transaction set_type transfer - add_nft_transfer to: transaction.address, nft: contract.address, amount: token_to_credit, nft_id: nft_id + add_token_transfer to: transaction.address, token: contract.address, amount: token_to_credit, token_id: token_id end end """ diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 7a627c770..9afa07879 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -315,12 +315,12 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do assert :ok = PendingTransactionValidation.validate(tx) end - test "should return :ok when a transaction contains valid fields for NFT creation" do + test "should return :ok when a transaction contains valid fields for token creation" do tx_seed = :crypto.strong_rand_bytes(32) tx = Transaction.new( - :nft, + :token, %TransactionData{ content: Jason.encode!(%{ diff --git a/test/archethic/p2p/messages_test.exs b/test/archethic/p2p/messages_test.exs index 366051cb1..e0d580468 100644 --- a/test/archethic/p2p/messages_test.exs +++ b/test/archethic/p2p/messages_test.exs @@ -599,19 +599,19 @@ defmodule Archethic.P2P.MessageTest do end test "Balance message" do - nft_address = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> - nft_id = 0 + token_address = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> + token_id = 0 assert %Balance{ uco: 1_050_000_000, - nft: %{ - {nft_address, nft_id} => 1_000_000_000 + token: %{ + {token_address, token_id} => 1_000_000_000 } } == %Balance{ uco: 1_050_000_000, - nft: %{ - {nft_address, nft_id} => 1_000_000_000 + token: %{ + {token_address, token_id} => 1_000_000_000 } } |> Message.encode() diff --git a/test/archethic/transaction_chain/transaction/validation_stamp_test.exs b/test/archethic/transaction_chain/transaction/validation_stamp_test.exs index 4ab081ad3..31c8fac7c 100644 --- a/test/archethic/transaction_chain/transaction/validation_stamp_test.exs +++ b/test/archethic/transaction_chain/transaction/validation_stamp_test.exs @@ -56,7 +56,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStampTest do StreamData.one_of([ StreamData.constant(:UCO), StreamData.tuple( - {StreamData.constant(:NFT), StreamData.binary(length: 33), + {StreamData.constant(:token), StreamData.binary(length: 33), StreamData.positive_integer()} ) ]) @@ -73,7 +73,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStampTest do StreamData.one_of([ StreamData.constant(:UCO), StreamData.tuple( - {StreamData.constant(:NFT), StreamData.binary(length: 33), + {StreamData.constant(:token), StreamData.binary(length: 33), StreamData.positive_integer()} ) ]) diff --git a/test/archethic/transaction_chain/transaction_data/ledger/nft/transfer_test.exs b/test/archethic/transaction_chain/transaction_data/ledger/nft/transfer_test.exs index 7a17b730b..4e5da8ad7 100644 --- a/test/archethic/transaction_chain/transaction_data/ledger/nft/transfer_test.exs +++ b/test/archethic/transaction_chain/transaction_data/ledger/nft/transfer_test.exs @@ -1,8 +1,8 @@ -defmodule Archethic.TransactionChain.TransactionData.NFTLedger.TransferTest do +defmodule Archethic.TransactionChain.TransactionData.TokenLedger.TransferTest do @moduledoc false use ExUnit.Case - alias Archethic.TransactionChain.TransactionData.NFTLedger.Transfer + alias Archethic.TransactionChain.TransactionData.TokenLedger.Transfer doctest Transfer end diff --git a/test/archethic/transaction_chain/transaction_data/ledger/nft_test.exs b/test/archethic/transaction_chain/transaction_data/ledger/nft_test.exs deleted file mode 100644 index 6a4d61957..000000000 --- a/test/archethic/transaction_chain/transaction_data/ledger/nft_test.exs +++ /dev/null @@ -1,38 +0,0 @@ -defmodule Archethic.TransactionChain.TransactionData.NFTLedgerTest do - @moduledoc false - use ExUnit.Case - use ExUnitProperties - - alias Archethic.TransactionChain.TransactionData.NFTLedger - alias Archethic.TransactionChain.TransactionData.NFTLedger.Transfer - - doctest NFTLedger - - property "symmetric serialization/deserialization of NFT ledger" do - check all( - transfers <- - StreamData.map_of( - StreamData.binary(length: 32), - {StreamData.binary(length: 32), StreamData.positive_integer(), - StreamData.positive_integer()} - ) - ) do - transfers = - Enum.map(transfers, fn {nft, {to, amount, nft_id}} -> - %Transfer{ - nft: <<0::8, 0::8>> <> nft, - to: <<0::8, 0::8>> <> to, - amount: amount, - nft_id: nft_id - } - end) - - {nft_ledger, _} = - %NFTLedger{transfers: transfers} - |> NFTLedger.serialize() - |> NFTLedger.deserialize() - - assert nft_ledger.transfers == transfers - end - end -end diff --git a/test/archethic/transaction_chain/transaction_data/ledger_test.exs b/test/archethic/transaction_chain/transaction_data/ledger_test.exs index 6e49b43dc..8af4a2e77 100644 --- a/test/archethic/transaction_chain/transaction_data/ledger_test.exs +++ b/test/archethic/transaction_chain/transaction_data/ledger_test.exs @@ -3,7 +3,7 @@ defmodule Archethic.TransactionChain.TransactionData.LedgerTest do use ExUnit.Case alias Archethic.TransactionChain.TransactionData.Ledger - alias Archethic.TransactionChain.TransactionData.NFTLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger alias Archethic.TransactionChain.TransactionData.UCOLedger doctest Ledger diff --git a/test/archethic/transaction_chain/transaction_data/token_test.exs b/test/archethic/transaction_chain/transaction_data/token_test.exs new file mode 100644 index 000000000..352928681 --- /dev/null +++ b/test/archethic/transaction_chain/transaction_data/token_test.exs @@ -0,0 +1,38 @@ +defmodule Archethic.TransactionChain.TransactionData.TokenLedgerTest do + @moduledoc false + use ExUnit.Case + use ExUnitProperties + + alias Archethic.TransactionChain.TransactionData.TokenLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger.Transfer + + doctest TokenLedger + + property "symmetric serialization/deserialization of token ledger" do + check all( + transfers <- + StreamData.map_of( + StreamData.binary(length: 32), + {StreamData.binary(length: 32), StreamData.positive_integer(), + StreamData.positive_integer()} + ) + ) do + transfers = + Enum.map(transfers, fn {token, {to, amount, token_id}} -> + %Transfer{ + token: <<0::8, 0::8>> <> token, + to: <<0::8, 0::8>> <> to, + amount: amount, + token_id: token_id + } + end) + + {token_ledger, _} = + %TokenLedger{transfers: transfers} + |> TokenLedger.serialize() + |> TokenLedger.deserialize() + + assert token_ledger.transfers == transfers + end + end +end diff --git a/test/archethic/transaction_chain/transaction_test.exs b/test/archethic/transaction_chain/transaction_test.exs index 32ee6e6e8..a9eab8305 100644 --- a/test/archethic/transaction_chain/transaction_test.exs +++ b/test/archethic/transaction_chain/transaction_test.exs @@ -13,7 +13,7 @@ defmodule Archethic.TransactionChain.TransactionTest do alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.Ledger - alias Archethic.TransactionChain.TransactionData.NFTLedger + alias Archethic.TransactionChain.TransactionData.TokenLedger alias Archethic.TransactionChain.TransactionData.UCOLedger doctest Archethic.TransactionChain.Transaction diff --git a/test/archethic_web/controllers/api/transaction_payload_test.exs b/test/archethic_web/controllers/api/transaction_payload_test.exs index 831accbd3..71ec6e713 100644 --- a/test/archethic_web/controllers/api/transaction_payload_test.exs +++ b/test/archethic_web/controllers/api/transaction_payload_test.exs @@ -44,7 +44,7 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do "uco" => %{ "transfers" => [] }, - "nft" => %{ + "token" => %{ "transfers" => [] } }, @@ -232,7 +232,7 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do changeset |> get_errors() |> get_in([:data, :ledger, :uco]) end - test "should return an error if the nft ledger transfer address is invalid" do + test "should return an error if the token ledger transfer address is invalid" do changeset = %Ecto.Changeset{ valid?: false @@ -248,13 +248,14 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do "originSignature" => Base.encode16(:crypto.strong_rand_bytes(64)), "data" => %{ "ledger" => %{ - "nft" => %{ + "token" => %{ "transfers" => [ %{ "to" => "abc", "amount" => 10.0, - "nft" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), - "nft_id" => 0 + "token" => + Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), + "token_id" => 0 } ] } @@ -263,10 +264,10 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do }) assert [%{to: ["must be hexadecimal"]}] = - changeset |> get_errors() |> get_in([:data, :ledger, :nft, :transfers]) + changeset |> get_errors() |> get_in([:data, :ledger, :token, :transfers]) end - test "should return an error if the nft ledger transfer amount is invalid" do + test "should return an error if the token ledger transfer amount is invalid" do changeset = %Ecto.Changeset{ valid?: false @@ -282,13 +283,14 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do "originSignature" => Base.encode16(:crypto.strong_rand_bytes(64)), "data" => %{ "ledger" => %{ - "nft" => %{ + "token" => %{ "transfers" => [ %{ "to" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), "amount" => "abc", - "nft" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), - "nft_id" => 0 + "token" => + Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), + "token_id" => 0 } ] } @@ -302,10 +304,10 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do "is invalid" ] } - ] = changeset |> get_errors |> get_in([:data, :ledger, :nft, :transfers]) + ] = changeset |> get_errors |> get_in([:data, :ledger, :token, :transfers]) end - test "should return an error if the nft ledger transfer nft address is invalid" do + test "should return an error if the token ledger transfer token address is invalid" do changeset = %Ecto.Changeset{ valid?: false @@ -321,13 +323,13 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do "originSignature" => Base.encode16(:crypto.strong_rand_bytes(64)), "data" => %{ "ledger" => %{ - "nft" => %{ + "token" => %{ "transfers" => [ %{ "to" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), "amount" => 10.0, - "nft" => "abc", - "nft_id" => 0 + "token" => "abc", + "token_id" => 0 } ] } @@ -335,11 +337,11 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do } }) - assert [%{nft: ["must be hexadecimal"]}] = - changeset |> get_errors |> get_in([:data, :ledger, :nft, :transfers]) + assert [%{token: ["must be hexadecimal"]}] = + changeset |> get_errors |> get_in([:data, :ledger, :token, :transfers]) end - test "should return an error if the nft ledger transfers are more than 256" do + test "should return an error if the token ledger transfers are more than 256" do changeset = %Ecto.Changeset{ valid?: false @@ -355,7 +357,7 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do "originSignature" => Base.encode16(:crypto.strong_rand_bytes(64)), "data" => %{ "ledger" => %{ - "nft" => %{ + "token" => %{ "transfers" => 1..257 |> Enum.map(fn _ -> @@ -363,9 +365,9 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do "to" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), "amount" => Enum.random(1..100), - "nft" => + "token" => Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>), - "nft_id" => Enum.random(0..255) + "token_id" => Enum.random(0..255) } end) } @@ -373,8 +375,8 @@ defmodule ArchethicWeb.API.TransactionPayloadTest do } }) - assert %{transfers: ["maximum nft transfers in a transaction can be 256"]} = - changeset |> get_errors |> get_in([:data, :ledger, :nft]) + assert %{transfers: ["maximum token transfers in a transaction can be 256"]} = + changeset |> get_errors |> get_in([:data, :ledger, :token]) end test "should return an error if the encrypted secret is not an hexadecimal" do diff --git a/test/archethic_web/graphql_schema_test.exs b/test/archethic_web/graphql_schema_test.exs index ad6e92da8..79b15b4cd 100644 --- a/test/archethic_web/graphql_schema_test.exs +++ b/test/archethic_web/graphql_schema_test.exs @@ -287,30 +287,30 @@ defmodule ArchethicWeb.GraphQLSchemaTest do assert %{"data" => %{"balance" => %{"uco" => 2.18}}} = json_response(conn, 200) end - test "should retrieve the nft balance of an address", %{conn: conn} do + test "should retrieve the token balance of an address", %{conn: conn} do addr = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>> MockClient |> stub(:send_message, fn _, %GetBalance{}, _ -> {:ok, %Balance{ - nft: %{ - {"@NFT1", 0} => 200_000_000, - {"@NFT2", 0} => 500_000_000, - {"@NFT3", 0} => 1_000_000_000 + token: %{ + {"@Token1", 0} => 200_000_000, + {"@Token2", 0} => 500_000_000, + {"@Token3", 0} => 1_000_000_000 } }} end) conn = post(conn, "/api", %{ - "query" => "query { balance(address: \"#{Base.encode16(addr)}\") { nft { amount } } }" + "query" => "query { balance(address: \"#{Base.encode16(addr)}\") { token { amount } } }" }) assert %{ "data" => %{ "balance" => %{ - "nft" => [%{"amount" => 2.0}, %{"amount" => 5.0}, %{"amount" => 10.0}] + "token" => [%{"amount" => 2.0}, %{"amount" => 5.0}, %{"amount" => 10.0}] } } } = json_response(conn, 200) diff --git a/test/support/template.ex b/test/support/template.ex index f4b85e4b2..1493c1e78 100644 --- a/test/support/template.ex +++ b/test/support/template.ex @@ -2,7 +2,7 @@ defmodule ArchethicCase do @moduledoc false use ExUnit.CaseTemplate - alias Archethic.Account.MemTables.NFTLedger + alias Archethic.Account.MemTables.TokenLedger alias Archethic.Account.MemTables.UCOLedger alias Archethic.Crypto @@ -169,7 +169,7 @@ defmodule ArchethicCase do MockClient |> stub(:new_connection, fn _, _, _, _ -> {:ok, make_ref()} end) - start_supervised!(NFTLedger) + start_supervised!(TokenLedger) start_supervised!(UCOLedger) start_supervised!(KOLedger) start_supervised!(PendingLedger) From 9837de96bd200ce9337488deb8fc11b10818e369 Mon Sep 17 00:00:00 2001 From: Samuel Date: Thu, 23 Jun 2022 15:21:15 +0200 Subject: [PATCH 7/7] Set supply as big int --- lib/archethic/mining/pending_transaction_validation.ex | 7 +++++-- .../transaction/validation_stamp/ledger_operations.ex | 10 +++++----- .../mining/pending_transaction_validation_test.exs | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 3063cb638..daaeeddd5 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -317,16 +317,19 @@ defmodule Archethic.Mining.PendingTransactionValidation do with {:ok, json_token} <- Jason.decode(content), :ok <- ExJsonSchema.Validator.validate(schema, json_token), %{"type" => "non-fungible", "supply" => supply, "properties" => properties} - when length(properties) == supply <- json_token do + when length(properties) == supply / 100_000_000 <- json_token do :ok else {:error, reason} -> Logger.debug("Invalid token token specification: #{inspect(reason)}") {:error, "Invalid token transaction - Invalid specification"} - %{"type" => "fungible"} -> + %{"type" => "fungible", "properties" => properties} when length(properties) <= 1 -> :ok + %{"type" => "fungible"} -> + {:error, "Invalid token transaction - Fungible should have only 1 set of properties"} + %{"type" => "non-fungible"} -> {:error, "Invalid token transaction - Supply should match properties for non-fungible tokens"} diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex index 59a939863..6daa10376 100644 --- a/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp/ledger_operations.ex @@ -48,18 +48,18 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation ...> %Transaction{ ...> address: "@Token2", ...> type: :token, - ...> data: %TransactionData{content: "{\"supply\": 10, \"type\": \"fungible\" }"} + ...> data: %TransactionData{content: "{\"supply\": 1000000000, \"type\": \"fungible\" }"} ...> } ...> ) %LedgerOperations{ - unspent_outputs: [%UnspentOutput{from: "@Token2", amount: 1_000_000_000, type: {:token, "@Token2", 0}}] + unspent_outputs: [%UnspentOutput{from: "@Token2", amount: 1000000000, type: {:token, "@Token2", 0}}] } iex> LedgerOperations.from_transaction(%LedgerOperations{}, ...> %Transaction{ ...> address: "@Token2", ...> type: :token, - ...> data: %TransactionData{content: "{\"supply\": 10, \"type\": \"non-fungible\", \"properties\": [[],[],[],[],[],[],[],[],[],[]]}"} + ...> data: %TransactionData{content: "{\"supply\": 1000000000, \"type\": \"non-fungible\", \"properties\": [[],[],[],[],[],[],[],[],[],[]]}"} ...> } ...> ) %LedgerOperations{ @@ -99,7 +99,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation [ %UnspentOutput{ from: address, - amount: supply * @unit_uco, + amount: supply, type: {:token, address, 0} } ] @@ -109,7 +109,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperation %{"type" => "non-fungible", "supply" => supply, "properties" => properties}, address ) - when length(properties) == supply do + when length(properties) == supply / @unit_uco do properties |> Enum.with_index() |> Enum.map(fn {_item_properties, index} -> diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index 9afa07879..a30edd95f 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -324,9 +324,9 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do %TransactionData{ content: Jason.encode!(%{ - supply: 3, + supply: 300_000_000, name: "MyToken", - type: "fungible", + type: "non-fungible", symbol: "MTK", properties: [ [