From 2566253d027672e6f561c9cdc43eeaff216003c5 Mon Sep 17 00:00:00 2001 From: Bastien Chamagne Date: Fri, 4 Aug 2023 11:18:52 +0200 Subject: [PATCH] credo --- .../mining/pending_transaction_validation.ex | 39 +++--- .../transaction_chain/transaction.ex | 122 +++++++++++------- priv/json-schemas/token-core.json | 24 +++- priv/json-schemas/token-resupply.json | 28 +++- .../pending_transaction_validation_test.exs | 2 +- .../transaction_chain/transaction_test.exs | 19 +++ 6 files changed, 159 insertions(+), 75 deletions(-) diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index 0638ac6099..2e11eddc54 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -768,25 +768,28 @@ defmodule Archethic.Mining.PendingTransactionValidation do end defp verify_token_transaction(tx = %Transaction{data: %TransactionData{content: content}}) do - case Jason.decode(content) do - {:error, _} -> + with {:ok, json_token} <- Jason.decode(content), + :ok <- verify_token_creation(tx, json_token) do + verify_token_recipients(json_token) + else + {:error, %Jason.DecodeError{}} -> {:error, "Invalid token transaction - invalid JSON"} - {:ok, json_token} -> - cond do - ExJsonSchema.Validator.valid?(@token_creation_schema, json_token) -> - with :ok <- verify_token_recipients(json_token), - :ok <- verify_token_creation(json_token), - do: :ok + {:error, reason} -> + {:error, reason} + end + end - ExJsonSchema.Validator.valid?(@token_resupply_schema, json_token) -> - with :ok <- verify_token_recipients(json_token), - :ok <- verify_token_resupply(tx, json_token), - do: :ok + defp verify_token_creation(tx, json_token) do + cond do + ExJsonSchema.Validator.valid?(@token_creation_schema, json_token) -> + verify_token_creation(json_token) - true -> - {:error, "Invalid token transaction - neither a token creation nor a token resupply"} - end + ExJsonSchema.Validator.valid?(@token_resupply_schema, json_token) -> + verify_token_resupply(tx, json_token) + + true -> + {:error, "Invalid token transaction - neither a token creation nor a token resupply"} end end @@ -828,7 +831,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do end defp verify_token_resupply(tx, %{"token_reference" => token_ref}) do - with {:ok, token_address} <- Base.decode16(token_ref, case: :mixed), + with token_address <- Base.decode16!(token_ref, case: :mixed), # verify same chain {:ok, genesis_address} <- fetch_previous_tx_genesis_address(tx), storage_nodes <- @@ -868,9 +871,6 @@ defmodule Archethic.Mining.PendingTransactionValidation do {:error, %Jason.DecodeError{}} -> {:error, "Invalid token transaction - token_reference exists but does not contain a valid JSON"} - - :error -> - {:error, "Invalid token transaction - token_reference is not an hexadecimal"} end end @@ -883,7 +883,6 @@ defmodule Archethic.Mining.PendingTransactionValidation do with :ok <- validate_token_recipients_amounts(recipients, json_token), :ok <- validate_token_recipients_total(recipients, json_token), :ok <- validate_token_recipients_addresses(recipients) do - :ok end end end diff --git a/lib/archethic/transaction_chain/transaction.ex b/lib/archethic/transaction_chain/transaction.ex index c3276d0f46..dfd9b03ce3 100755 --- a/lib/archethic/transaction_chain/transaction.ex +++ b/lib/archethic/transaction_chain/transaction.ex @@ -18,6 +18,18 @@ defmodule Archethic.TransactionChain.Transaction do alias Archethic.Utils + @token_creation_schema :archethic + |> Application.app_dir("priv/json-schemas/token-core.json") + |> File.read!() + |> Jason.decode!() + |> ExJsonSchema.Schema.resolve() + + @token_resupply_schema :archethic + |> Application.app_dir("priv/json-schemas/token-resupply.json") + |> File.read!() + |> Jason.decode!() + |> ExJsonSchema.Schema.resolve() + @unit_uco 100_000_000 @version 1 @@ -448,56 +460,9 @@ defmodule Archethic.TransactionChain.Transaction do } ), case type do - :token -> - case Jason.decode(content) do - {:ok, json_content = %{"recipients" => recipients}} when is_list(recipients) -> - # resupply token transactions do not have a type, but is applied only to fungible tokens - fungible? = (json_content["type"] || "fungible") == "fungible" - - Enum.map(recipients, fn r = %{"to" => address_hex, "amount" => amount} -> - token_id = r["token_id"] || if fungible?, do: 0, else: 1 - - token_address = - case json_content["token_reference"] do - nil -> - # it's a token creation - tx_address - - token_reference_hex -> - # it's a token resupply - case Base.decode16(token_reference_hex, case: :mixed) do - {:ok, token_reference} -> - token_reference - - _ -> - # failure - "" - end - end - - case Base.decode16(address_hex, case: :mixed) do - {:ok, address} -> - if not fungible? && amount != @unit_uco do - [] - else - %TransactionMovement{ - to: address, - amount: amount, - type: {:token, token_address, token_id} - } - end - - _ -> - [] - end - end) - - _ -> - [] - end - - _ -> - [] + :token -> get_movements_from_token_transaction(tx_address, content) + :mint_reward -> get_movements_from_token_transaction(tx_address, content) + _ -> [] end ]) end @@ -984,4 +949,61 @@ defmodule Archethic.TransactionChain.Transaction do |> Enum.map(&CrossValidationStamp.cast/1) } end + + defp get_movements_from_token_transaction(tx_address, tx_content) do + case Jason.decode(tx_content) do + {:ok, json} -> + cond do + ExJsonSchema.Validator.valid?(@token_creation_schema, json) -> + get_movements_from_token_creation(tx_address, json) + + ExJsonSchema.Validator.valid?(@token_resupply_schema, json) -> + get_movements_from_token_resupply(json) + + true -> + [] + end + + {:error, _} -> + [] + end + end + + defp get_movements_from_token_creation(tx_address, json) do + recipients = json["recipients"] || [] + fungible? = json["type"] == "fungible" + + Enum.map(recipients, fn r = %{"to" => address_hex, "amount" => amount} -> + token_id = r["token_id"] || if fungible?, do: 0, else: 1 + address = Base.decode16!(address_hex, case: :mixed) + + if not fungible? and amount != @unit_uco do + nil + else + %TransactionMovement{ + to: address, + amount: amount, + type: {:token, tx_address, token_id} + } + end + end) + |> Enum.reject(&is_nil/1) + end + + defp get_movements_from_token_resupply(json) do + recipients = json["recipients"] || [] + token_reference = json["token_reference"] + + Enum.map(recipients, fn r = %{"to" => address_hex, "amount" => amount} -> + token_id = r["token_id"] || 0 + token_address = Base.decode16!(token_reference, case: :mixed) + address = Base.decode16!(address_hex, case: :mixed) + + %TransactionMovement{ + to: address, + amount: amount, + type: {:token, token_address, token_id} + } + end) + end end diff --git a/priv/json-schemas/token-core.json b/priv/json-schemas/token-core.json index f9bcc8eff0..1e3f2fb899 100644 --- a/priv/json-schemas/token-core.json +++ b/priv/json-schemas/token-core.json @@ -1,5 +1,19 @@ { "$schema": "http://json-schema.org/draft-07/schema#", + "$defs": { + "address": { + "oneOf": [ + { + "type": "string", + "pattern": "^0[0-2]0[025][0-9a-fA-F]{64}$" + }, + { + "type": "string", + "pattern": "^0[0-2]0[134][0-9a-fA-F]{128}$" + } + ] + } + }, "type": "object", "properties": { "supply": { @@ -44,9 +58,17 @@ "items": { "type": "object", "additionalProperties": false, + "required": [ + "to", + "amount" + ], "properties": { + "token_id": { + "type": "integer", + "description": "The index of the token in a collection" + }, "to": { - "type": "string", + "$ref": "#/$defs/address", "description": "Recipient address" }, "amount": { diff --git a/priv/json-schemas/token-resupply.json b/priv/json-schemas/token-resupply.json index a8e5bb6799..5b68ce468f 100644 --- a/priv/json-schemas/token-resupply.json +++ b/priv/json-schemas/token-resupply.json @@ -1,6 +1,20 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", + "$defs": { + "address": { + "oneOf": [ + { + "type": "string", + "pattern": "^0[0-2]0[025][0-9a-fA-F]{64}$" + }, + { + "type": "string", + "pattern": "^0[0-2]0[134][0-9a-fA-F]{128}$" + } + ] + } + }, "properties": { "supply": { "type": "integer", @@ -16,7 +30,7 @@ } }, "token_reference": { - "type": "string", + "$ref": "#/$defs/address", "description": "Address of the fungible token to resupply" }, "recipients": { @@ -25,10 +39,18 @@ "items": { "type": "object", "additionalProperties": false, + "required": [ + "to", + "amount" + ], "properties": { + "token_id": { + "type": "integer", + "description": "The index of the token in a collection" + }, "to": { - "type": "string", - "description": "Recipient address" + "$ref": "#/$defs/address", + "description": "The recipient's address" }, "amount": { "type": "integer", diff --git a/test/archethic/mining/pending_transaction_validation_test.exs b/test/archethic/mining/pending_transaction_validation_test.exs index bc33f83e98..030ee5f69f 100644 --- a/test/archethic/mining/pending_transaction_validation_test.exs +++ b/test/archethic/mining/pending_transaction_validation_test.exs @@ -1347,7 +1347,7 @@ defmodule Archethic.Mining.PendingTransactionValidationTest do 0 ) - assert {:error, "Invalid token transaction - token_reference is not an hexadecimal"} = + assert {:error, "Invalid token transaction - neither a token creation nor a token resupply"} = PendingTransactionValidation.validate(tx) end diff --git a/test/archethic/transaction_chain/transaction_test.exs b/test/archethic/transaction_chain/transaction_test.exs index 87e1c6743a..9c0c357b52 100644 --- a/test/archethic/transaction_chain/transaction_test.exs +++ b/test/archethic/transaction_chain/transaction_test.exs @@ -218,6 +218,8 @@ defmodule Archethic.TransactionChain.TransactionTest do test "should return an empty list if invalid transaction" do token = random_address() token_hex = token |> Base.encode16() + recipient1 = random_address() + recipient1_hex = recipient1 |> Base.encode16() assert [] = Transaction.get_movements( @@ -231,6 +233,23 @@ defmodule Archethic.TransactionChain.TransactionTest do ) ) + assert [] = + Transaction.get_movements( + TransactionFactory.create_valid_transaction([], + type: :token, + content: """ + { + "token_reference": "not an hexadecimal", + "supply": 100000000, + "recipients": [{ + "to": "#{recipient1_hex}", + "amount": 1000 + }] + } + """ + ) + ) + assert [] = Transaction.get_movements( TransactionFactory.create_valid_transaction([],