diff --git a/lib/archethic/contracts/interpreter.ex b/lib/archethic/contracts/interpreter.ex index 0ccac38252..6b7fde6146 100644 --- a/lib/archethic/contracts/interpreter.ex +++ b/lib/archethic/contracts/interpreter.ex @@ -133,7 +133,12 @@ defmodule Archethic.Contracts.Interpreter do ) :: {:ok, nil | Transaction.t(), State.t(), logs :: list(String.t())} | {:error, err :: String.t()} - | {:error, err :: Exception.t(), stacktrace :: term(), logs :: list(String.t())} + | { + :error, + err :: Exception.t() | String.t(), + stacktrace :: term(), + logs :: list(String.t()) + } def execute_trigger( trigger_key, diff --git a/lib/archethic/contracts/interpreter/condition_validator.ex b/lib/archethic/contracts/interpreter/condition_validator.ex index 0037ec226c..a8b1fd3b04 100644 --- a/lib/archethic/contracts/interpreter/condition_validator.ex +++ b/lib/archethic/contracts/interpreter/condition_validator.ex @@ -14,7 +14,9 @@ defmodule Archethic.Contracts.Interpreter.ConditionValidator do Determines if the conditions of a contract are valid from the given constants """ @spec execute_condition(Macro.t() | ConditionsSubjects.t(), map()) :: - {:ok, list(String.t())} | {:error, String.t(), list(String.t())} + {:ok, list(String.t())} + | {:error, subject :: String.t(), logs :: list(String.t())} + | {:error, subject :: String.t(), message :: String.t(), logs :: list(String.t())} def execute_condition(condition_or_ast, constants = %{}) do case condition_or_ast do %ConditionsSubjects{} -> diff --git a/lib/archethic/mining/distributed_workflow.ex b/lib/archethic/mining/distributed_workflow.ex index 28e314a1a7..cb386e97b4 100644 --- a/lib/archethic/mining/distributed_workflow.ex +++ b/lib/archethic/mining/distributed_workflow.ex @@ -1158,6 +1158,9 @@ defmodule Archethic.Mining.DistributedWorkflow do :invalid_proof_of_work -> {:invalid_transaction, "Invalid origin signature"} + {:invalid_recipients_execution, reason} -> + {:invalid_transaction, "Invalid recipient execution: #{reason}"} + reason -> {:network_issue, reason |> Atom.to_string() |> String.replace("_", " ")} end diff --git a/lib/archethic/mining/smart_contract_validation.ex b/lib/archethic/mining/smart_contract_validation.ex index 59fd863a6b..f9efbf509f 100644 --- a/lib/archethic/mining/smart_contract_validation.ex +++ b/lib/archethic/mining/smart_contract_validation.ex @@ -35,7 +35,7 @@ defmodule Archethic.Mining.SmartContractValidation do recipients :: list(Recipient.t()), transaction :: Transaction.t(), validation_time :: DateTime.t() - ) :: {true, fee :: non_neg_integer()} | {false, 0} + ) :: {:ok, fee :: non_neg_integer()} | {:error, String.t()} def validate_contract_calls( recipients, transaction = %Transaction{}, @@ -49,9 +49,13 @@ defmodule Archethic.Mining.SmartContractValidation do ordered: false, on_timeout: :kill_task ) - |> Enum.reduce_while({true, 0}, fn - {:ok, {_valid? = true, fee}}, {true, total_fee} -> {:cont, {true, total_fee + fee}} - _, _ -> {:halt, {false, 0}} + |> Enum.reduce_while(0, fn + {:ok, %SmartContractCallValidation{valid?: true, fee: fee}}, acc -> {:cont, acc + fee} + {:ok, %SmartContractCallValidation{valid?: false, reason: reason}}, _acc -> {:halt, reason} + end) + |> then(fn + n when is_integer(n) -> {:ok, n} + reason when is_binary(reason) -> {:error, reason} end) end @@ -216,13 +220,10 @@ defmodule Archethic.Mining.SmartContractValidation do ) do storage_nodes = Election.chain_storage_nodes(address, P2P.authorized_and_available_nodes()) - # We are strict on the results to achieve atomic commitment conflicts_resolver = fn results -> - if Enum.any?(results, &(&1.valid? == false)) do - %SmartContractCallValidation{valid?: false} - else - %SmartContractCallValidation{valid?: true} - end + # keep the first invalid result if any + # otherwise take the first + Enum.find(results, List.first(results), &(not &1.valid?)) end case P2P.quorum_read( @@ -235,8 +236,11 @@ defmodule Archethic.Mining.SmartContractValidation do conflicts_resolver, @timeout ) do - {:ok, %SmartContractCallValidation{valid?: valid?, fee: fee}} -> {valid?, fee} - _ -> {false, 0} + {:ok, call_validation = %SmartContractCallValidation{}} -> + call_validation + + {:error, :network_issue} -> + %SmartContractCallValidation{valid?: false, fee: 0, reason: "Network issue"} end end end diff --git a/lib/archethic/mining/standalone_workflow.ex b/lib/archethic/mining/standalone_workflow.ex index 0baef13ce1..34991e40b5 100644 --- a/lib/archethic/mining/standalone_workflow.ex +++ b/lib/archethic/mining/standalone_workflow.ex @@ -233,6 +233,9 @@ defmodule Archethic.Mining.StandaloneWorkflow do :invalid_proof_of_work -> {:invalid_transaction, "Invalid origin signature"} + {:invalid_recipients_execution, reason} -> + {:invalid_transaction, "Invalid recipient execution: #{reason}"} + reason -> {:network_issue, reason |> Atom.to_string() |> String.replace("_", " ")} end diff --git a/lib/archethic/mining/validation_context.ex b/lib/archethic/mining/validation_context.ex index 56e8d8f9a1..9122bb7b98 100644 --- a/lib/archethic/mining/validation_context.ex +++ b/lib/archethic/mining/validation_context.ex @@ -732,9 +732,15 @@ defmodule Archethic.Mining.ValidationContext do resolved_recipients = resolved_recipients(recipients, resolved_addresses) - {valid_contract_recipients?, contract_recipients_fee} = + validate_contract_recipients_result = validate_contract_recipients(tx, resolved_recipients, validation_time) + contract_recipients_fee = + case validate_contract_recipients_result do + {:ok, fee} -> fee + _ -> 0 + end + fee = calculate_fee( tx, @@ -765,7 +771,7 @@ defmodule Archethic.Mining.ValidationContext do resolved_recipients, validation_time, valid_contract_execution?, - valid_contract_recipients? + validate_contract_recipients_result ) } |> ValidationStamp.sign() @@ -848,7 +854,8 @@ defmodule Archethic.Mining.ValidationContext do resolved_recipients :: list(Recipient.t()), validation_time :: DateTime.t(), valid_contract_execution? :: boolean(), - valid_contract_recipients? :: boolean() + validate_contract_recipients_result :: + {:ok, fee :: non_neg_integer()} | {:error, String.t()} ) :: nil | ValidationStamp.error() defp get_validation_error( prev_tx, @@ -858,7 +865,7 @@ defmodule Archethic.Mining.ValidationContext do resolved_recipients, validation_time, valid_contract_execution?, - valid_contract_recipients? + validate_contract_recipients_result ) do cond do not valid_pending_transaction? -> @@ -876,11 +883,11 @@ defmodule Archethic.Mining.ValidationContext do not valid_contract_recipients_distinct?(resolved_recipients) -> :recipients_not_distinct - not valid_contract_recipients? -> - :invalid_recipients_execution - true -> - nil + case validate_contract_recipients_result do + {:ok, _} -> nil + {:error, reason} -> {:invalid_recipients_execution, reason} + end end end @@ -890,7 +897,7 @@ defmodule Archethic.Mining.ValidationContext do resolved_recipients_addresses == Enum.uniq(resolved_recipients_addresses) end - defp validate_contract_recipients(_tx, [], _validation_time), do: {true, 0} + defp validate_contract_recipients(_tx, [], _validation_time), do: {:ok, 0} defp validate_contract_recipients(tx, resolved_recipients, validation_time), do: SmartContractValidation.validate_contract_calls(resolved_recipients, tx, validation_time) @@ -1104,9 +1111,15 @@ defmodule Archethic.Mining.ValidationContext do {valid_contract_execution?, next_state} = SmartContractValidation.valid_contract_execution?(contract_context, prev_tx, tx) - {valid_contract_recipients?, contract_recipients_fee} = + validate_contract_recipients_result = validate_contract_recipients(tx, resolved_recipients, validation_time) + contract_recipients_fee = + case validate_contract_recipients_result do + {:ok, fee} -> fee + _ -> 0 + end + fee = calculate_fee( tx, @@ -1134,7 +1147,7 @@ defmodule Archethic.Mining.ValidationContext do stamp, context, valid_contract_execution?, - valid_contract_recipients?, + validate_contract_recipients_result, sufficient_funds?, resolved_recipients ) @@ -1204,7 +1217,7 @@ defmodule Archethic.Mining.ValidationContext do validation_time: validation_time }, valid_contract_execution?, - valid_contract_recipients?, + validate_contract_recipients_result, sufficient_funds?, resolved_recipients ) do @@ -1217,7 +1230,7 @@ defmodule Archethic.Mining.ValidationContext do resolved_recipients, validation_time, valid_contract_execution?, - valid_contract_recipients? + validate_contract_recipients_result ) error == expected_error diff --git a/lib/archethic/replication/transaction_validator.ex b/lib/archethic/replication/transaction_validator.ex index 20c6b242f4..6769313286 100644 --- a/lib/archethic/replication/transaction_validator.ex +++ b/lib/archethic/replication/transaction_validator.ex @@ -155,8 +155,8 @@ defmodule Archethic.Replication.TransactionValidator do |> SmartContractValidation.validate_contract_calls(tx, timestamp) case res do - {true, fees} -> {:ok, fees} - {false, _} -> {:error, :invalid_recipients_execution} + {:ok, fees} -> {:ok, fees} + {:error, reason} -> {:error, {:invalid_recipients_execution, reason}} end end diff --git a/lib/archethic/transaction_chain/transaction/validation_stamp.ex b/lib/archethic/transaction_chain/transaction/validation_stamp.ex index 037a823708..6966f4aa7e 100755 --- a/lib/archethic/transaction_chain/transaction/validation_stamp.ex +++ b/lib/archethic/transaction_chain/transaction/validation_stamp.ex @@ -27,7 +27,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do | :invalid_inherit_constraints | :insufficient_funds | :invalid_contract_execution - | :invalid_recipients_execution + | {:invalid_recipients_execution, String.t()} | :recipients_not_distinct @typedoc """ @@ -180,7 +180,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do <> + serialize_error(error)::binary>> end def serialize(%__MODULE__{ @@ -207,7 +207,7 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do <> + serialize_error(error)::binary, byte_size(signature)::8, signature::binary>> end @doc """ @@ -271,10 +271,9 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do {recipients_length, rest} = rest |> VarInt.get_value() - {recipients, <>} = - Utils.deserialize_addresses(rest, recipients_length, []) + {recipients, rest} = Utils.deserialize_addresses(rest, recipients_length, []) - error = deserialize_error(error_byte) + {error, rest} = deserialize_error(rest) <> = rest @@ -371,19 +370,32 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStamp do } end - defp serialize_error(nil), do: 0 - defp serialize_error(:invalid_pending_transaction), do: 1 - defp serialize_error(:invalid_inherit_constraints), do: 2 - defp serialize_error(:insufficient_funds), do: 3 - defp serialize_error(:invalid_contract_execution), do: 4 - defp serialize_error(:invalid_recipients_execution), do: 5 - defp serialize_error(:recipients_not_distinct), do: 6 - - defp deserialize_error(0), do: nil - defp deserialize_error(1), do: :invalid_pending_transaction - defp deserialize_error(2), do: :invalid_inherit_constraints - defp deserialize_error(3), do: :insufficient_funds - defp deserialize_error(4), do: :invalid_contract_execution - defp deserialize_error(5), do: :invalid_recipients_execution - defp deserialize_error(6), do: :recipients_not_distinct + defp serialize_error(nil), do: <<0::8>> + defp serialize_error(:invalid_pending_transaction), do: <<1::8>> + defp serialize_error(:invalid_inherit_constraints), do: <<2::8>> + defp serialize_error(:insufficient_funds), do: <<3::8>> + defp serialize_error(:invalid_contract_execution), do: <<4::8>> + + defp serialize_error({:invalid_recipients_execution, reason}) do + reason_length = byte_size(reason) + + reason_length_serialized = VarInt.from_value(reason_length) + <<5::8, reason_length_serialized::binary, reason::binary>> + end + + defp serialize_error(:recipients_not_distinct), do: <<6::8>> + + defp deserialize_error(<<0::8, rest::bitstring>>), do: {nil, rest} + defp deserialize_error(<<1::8, rest::bitstring>>), do: {:invalid_pending_transaction, rest} + defp deserialize_error(<<2::8, rest::bitstring>>), do: {:invalid_inherit_constraints, rest} + defp deserialize_error(<<3::8, rest::bitstring>>), do: {:insufficient_funds, rest} + defp deserialize_error(<<4::8, rest::bitstring>>), do: {:invalid_contract_execution, rest} + + defp deserialize_error(<<5::8, rest::bitstring>>) do + {reason_length, rest} = VarInt.get_value(rest) + <> = rest + {{:invalid_recipients_execution, reason}, rest} + end + + defp deserialize_error(<<6::8, rest::bitstring>>), do: {:recipients_not_distinct, rest} end diff --git a/lib/archethic_web/api/jsonrpc/methods/estimate_transaction_fee.ex b/lib/archethic_web/api/jsonrpc/methods/estimate_transaction_fee.ex index 255c993cf9..7ea4e133a5 100644 --- a/lib/archethic_web/api/jsonrpc/methods/estimate_transaction_fee.ex +++ b/lib/archethic_web/api/jsonrpc/methods/estimate_transaction_fee.ex @@ -51,8 +51,11 @@ defmodule ArchethicWeb.API.JsonRPC.Method.EstimateTransactionFee do resolved_recipients = resolve_recipient_addresses(tx, timestamp) - {_valid?, recipients_fee} = - SmartContractValidation.validate_contract_calls(resolved_recipients, tx, timestamp) + recipients_fee = + case SmartContractValidation.validate_contract_calls(resolved_recipients, tx, timestamp) do + {:ok, fees} -> fees + _ -> 0 + end # not possible to have a contract's state here fee = Mining.get_transaction_fee(tx, nil, uco_usd, timestamp, nil, recipients_fee) diff --git a/test/archethic/mining/smart_contract_validation_test.exs b/test/archethic/mining/smart_contract_validation_test.exs index 0e6769e860..2ea06537d6 100644 --- a/test/archethic/mining/smart_contract_validation_test.exs +++ b/test/archethic/mining/smart_contract_validation_test.exs @@ -15,7 +15,7 @@ defmodule Archethic.Mining.SmartContractValidationTest do import Mox describe "validate_contract_calls/2" do - test "should returns {true, fees} if all contracts calls are valid" do + test "should returns fees if all contracts calls are valid" do MockClient |> stub( :send_message, @@ -41,7 +41,7 @@ defmodule Archethic.Mining.SmartContractValidationTest do P2P.add_and_connect_node(node) - assert {true, 777_777} = + assert {:ok, 777_777} = SmartContractValidation.validate_contract_calls( [ %Recipient{address: "@SC1"}, @@ -52,13 +52,13 @@ defmodule Archethic.Mining.SmartContractValidationTest do ) end - test "should returns {false, 0} if any contract is invalid" do + test "should returns error if any contract is invalid" do MockClient |> stub( :send_message, fn _, %ValidateSmartContractCall{recipient: %Recipient{address: "@SC1"}}, _ -> - {:ok, %SmartContractCallValidation{valid?: false, fee: 0}} + {:ok, %SmartContractCallValidation{valid?: false, reason: "REASON", fee: 0}} _, %ValidateSmartContractCall{recipient: %Recipient{address: "@SC2"}}, _ -> {:ok, %SmartContractCallValidation{valid?: true, fee: 0}} @@ -78,7 +78,7 @@ defmodule Archethic.Mining.SmartContractValidationTest do P2P.add_and_connect_node(node) - assert {false, 0} = + assert {:error, "REASON"} = SmartContractValidation.validate_contract_calls( [ %Recipient{address: "@SC1"}, @@ -93,7 +93,7 @@ defmodule Archethic.Mining.SmartContractValidationTest do ) end - test "should returns {false, 0} if one node replying asserting the contract is invalid" do + test "should returns error if one node replying asserting the contract is invalid" do MockClient |> stub( :send_message, @@ -101,7 +101,7 @@ defmodule Archethic.Mining.SmartContractValidationTest do %Node{port: 1234}, %ValidateSmartContractCall{recipient: %Recipient{address: "@SC1"}}, _ -> - {:ok, %SmartContractCallValidation{valid?: false, fee: 0}} + {:ok, %SmartContractCallValidation{valid?: false, reason: "REASON", fee: 0}} %Node{port: 1235}, %ValidateSmartContractCall{recipient: %Recipient{address: "@SC1"}}, @@ -135,7 +135,7 @@ defmodule Archethic.Mining.SmartContractValidationTest do P2P.add_and_connect_node(node1) P2P.add_and_connect_node(node2) - assert {false, 0} = + assert {:error, "REASON"} = SmartContractValidation.validate_contract_calls( [%Recipient{address: "@SC1"}], %Transaction{}, @@ -143,13 +143,63 @@ defmodule Archethic.Mining.SmartContractValidationTest do ) end - test "should returns {false, 0} if one smart contract is invalid" do + test "should resolve the conflict" do + MockClient + |> stub( + :send_message, + fn + %Node{port: 1234}, + %ValidateSmartContractCall{recipient: %Recipient{address: "@SC1"}}, + _ -> + {:ok, %SmartContractCallValidation{valid?: true, fee: 777_777}} + + %Node{port: 1235}, + %ValidateSmartContractCall{recipient: %Recipient{address: "@SC1"}}, + _ -> + {:ok, %SmartContractCallValidation{valid?: true, fee: 123_456}} + end + ) + + node1 = %Node{ + ip: "127.0.0.1", + port: 1234, + first_public_key: :crypto.strong_rand_bytes(32), + last_public_key: :crypto.strong_rand_bytes(32), + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now(), + geo_patch: "AAA" + } + + node2 = %Node{ + ip: "127.0.0.1", + port: 1235, + first_public_key: :crypto.strong_rand_bytes(32), + last_public_key: :crypto.strong_rand_bytes(32), + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now(), + geo_patch: "AAA" + } + + P2P.add_and_connect_node(node1) + P2P.add_and_connect_node(node2) + + assert {:ok, _} = + SmartContractValidation.validate_contract_calls( + [%Recipient{address: "@SC1"}], + %Transaction{}, + DateTime.utc_now() + ) + end + + test "should return error if one smart contract is invalid" do MockClient |> stub( :send_message, fn _, %ValidateSmartContractCall{recipient: %Recipient{address: "@SC1"}}, _ -> - {:ok, %SmartContractCallValidation{valid?: false, fee: 0}} + {:ok, %SmartContractCallValidation{valid?: false, reason: "REASON", fee: 0}} _, %ValidateSmartContractCall{recipient: %Recipient{address: "@SC2"}}, _ -> {:ok, %SmartContractCallValidation{valid?: true, fee: 123_456}} @@ -181,7 +231,7 @@ defmodule Archethic.Mining.SmartContractValidationTest do P2P.add_and_connect_node(node1) P2P.add_and_connect_node(node2) - assert {false, 0} = + assert {:error, "REASON"} = SmartContractValidation.validate_contract_calls( [%Recipient{address: "@SC1"}, %Recipient{address: "@SC2"}], %Transaction{}, diff --git a/test/archethic/mining/validation_context_test.exs b/test/archethic/mining/validation_context_test.exs index d2314fb47c..0bf36cf285 100644 --- a/test/archethic/mining/validation_context_test.exs +++ b/test/archethic/mining/validation_context_test.exs @@ -198,6 +198,7 @@ defmodule Archethic.Mining.ValidationContextTest do last_public_key: "key1", first_public_key: "key1", geo_patch: "AAA", + network_patch: "AAA", ip: {127, 0, 0, 1}, port: 3000, reward_address: :crypto.strong_rand_bytes(32), @@ -209,6 +210,7 @@ defmodule Archethic.Mining.ValidationContextTest do first_public_key: Crypto.last_node_public_key(), last_public_key: Crypto.last_node_public_key(), geo_patch: "AAA", + network_patch: "AAA", ip: {127, 0, 0, 1}, port: 3000, reward_address: :crypto.strong_rand_bytes(32), @@ -221,6 +223,7 @@ defmodule Archethic.Mining.ValidationContextTest do first_public_key: "key2", last_public_key: "key2", geo_patch: "AAA", + network_patch: "AAA", ip: {127, 0, 0, 1}, port: 3000, reward_address: :crypto.strong_rand_bytes(32), @@ -231,6 +234,7 @@ defmodule Archethic.Mining.ValidationContextTest do first_public_key: "key3", last_public_key: "key3", geo_patch: "AAA", + network_patch: "AAA", ip: {127, 0, 0, 1}, port: 3000, reward_address: :crypto.strong_rand_bytes(32), @@ -244,6 +248,7 @@ defmodule Archethic.Mining.ValidationContextTest do last_public_key: "key2", first_public_key: "key2", geo_patch: "AAA", + network_patch: "AAA", available?: true, reward_address: :crypto.strong_rand_bytes(32), authorized?: true, @@ -253,6 +258,7 @@ defmodule Archethic.Mining.ValidationContextTest do last_public_key: "key3", first_public_key: "key3", geo_patch: "DEA", + network_patch: "DEA", available?: true, reward_address: :crypto.strong_rand_bytes(32), authorized?: true, diff --git a/test/archethic/replication/transaction_validator_test.exs b/test/archethic/replication/transaction_validator_test.exs index 2c99a4d099..3013863bd1 100644 --- a/test/archethic/replication/transaction_validator_test.exs +++ b/test/archethic/replication/transaction_validator_test.exs @@ -245,10 +245,10 @@ defmodule Archethic.Replication.TransactionValidatorTest do {:ok, %LastTransactionAddress{address: random_address()}} end) |> expect(:send_message, fn _, %ValidateSmartContractCall{}, _ -> - {:ok, %SmartContractCallValidation{valid?: false, fee: 0}} + {:ok, %SmartContractCallValidation{valid?: false, reason: "nope", fee: 0}} end) - assert {:error, :invalid_recipients_execution} = + assert {:error, {:invalid_recipients_execution, "nope"}} = TransactionValidator.validate(tx, nil, unspent_outputs, nil) end end diff --git a/test/archethic/transaction_chain/transaction/validation_stamp_test.exs b/test/archethic/transaction_chain/transaction/validation_stamp_test.exs index 86f2e71eca..94bc61a500 100644 --- a/test/archethic/transaction_chain/transaction/validation_stamp_test.exs +++ b/test/archethic/transaction_chain/transaction/validation_stamp_test.exs @@ -15,6 +15,33 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStampTest do doctest ValidationStamp + property "serialize/deserialize" do + check all( + proof_of_work <- StreamData.binary(length: 32), + proof_of_integrity <- StreamData.binary(length: 32), + signature <- StreamData.binary(length: 32), + proof_of_election <- StreamData.binary(length: 64), + ledger_operations <- gen_ledger_operations(), + error <- gen_error() + ) do + stamp = %ValidationStamp{ + timestamp: DateTime.utc_now() |> DateTime.truncate(:millisecond), + proof_of_work: <<0::8, 0::8, proof_of_work::binary>>, + proof_of_integrity: <<0::8, proof_of_integrity::binary>>, + proof_of_election: proof_of_election, + ledger_operations: ledger_operations, + protocol_version: current_protocol_version(), + error: error, + signature: signature + } + + assert {^stamp, <<>>} = + stamp + |> ValidationStamp.serialize() + |> ValidationStamp.deserialize() + end + end + property "symmetric sign/valid validation stamp" do check all( proof_of_work <- StreamData.binary(length: 33), @@ -51,38 +78,57 @@ defmodule Archethic.TransactionChain.Transaction.ValidationStampTest do end end + defp gen_error do + StreamData.one_of([ + StreamData.constant(nil), + StreamData.constant(:invalid_pending_transaction), + StreamData.constant(:invalid_inherit_constraints), + StreamData.constant(:insufficient_funds), + StreamData.constant(:invalid_contract_execution), + StreamData.constant({:invalid_recipients_execution, "A custom error message"}), + StreamData.constant(:recipients_not_distinct) + ]) + end + defp gen_transaction_movement do gen all( - to <- StreamData.binary(length: 33), + to <- StreamData.binary(length: 32), amount <- StreamData.positive_integer(), type <- StreamData.one_of([ StreamData.constant(:UCO), StreamData.tuple( - {StreamData.constant(:token), StreamData.binary(length: 33), + {StreamData.constant(:token), + StreamData.binary(length: 32) |> StreamData.map(&<<0::16, &1::binary>>), StreamData.positive_integer()} ) ]) ) do - %TransactionMovement{to: to, amount: amount, type: type} + %TransactionMovement{to: <<0::16, to::binary>>, amount: amount, type: type} end end defp gen_unspent_outputs do gen all( - from <- StreamData.binary(length: 33), + from <- StreamData.binary(length: 32), amount <- StreamData.positive_integer(), timestamp <- StreamData.constant(DateTime.utc_now() |> DateTime.truncate(:millisecond)), type <- StreamData.one_of([ StreamData.constant(:UCO), StreamData.tuple( - {StreamData.constant(:token), StreamData.binary(length: 33), + {StreamData.constant(:token), + StreamData.binary(length: 32) |> StreamData.map(&<<0::16, &1::binary>>), StreamData.positive_integer()} ) ]) ) do - %UnspentOutput{from: from, amount: amount, type: type, timestamp: timestamp} + %UnspentOutput{ + from: <<0::16, from::binary>>, + amount: amount, + type: type, + timestamp: timestamp + } end end end