Skip to content

Commit

Permalink
added get_token_id function in smart contract library
Browse files Browse the repository at this point in the history
  • Loading branch information
tenmoves committed Jan 27, 2023
1 parent dc716e7 commit 8e1a216
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 35 deletions.
8 changes: 8 additions & 0 deletions lib/archethic/contracts/interpreter/condition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@ defmodule Archethic.Contracts.ConditionInterpreter do
{node, acc}
end

# Whitelist the get_token_id/1 function in the condition
defp prewalk(
node = {{:atom, "get_token_id"}, _, [_search]},
acc = {:ok, %{scope: {:condition, _, _}}}
) do
{node, acc}
end

# Whitelist the hash/0 function in the condition
defp prewalk(
node = {{:atom, "hash"}, _, []},
Expand Down
35 changes: 35 additions & 0 deletions lib/archethic/contracts/interpreter/library.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ defmodule Archethic.Contracts.Interpreter.Library do
Contracts.Interpreter.Utils
}

alias Archethic.Utils

require Logger

@doc """
Match a regex expression
Expand Down Expand Up @@ -228,6 +232,37 @@ defmodule Archethic.Contracts.Interpreter.Library do
@spec timestamp() :: non_neg_integer()
def timestamp, do: DateTime.utc_now() |> DateTime.to_unix()

@doc """
Provide a token id which uniquely identify the token base on it's properties and genesis address.
"""
@spec get_token_id(binary()) :: {:error, binary()} | {:ok, binary()}
def get_token_id(address) do
t1 = Task.async(fn -> Archethic.fetch_genesis_address_remotely(address) end)
t2 = Task.async(fn -> Utils.get_transaction_content(address) end)

with {:ok, {:ok, genesis_address}} <- Task.yield(t1),
{:ok, {:ok, definition}} <- Task.yield(t2) do
Utils.get_token_id(genesis_address, definition)
else
{:ok, {:error, :network_issue}} ->
{:error, "Network issue"}

{:ok, {:error, :decode_error}} ->
{:error, "Error in decoding transaction"}

{:ok, {:error, :not_a_token_transaction}} ->
{:error, "Transaction is not of type token"}

{:exit, reason} ->
Logger.debug("Task exited with reason")
Logger.debug(reason)
{:error, "Task Exited!"}

nil ->
{:error, "Task didn't responded within timeout!"}
end
end

@doc """
Get the genesis address of the chain
"""
Expand Down
9 changes: 9 additions & 0 deletions lib/archethic/contracts/interpreter/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,15 @@ defmodule Archethic.Contracts.Interpreter.Utils do
{node, acc}
end

# Whitelist the get_token_id/0 function
def prewalk(
node = {{:atom, "get_token_id"}, _, [_address]},
acc = {:ok, %{scope: scope}}
)
when scope != :root do
{node, acc}
end

# Whitelist the timestamp/0 function in condition
def prewalk(node = {{:atom, "timestamp"}, _, _}, acc = {:ok, %{scope: scope}})
when scope != :root do
Expand Down
47 changes: 47 additions & 0 deletions lib/archethic/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ defmodule Archethic.Utils do

alias Archethic.P2P.Node

alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.TransactionData

alias Archethic.Reward.Scheduler, as: RewardScheduler

import Bitwise
Expand Down Expand Up @@ -913,4 +916,48 @@ defmodule Archethic.Utils do
|> Stream.take_while(&(NaiveDateTime.compare(&1, end_of_month_datetime) in [:lt]))
|> Enum.count()
end

@doc """
computes token id based on the genesis address and transaction content
"""
@spec get_token_id(binary(), map()) :: binary()
def get_token_id(genesis_address, transaction_content) do
properties = Map.get(transaction_content, "properties", %{})
decimals = Map.get(transaction_content, "decimals", 8)
name = Map.get(transaction_content, "name", "")
symbol = Map.get(transaction_content, "symbol", "")

data_to_digest = %{
genesis_address: Base.encode16(genesis_address),
name: name,
symbol: symbol,
properties: properties,
decimals: decimals
}

:crypto.hash(:sha256, Jason.encode!(data_to_digest))
|> Base.encode16()
end

@doc """
get transaction content based on the address of the transaction
"""
@spec get_transaction_content(binary()) :: {:ok, map()} | {:error, atom()}
def get_transaction_content(address) do
case Archethic.search_transaction(address) do
{:ok,
%Transaction{data: %TransactionData{content: content, ownerships: ownerships}, type: type}}
when type in [:token, :mint_rewards] ->
case Jason.decode(content) do
{:ok, map} ->
{:ok, map |> Map.put("ownerships", ownerships)}

_ ->
{:error, :decode_error}
end

_ ->
{:error, :not_a_token_transaction}
end
end
end
35 changes: 5 additions & 30 deletions lib/archethic_web/graphql_schema/resolver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ defmodule ArchethicWeb.GraphQLSchema.Resolver do

alias Archethic.TransactionChain
alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.TransactionData
alias Archethic.TransactionChain.TransactionInput

alias Archethic.Mining

alias Archethic.Utils

require Logger

@limit_page 10
Expand Down Expand Up @@ -54,7 +55,7 @@ defmodule ArchethicWeb.GraphQLSchema.Resolver do

def get_token(address) do
t1 = Task.async(fn -> Archethic.fetch_genesis_address_remotely(address) end)
t2 = Task.async(fn -> get_transaction_content(address) end)
t2 = Task.async(fn -> Utils.get_transaction_content(address) end)

with {:ok, {:ok, genesis_address}} <- Task.yield(t1),
{:ok,
Expand All @@ -70,15 +71,7 @@ defmodule ArchethicWeb.GraphQLSchema.Resolver do
name = Map.get(definition, "name", "")
symbol = Map.get(definition, "symbol", "")

data_to_digest = %{
genesis_address: Base.encode16(genesis_address),
name: name,
symbol: symbol,
properties: properties,
decimals: decimals
}

token_id = :crypto.hash(:sha256, Jason.encode!(data_to_digest)) |> Base.encode16()
token_id = Utils.get_token_id(genesis_address, definition)

{:ok,
%{
Expand All @@ -101,7 +94,7 @@ defmodule ArchethicWeb.GraphQLSchema.Resolver do
{:error, "Error in decoding transaction"}

{:ok, {:error, :transaction_not_found}} ->
{:error, "Transaction does not exist!"}
{:error, "Transaction is not of type token"}

{:exit, reason} ->
Logger.debug("Task exited with reason")
Expand All @@ -113,24 +106,6 @@ defmodule ArchethicWeb.GraphQLSchema.Resolver do
end
end

defp get_transaction_content(address) do
case Archethic.search_transaction(address) do
{:ok,
%Transaction{data: %TransactionData{content: content, ownerships: ownerships}, type: type}}
when type in [:token, :mint_rewards] ->
case Jason.decode(content) do
{:ok, map} ->
{:ok, map |> Map.put("ownerships", ownerships)}

_ ->
{:error, :decode_error}
end

_ ->
{:error, :transaction_not_found}
end
end

def get_inputs(address, paging_offset \\ 0, limit \\ 0) do
inputs =
address
Expand Down
61 changes: 56 additions & 5 deletions test/archethic/contracts/interpreter/library_test.exs
Original file line number Diff line number Diff line change
@@ -1,17 +1,68 @@
defmodule Archethic.Contracts.Interpreter.LibraryTest do
use ArchethicCase

alias Archethic.{Contracts.Interpreter.Library, P2P, P2P.Node}
alias Archethic.Contracts.Interpreter.Library

alias P2P.Message.{
GetFirstTransactionAddress,
FirstTransactionAddress
}
alias P2P.Message.GetFirstTransactionAddress
alias P2P.Message.FirstTransactionAddress

alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.TransactionData

alias Archethic.Utils

alias Archethic.P2P
alias Archethic.P2P.Node

doctest Library

import Mox

describe "get_token_id\1" do
test "should return token_id given the address of the transaction" do
tx_seed = :crypto.strong_rand_bytes(32)

tx =
Transaction.new(
:token,
%TransactionData{
content:
Jason.encode!(%{
supply: 300_000_000,
name: "MyToken",
type: "non-fungible",
symbol: "MTK",
properties: %{
global: "property"
},
collection: [
%{image: "link", value: "link"},
%{image: "link", value: "link"},
%{image: "link", value: "link"}
]
})
},
tx_seed,
0
)

genesis_address = "@Alice1"

transaction_content =
tx.data.content
|> Jason.decode!()

MockDB
|> stub(:get_transaction, fn _, _ -> {:ok, tx} end)
|> stub(:get_genesis_address, fn _ -> genesis_address end)

assert Utils.get_token_id(genesis_address, transaction_content) ==
Library.get_token_id(tx.address)
end
end

import Mox

test "get_first_transaction_address/1" do
P2P.add_and_connect_node(%Node{
ip: {127, 0, 0, 1},
Expand Down

0 comments on commit 8e1a216

Please sign in to comment.