Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token standard #390

Merged
7 commits merged into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions lib/archethic/account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 """
Expand All @@ -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

Expand Down
70 changes: 36 additions & 34 deletions lib/archethic/account/mem_tables/nft_ledger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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}
]
}

Expand All @@ -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)}",
Expand All @@ -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()
Expand All @@ -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
]
Expand All @@ -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")
[]
Expand All @@ -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
Expand All @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions lib/archethic/account/mem_tables_loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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
Expand Down
9 changes: 5 additions & 4 deletions lib/archethic/contracts/contract/constants.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
Expand Down
5 changes: 3 additions & 2 deletions lib/archethic/contracts/interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
22 changes: 16 additions & 6 deletions lib/archethic/contracts/interpreter/transaction_statements.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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
}
]
}
Expand All @@ -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

Expand All @@ -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"
}
}
"""
Expand All @@ -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()
Expand Down
14 changes: 9 additions & 5 deletions lib/archethic/contracts/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
Loading