From 736c77516139b900aea5cad208db4dc5252cb419 Mon Sep 17 00:00:00 2001 From: Yan Guiborat Date: Tue, 20 Dec 2022 14:51:14 +0100 Subject: [PATCH] Validate Smart Contracts implementation before the SC merge --- lib/archethic.ex | 31 +- lib/archethic/contracts.ex | 133 ++++++ lib/archethic/contracts/interpreter.ex | 4 +- .../legacy/condition_interpreter.ex | 3 +- lib/archethic/contracts/worker.ex | 46 +- .../controllers/api/transaction_controller.ex | 85 ++++ lib/archethic_web/explorer_router.ex | 6 + test/archethic/contracts_test.exs | 70 +++ .../transaction_chain/transaction_test.exs | 1 - .../api/transaction_controller_test.exs | 405 ++++++++++++++++++ 10 files changed, 755 insertions(+), 29 deletions(-) diff --git a/lib/archethic.ex b/lib/archethic.ex index 6621cf98ef..f1fd0c2e30 100644 --- a/lib/archethic.ex +++ b/lib/archethic.ex @@ -4,14 +4,19 @@ defmodule Archethic do """ alias Archethic.SharedSecrets - alias __MODULE__.{Account, BeaconChain, Crypto, Election, P2P, P2P.Node, P2P.Message} + alias __MODULE__.{Account, BeaconChain, Contracts, Crypto, Election, P2P, P2P.Node, P2P.Message} alias __MODULE__.{SelfRepair, TransactionChain} alias Message.{NewTransaction, NotFound, StartMining, TransactionSummaryList} alias Message.{Balance, GetBalance, GetCurrentSummaries, GetTransactionSummary} alias Message.{StartMining, Ok, TransactionSummaryMessage} - alias TransactionChain.{Transaction, TransactionInput, TransactionSummary} + alias TransactionChain.{ + Transaction, + TransactionInput, + TransactionSummary, + TransactionData + } require Logger @@ -302,6 +307,28 @@ defmodule Archethic do end end + @doc """ + Assert the transaction holds a contract and then simulate its execution. + Return an error if the transaction holds no contract. + """ + @spec simulate_contract_execution(Transaction.t(), Transaction.t()) :: + :ok | {:error, reason :: term()} + def simulate_contract_execution( + prev_tx = %Transaction{data: %TransactionData{code: code}}, + incoming_tx + ) + when code != "" do + Contracts.simulate_contract_execution(prev_tx, incoming_tx, DateTime.utc_now()) + end + + ## Empty contracts are considered invalid + def simulate_contract_execution( + _prev_tx, + _next_tx + ) do + {:error, :no_contract} + end + @doc """ Retrieve the number of transaction in a transaction chain from the closest nodes """ diff --git a/lib/archethic/contracts.ex b/lib/archethic/contracts.ex index b807bd7334..d16f56a751 100644 --- a/lib/archethic/contracts.ex +++ b/lib/archethic/contracts.ex @@ -18,6 +18,8 @@ defmodule Archethic.Contracts do alias Archethic.TransactionChain.Transaction alias Archethic.TransactionChain.TransactionData + alias Archethic.Contracts.ContractConstants, as: Constants + require Logger @extended_mode? Mix.env() != :prod @@ -165,6 +167,137 @@ defmodule Archethic.Contracts do end end + @doc """ + Simulate the execution of the contract hold in prev_tx with the inputs of next_tx, at a certain date + """ + + @spec simulate_contract_execution(Transaction.t(), Transaction.t(), DateTime.t()) :: + :ok | {:error, reason :: term()} + def simulate_contract_execution( + prev_tx = %Transaction{data: %TransactionData{code: code}}, + incoming_tx = %Transaction{}, + date = %DateTime{} + ) do + case Interpreter.parse(code) do + {:ok, + %Contract{ + version: version, + triggers: triggers, + conditions: conditions + }} -> + triggers + |> Enum.find_value(:ok, fn {trigger_type, trigger_code} -> + do_simulate_contract( + version, + trigger_code, + trigger_type, + conditions, + prev_tx, + incoming_tx, + date + ) + end) + + {:error, reason} -> + {:error, reason} + end + end + + defp do_simulate_contract( + version, + trigger_code, + trigger_type, + conditions, + prev_tx, + incoming_tx, + date + ) do + case valid_from_trigger?(trigger_type, incoming_tx, date) do + true -> + case validate_transaction_conditions( + version, + trigger_type, + conditions, + prev_tx, + incoming_tx + ) do + :ok -> + validate_inherit_conditions(version, trigger_code, conditions, prev_tx, incoming_tx) + + {:error, reason} -> + {:error, reason} + end + + false -> + {:error, :invalid_trigger} + end + end + + defp validate_transaction_conditions( + version, + trigger_type, + %{transaction: transaction_conditions}, + prev_tx, + incoming_tx + ) do + case trigger_type do + :transaction -> + constants_prev = %{ + "transaction" => Constants.from_transaction(incoming_tx), + "contract" => Constants.from_transaction(prev_tx) + } + + case Interpreter.valid_conditions?(version, transaction_conditions, constants_prev) do + true -> + :ok + + false -> + {:error, :invalid_transaction_conditions} + end + + _ -> + :ok + end + end + + defp validate_inherit_conditions( + version, + trigger_code, + %{inherit: inherit_conditions}, + prev_tx, + incoming_tx + ) do + prev_constants = %{ + "transaction" => Constants.from_transaction(incoming_tx), + "contract" => Constants.from_transaction(prev_tx) + } + + case Interpreter.execute_trigger(version, trigger_code, prev_constants) do + nil -> + :ok + + next_transaction = %Transaction{} -> + %{next_transaction: next_transaction} = + %{next_transaction: next_transaction, previous_transaction: prev_tx} + |> Worker.chain_type() + |> Worker.chain_code() + |> Worker.chain_ownerships() + + constants_both = %{ + "previous" => Constants.from_transaction(prev_tx), + "next" => Constants.from_transaction(next_transaction) + } + + case Interpreter.valid_conditions?(version, inherit_conditions, constants_both) do + true -> + :ok + + false -> + {:error, :invalid_inherit_conditions} + end + end + end + defp validate_conditions(version, inherit_conditions, constants) do if Interpreter.valid_conditions?(version, inherit_conditions, constants) do :ok diff --git a/lib/archethic/contracts/interpreter.ex b/lib/archethic/contracts/interpreter.ex index fb5503279a..347469e42f 100644 --- a/lib/archethic/contracts/interpreter.ex +++ b/lib/archethic/contracts/interpreter.ex @@ -31,8 +31,8 @@ defmodule Archethic.Contracts.Interpreter do Legacy.parse(block) end - {:error, reason} -> - {:error, reason} + {:error, {[line: line, column: column], _msg_info, _token}} -> + {:error, "Parse error at line #{line} column #{column}"} end end diff --git a/lib/archethic/contracts/interpreter/legacy/condition_interpreter.ex b/lib/archethic/contracts/interpreter/legacy/condition_interpreter.ex index 1920390378..ee4cd2bb5e 100644 --- a/lib/archethic/contracts/interpreter/legacy/condition_interpreter.ex +++ b/lib/archethic/contracts/interpreter/legacy/condition_interpreter.ex @@ -556,7 +556,8 @@ defmodule Archethic.Contracts.Interpreter.Legacy.ConditionInterpreter do {"timestamp", true} end - defp validate_condition({"type", nil}, %{"next" => %{"type" => "transfer"}}) do + defp validate_condition({"type", nil}, %{"next" => %{"type" => type}}) + when type in ["transfer", "contract"] do # Skip the verification when it's the default type {"type", true} end diff --git a/lib/archethic/contracts/worker.ex b/lib/archethic/contracts/worker.ex index 8c4d075d12..79d86414af 100644 --- a/lib/archethic/contracts/worker.ex +++ b/lib/archethic/contracts/worker.ex @@ -380,36 +380,36 @@ defmodule Archethic.Contracts.Worker do end end - defp chain_type( - acc = %{ - next_transaction: %Transaction{type: nil}, - previous_transaction: _ - } - ) do + def chain_type( + acc = %{ + next_transaction: %Transaction{type: nil}, + previous_transaction: _ + } + ) do put_in(acc, [:next_transaction, Access.key(:type)], :contract) end - defp chain_type(acc), do: acc + def chain_type(acc), do: acc - defp chain_code( - acc = %{ - next_transaction: %Transaction{data: %TransactionData{code: ""}}, - previous_transaction: %Transaction{data: %TransactionData{code: previous_code}} - } - ) do + def chain_code( + acc = %{ + next_transaction: %Transaction{data: %TransactionData{code: ""}}, + previous_transaction: %Transaction{data: %TransactionData{code: previous_code}} + } + ) do put_in(acc, [:next_transaction, Access.key(:data, %{}), Access.key(:code)], previous_code) end - defp chain_code(acc), do: acc + def chain_code(acc), do: acc - defp chain_ownerships( - acc = %{ - next_transaction: %Transaction{data: %TransactionData{ownerships: []}}, - previous_transaction: %Transaction{ - data: %TransactionData{ownerships: previous_ownerships} - } - } - ) do + def chain_ownerships( + acc = %{ + next_transaction: %Transaction{data: %TransactionData{ownerships: []}}, + previous_transaction: %Transaction{ + data: %TransactionData{ownerships: previous_ownerships} + } + } + ) do put_in( acc, [:next_transaction, Access.key(:data, %{}), Access.key(:ownerships)], @@ -417,7 +417,7 @@ defmodule Archethic.Contracts.Worker do ) end - defp chain_ownerships(acc), do: acc + def chain_ownerships(acc), do: acc defp enough_funds?(contract_address) do case Account.get_balance(contract_address) do diff --git a/lib/archethic_web/controllers/api/transaction_controller.ex b/lib/archethic_web/controllers/api/transaction_controller.ex index 40a610335f..484abd1b93 100644 --- a/lib/archethic_web/controllers/api/transaction_controller.ex +++ b/lib/archethic_web/controllers/api/transaction_controller.ex @@ -143,4 +143,89 @@ defmodule ArchethicWeb.API.TransactionController do |> render("400.json", changeset: changeset) end end + + @doc """ + This controller, Fetch the recipients contract and simulate the transaction, managing possible + exits from contract execution + """ + def simulate_contract_execution( + conn, + params = %{} + ) do + case TransactionPayload.changeset(params) do + changeset = %{valid?: true} -> + tx = + %Transaction{data: %TransactionData{recipients: recipients}} = + changeset + |> TransactionPayload.to_map() + |> Transaction.cast() + + {:ok, sup} = Task.Supervisor.start_link(strategy: :one_for_one) + + results = + Task.Supervisor.async_stream_nolink( + sup, + recipients, + &fetch_recipient_tx_and_simulate(&1, tx), + on_timeout: :kill_task, + timeout: 5000 + ) + |> Stream.zip(recipients) + |> Stream.map(fn + {{:ok, :ok}, recipient} -> + %{ + "valid" => true, + "recipient_address" => Base.encode16(recipient) + } + + {{:ok, {:error, reason}}, recipient} -> + %{ + "valid" => false, + "reason" => reason, + "recipient_address" => Base.encode16(recipient) + } + + {{:exit, :timeout}, recipient} -> + %{ + "valid" => false, + "reason" => "A contract timed out", + "recipient_address" => Base.encode16(recipient) + } + + {{:exit, _reason}, recipient} -> + # TODO: find a way to prettify the error and remove the stacktrace + %{ + "valid" => false, + "reason" => "A contract exited while simulating", + "recipient_address" => Base.encode16(recipient) + } + end) + |> Enum.to_list() + + conn + |> put_status(:ok) + |> json(results) + + changeset -> + error_details = + Ecto.Changeset.traverse_errors(changeset, &ArchethicWeb.ErrorHelpers.translate_error/1) + + json_body = + Map.merge(error_details, %{"valid" => false, "reason" => "Query validation failled"}) + + conn + |> put_status(:ok) + |> json([json_body]) + end + end + + defp fetch_recipient_tx_and_simulate(recipient_address, tx) do + case Archethic.search_transaction(recipient_address) do + {:ok, prev_tx} -> + Archethic.simulate_contract_execution(prev_tx, tx) + + {:error, reason} -> + {:error, reason} + end + end end diff --git a/lib/archethic_web/explorer_router.ex b/lib/archethic_web/explorer_router.ex index 7c71e0f07f..bf710ca2cf 100644 --- a/lib/archethic_web/explorer_router.ex +++ b/lib/archethic_web/explorer_router.ex @@ -90,6 +90,12 @@ defmodule ArchethicWeb.ExplorerRouter do post("/transaction", ArchethicWeb.API.TransactionController, :new) post("/transaction_fee", ArchethicWeb.API.TransactionController, :transaction_fee) + post( + "/transaction/contract/simulator", + ArchethicWeb.API.TransactionController, + :simulate_contract_execution + ) + forward( "/graphiql", Absinthe.Plug.GraphiQL, diff --git a/test/archethic/contracts_test.exs b/test/archethic/contracts_test.exs index 3a18e2f599..474ea62ceb 100644 --- a/test/archethic/contracts_test.exs +++ b/test/archethic/contracts_test.exs @@ -318,4 +318,74 @@ defmodule Archethic.ContractsTest do assert true == Contracts.accept_new_contract?(previous_tx, next_tx, time) end end + + describe "simulate_contract_execution?/3" do + test "should return true when simulating execution of a valid contract" do + code = """ + condition inherit: [ + content: "hello" + ] + + actions triggered_by: transaction do + set_content "hello" + end + """ + + previous_tx = %Transaction{ + data: %TransactionData{ + code: code + } + } + + {:ok, time} = DateTime.new(~D[2016-05-24], ~T[13:26:00.000999], "Etc/UTC") + + incoming_tx = %Transaction{ + type: :transfer, + data: %TransactionData{ + content: "hello", + code: code + } + } + + assert match?( + :ok, + Contracts.simulate_contract_execution(previous_tx, incoming_tx, time) + ) + end + + test "should return false when simulating execution of a contract where + conditions aren't filled" do + code = """ + condition inherit: [ + content: "hello", + type: data + ] + + actions triggered_by: transaction do + set_content "hello" + end + """ + + previous_tx = %Transaction{ + data: %TransactionData{ + code: code + } + } + + {:ok, time} = DateTime.new(~D[2016-05-24], ~T[13:26:00.000999], "Etc/UTC") + + incoming_tx = %Transaction{ + type: :transfer, + data: %TransactionData{ + content: "hola", + code: code + } + } + + assert match?( + {:error, :invalid_inherit_conditions}, + Contracts.simulate_contract_execution(previous_tx, incoming_tx, time) + ) + end + end end diff --git a/test/archethic/transaction_chain/transaction_test.exs b/test/archethic/transaction_chain/transaction_test.exs index 78d80c5ceb..1b53ea6d69 100644 --- a/test/archethic/transaction_chain/transaction_test.exs +++ b/test/archethic/transaction_chain/transaction_test.exs @@ -5,7 +5,6 @@ defmodule Archethic.TransactionChain.TransactionTest do import ArchethicCase, only: [current_transaction_version: 0, current_protocol_version: 0] alias Archethic.Crypto - alias Archethic.TransactionChain.Transaction alias Archethic.TransactionChain.Transaction.CrossValidationStamp # alias Archethic.TransactionChain.Transaction.ValidationStamp diff --git a/test/archethic_web/controllers/api/transaction_controller_test.exs b/test/archethic_web/controllers/api/transaction_controller_test.exs index 8206b47fce..6365fff2a0 100644 --- a/test/archethic_web/controllers/api/transaction_controller_test.exs +++ b/test/archethic_web/controllers/api/transaction_controller_test.exs @@ -8,6 +8,11 @@ defmodule ArchethicWeb.API.TransactionControllerTest do alias Archethic.P2P.Node alias Archethic.Crypto + alias Archethic.TransactionChain.Transaction + alias Archethic.P2P.Message.GetTransaction + alias Archethic.TransactionChain.TransactionData + import Mox + setup do P2P.add_and_connect_node(%Node{ ip: {127, 0, 0, 1}, @@ -99,4 +104,404 @@ defmodule ArchethicWeb.API.TransactionControllerTest do } = json_response(conn, 400) end end + + describe "simulate_contract_execution/2" do + test "should return sucessfull answer when asked to validate a valid contract", %{conn: conn} do + code = """ + condition inherit: [ + type: transfer, + content: true, + uco_transfers: true + ] + + condition transaction: [ + uco_transfers: size() > 0 + ] + + actions triggered_by: transaction do + set_type transfer + add_uco_transfer to: "000030831178cd6a49fe446778455a7a980729a293bfa16b0a1d2743935db210da76", amount: 1337 + end + """ + + previous_tx1 = %Transaction{ + address: "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + type: :transfer, + data: %TransactionData{ + code: code, + content: "hello" + }, + version: 1 + } + + previous_tx2 = %Transaction{ + address: "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d66", + type: :transfer, + data: %TransactionData{ + code: code, + content: "hello" + }, + version: 1 + } + + MockClient + |> stub(:send_message, fn + ## These decoded addresses are matching the ones in the code above + _, + %GetTransaction{ + address: + <<0, 0, 158, 5, 158, 129, 113, 100, 59, 149, 146, 132, 254, 84, 41, 9, 243, 179, 33, + 152, 184, 252, 37, 179, 229, 4, 71, 88, 155, 132, 52, 28, 29, 103>> + }, + _ -> + {:ok, previous_tx1} + + _, + %GetTransaction{ + address: + <<0, 0, 158, 5, 158, 129, 113, 100, 59, 149, 146, 132, 254, 84, 41, 9, 243, 179, 33, + 152, 184, 252, 37, 179, 229, 4, 71, 88, 155, 132, 52, 28, 29, 102>> + }, + _ -> + {:ok, previous_tx2} + end) + + new_tx = %{ + "address" => "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + "data" => %{ + "content" => "0000", + "recipients" => [ + "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d66", + "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67" + ] + }, + "originSignature" => + "3045022024f8d254671af93f8b9c11b5a2781a4a7535d2e89bad69d6b1f142f8f4bcf489022100c364e10f5f846b2534a7ace4aeaa1b6c8cb674f842b9f8bc78225dfa61cabec6", + "previousPublicKey" => + "000071e1b5d4b89eddf2322c69bbf1c5591f7361b24cb3c4c464f6b5eb688fe50f7a", + "previousSignature" => + "9b209dd92c6caffbb5c39d12263f05baebc9fe3c36cb0f4dde04c96f1237b75a3a2973405c6d9d5e65d8a970a37bafea57b919febad46b0cceb04a7ffa4b6b00", + "type" => "transfer", + "version" => 1 + } + + conn = post(conn, "/api/transaction/contract/simulator", new_tx) + + assert( + match?( + [ + %{ + "valid" => true, + "address" => "00009E059E8171643B959284FE542909F3B32198B8FC25B3E50447589B84341C1D66" + }, + %{ + "valid" => true, + "address" => "00009E059E8171643B959284FE542909F3B32198B8FC25B3E50447589B84341C1D67" + } + ], + json_response(conn, 200) + ) + ) + end + + test "should indicate faillure when asked to validate an invalid contract", %{conn: conn} do + code = """ + condition inherit: [ + type: transfer, + content: false, + uco_transfers: true + ] + + condition transaction: [ + uco_transfers: size() > 0 + ] + + actions triggered_by: transaction do + set_type transfer + add_uco_transfer to: "000030831178cd6a49fe446778455a7a980729a293bfa16b0a1d2743935db210da76", amount: 1337 + end + """ + + previous_tx = %Transaction{ + address: "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + type: :transfer, + data: %TransactionData{ + code: code, + content: "hello" + }, + version: 1 + } + + MockClient + |> stub(:send_message, fn _, %GetTransaction{address: _}, _ -> + {:ok, previous_tx} + end) + + new_tx = %{ + "address" => "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + "data" => %{ + "code" => code, + "content" => "0000", + "recipients" => ["00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67"] + }, + "originSignature" => + "3045022024f8d254671af93f8b9c11b5a2781a4a7535d2e89bad69d6b1f142f8f4bcf489022100c364e10f5f846b2534a7ace4aeaa1b6c8cb674f842b9f8bc78225dfa61cabec6", + "previousPublicKey" => + "000071e1b5d4b89eddf2322c69bbf1c5591f7361b24cb3c4c464f6b5eb688fe50f7a", + "previousSignature" => + "9b209dd92c6caffbb5c39d12263f05baebc9fe3c36cb0f4dde04c96f1237b75a3a2973405c6d9d5e65d8a970a37bafea57b919febad46b0cceb04a7ffa4b6b00", + "type" => "transfer", + "version" => 1 + } + + conn = post(conn, "/api/transaction/contract/simulator", new_tx) + + assert(match?([%{"valid" => false}], json_response(conn, 200))) + end + + test "should indicate when body fails changeset validation", %{conn: conn} do + code = """ + condition inherit: [ + type: transfer, + content: "hello", + uco_transfers: true + ] + + condition transaction: [ + uco_transfers: size() > 0 + ] + + actions triggered_by: transaction do + set_type transfer + add_uco_transfer to: "000030831178cd6a49fe446778455a7a980729a293bfa16b0a1d2743935db210da76", amount: 1337 + end + """ + + previous_tx = %Transaction{ + address: "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + type: :transfer, + data: %TransactionData{ + code: code, + content: "hello" + }, + version: 1 + } + + MockClient + |> stub(:send_message, fn _, %GetTransaction{address: _}, _ -> + {:ok, previous_tx} + end) + + new_tx = %{ + "address" => "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + "data" => %{ + "code" => code, + ## Next line is the invalid part + "content" => "hola", + "recipients" => ["00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67"] + }, + "originSignature" => + "3045022024f8d254671af93f8b9c11b5a2781a4a7535d2e89bad69d6b1f142f8f4bcf489022100c364e10f5f846b2534a7ace4aeaa1b6c8cb674f842b9f8bc78225dfa61cabec6", + "previousPublicKey" => + "000071e1b5d4b89eddf2322c69bbf1c5591f7361b24cb3c4c464f6b5eb688fe50f7a", + "previousSignature" => + "9b209dd92c6caffbb5c39d12263f05baebc9fe3c36cb0f4dde04c96f1237b75a3a2973405c6d9d5e65d8a970a37bafea57b919febad46b0cceb04a7ffa4b6b00", + "type" => "transfer", + "version" => 1 + } + + conn = post(conn, "/api/transaction/contract/simulator", new_tx) + + assert(match?([%{"valid" => false}], json_response(conn, 200))) + end + + test "should indicate faillure when failling parsing of contracts", %{conn: conn} do + ## SC is missing the "inherit" keyword + code = """ + condition : [ + type: transfer, + content: true, + uco_transfers: true + ] + + condition transaction: [ + uco_transfers: size() > 0 + ] + + actions triggered_by: transaction do + set_type transfer + add_uco_transfer to: "000030831178cd6a49fe446778455a7a980729a293bfa16b0a1d2743935db210da76", amount: 1337 + end + """ + + previous_tx = %Transaction{ + address: "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + type: :transfer, + data: %TransactionData{ + code: code + }, + version: 1 + } + + MockClient + |> stub(:send_message, fn _, %GetTransaction{address: _}, _ -> + {:ok, previous_tx} + end) + + new_tx = %{ + "address" => "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + "data" => %{ + "code" => code, + "recipients" => ["00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67"] + }, + "originSignature" => + "3045022024f8d254671af93f8b9c11b5a2781a4a7535d2e89bad69d6b1f142f8f4bcf489022100c364e10f5f846b2534a7ace4aeaa1b6c8cb674f842b9f8bc78225dfa61cabec6", + "previousPublicKey" => + "000071e1b5d4b89eddf2322c69bbf1c5591f7361b24cb3c4c464f6b5eb688fe50f7a", + "previousSignature" => + "9b209dd92c6caffbb5c39d12263f05baebc9fe3c36cb0f4dde04c96f1237b75a3a2973405c6d9d5e65d8a970a37bafea57b919febad46b0cceb04a7ffa4b6b00", + "type" => "transfer", + "version" => 1 + } + + conn = post(conn, "/api/transaction/contract/simulator", new_tx) + + assert(match?([%{"valid" => false}], json_response(conn, 200))) + end + + test "Assert empty contract are not simulated and return negative answer", %{conn: conn} do + code = "" + + previous_tx = %Transaction{ + address: "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + type: :transfer, + data: %TransactionData{ + code: code + }, + version: 1 + } + + MockClient + |> stub(:send_message, fn _, %GetTransaction{address: _}, _ -> + {:ok, previous_tx} + end) + + new_tx = %{ + "address" => "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + "data" => %{ + "code" => code, + "recipients" => [ + "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67" + ] + }, + "originSignature" => + "3045022024f8d254671af93f8b9c11b5a2781a4a7535d2e89bad69d6b1f142f8f4bcf489022100c364e10f5f846b2534a7ace4aeaa1b6c8cb674f842b9f8bc78225dfa61cabec6", + "previousPublicKey" => + "000071e1b5d4b89eddf2322c69bbf1c5591f7361b24cb3c4c464f6b5eb688fe50f7a", + "previousSignature" => + "9b209dd92c6caffbb5c39d12263f05baebc9fe3c36cb0f4dde04c96f1237b75a3a2973405c6d9d5e65d8a970a37bafea57b919febad46b0cceb04a7ffa4b6b00", + "type" => "transfer", + "version" => 1 + } + + conn = post(conn, "/api/transaction/contract/simulator", new_tx) + assert(match?([%{"valid" => false}], json_response(conn, 200))) + end + + test "should return error answer when asked to validate a crashing contract", %{ + conn: conn + } do + code = """ + condition inherit: [ + content: true + ] + + condition transaction: [ + uco_transfers: size() > 0 + ] + + actions triggered_by: transaction do + set_content 10 / 0 + end + """ + + previous_tx1 = %Transaction{ + address: "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + type: :transfer, + data: %TransactionData{ + code: code, + content: "hello" + }, + version: 1 + } + + previous_tx2 = %Transaction{ + address: "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d66", + type: :transfer, + data: %TransactionData{ + code: code, + content: "hello" + }, + version: 1 + } + + MockClient + |> stub(:send_message, fn + ## These decoded addresses are matching the ones in the code above + _, + %GetTransaction{ + address: + <<0, 0, 158, 5, 158, 129, 113, 100, 59, 149, 146, 132, 254, 84, 41, 9, 243, 179, 33, + 152, 184, 252, 37, 179, 229, 4, 71, 88, 155, 132, 52, 28, 29, 103>> + }, + _ -> + {:ok, previous_tx1} + + _, + %GetTransaction{ + address: + <<0, 0, 158, 5, 158, 129, 113, 100, 59, 149, 146, 132, 254, 84, 41, 9, 243, 179, 33, + 152, 184, 252, 37, 179, 229, 4, 71, 88, 155, 132, 52, 28, 29, 102>> + }, + _ -> + {:ok, previous_tx2} + end) + + new_tx = %{ + "address" => "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67", + "data" => %{ + "code" => code, + "content" => "0000", + "recipients" => [ + "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d66", + "00009e059e8171643b959284fe542909f3b32198b8fc25b3e50447589b84341c1d67" + ] + }, + "originSignature" => + "3045022024f8d254671af93f8b9c11b5a2781a4a7535d2e89bad69d6b1f142f8f4bcf489022100c364e10f5f846b2534a7ace4aeaa1b6c8cb674f842b9f8bc78225dfa61cabec6", + "previousPublicKey" => + "000071e1b5d4b89eddf2322c69bbf1c5591f7361b24cb3c4c464f6b5eb688fe50f7a", + "previousSignature" => + "9b209dd92c6caffbb5c39d12263f05baebc9fe3c36cb0f4dde04c96f1237b75a3a2973405c6d9d5e65d8a970a37bafea57b919febad46b0cceb04a7ffa4b6b00", + "type" => "transfer", + "version" => 1 + } + + conn = post(conn, "/api/transaction/contract/simulator", new_tx) + + assert( + match?( + [ + %{ + "valid" => false + }, + %{ + "valid" => false + } + ], + json_response(conn, 200) + ) + ) + end + end end