diff --git a/assets/js/editor/language.js b/assets/js/editor/language.js index 0cdbb14..e467be3 100644 --- a/assets/js/editor/language.js +++ b/assets/js/editor/language.js @@ -514,6 +514,36 @@ export async function setLanguage(monaco) { insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, documentation: 'Returns the map values' }, + + // State + { + label: 'State.get/1', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: 'State.get("${1:key}")', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + documentation: 'Return the value stored at given key. Return nil if no value' + }, + { + label: 'State.get/2', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: 'State.get("${1:key}", ${2:default})', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + documentation: 'Return the value stored at given key. Return default if no value.' + }, + { + label: 'State.set/2', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: 'State.set("${1:key}", ${2:value})', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + documentation: 'Store the value in the state under given key' + }, + { + label: 'State.delete/1', + kind: monaco.languages.CompletionItemKind.Snippet, + insertText: 'State.delete("${1:key}")', + insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, + documentation: 'Remove the value stored under key from the state' + }, // Time { label: 'Time.now/0', diff --git a/lib/archethic_playground.ex b/lib/archethic_playground.ex index 3ba834c..fcab2a5 100644 --- a/lib/archethic_playground.ex +++ b/lib/archethic_playground.ex @@ -2,14 +2,19 @@ defmodule ArchethicPlayground do @moduledoc """ Main module to run the functionality needed """ - alias Archethic.Crypto + alias Archethic.Contracts alias Archethic.Contracts.Contract + alias Archethic.Contracts.Contract.ActionWithoutTransaction + alias Archethic.Contracts.Contract.ActionWithTransaction + alias Archethic.Contracts.Contract.Failure + alias Archethic.Contracts.Contract.State + alias Archethic.Crypto alias Archethic.TransactionChain.Transaction - + alias Archethic.TransactionChain.Transaction.ValidationStamp.LedgerOperations.UnspentOutput + alias ArchethicPlayground.RecipientForm alias ArchethicPlayground.Transaction, as: PlaygroundTransaction alias ArchethicPlayground.TriggerForm - alias ArchethicPlayground.RecipientForm alias ArchethicPlayground.Utils require Logger @@ -32,7 +37,8 @@ defmodule ArchethicPlayground do @spec execute_function( contract_tx :: PlaygroundTransaction.t(), function_name :: String.t(), - args_values :: list(any()) + args_values :: list(any()), + maybe_state_utxo :: nil | UnspentOutput.t() ) :: {:ok, any()} | {:error, :function_failure} @@ -42,10 +48,11 @@ defmodule ArchethicPlayground do def execute_function( contract_tx, function_name, - args_values + args_values, + maybe_state_utxo ) do {:ok, contract} = parse(contract_tx) - Contracts.execute_function(contract, function_name, args_values) + Contracts.execute_function(contract, function_name, args_values, maybe_state_utxo) end @spec execute(PlaygroundTransaction.t(), TriggerForm.t(), list(Mock.t())) :: @@ -98,27 +105,36 @@ defmodule ArchethicPlayground do ArchethicPlayground.MockFunctions.prepare_mocks(mocks) with {:ok, contract} <- parse(transaction_contract), + maybe_state_utxo <- State.get_utxo_from_transaction(contract.transaction), :ok <- check_valid_precondition(trigger, contract, maybe_tx, maybe_recipient, datetime), - {:ok, tx_or_nil} <- + %ActionWithTransaction{next_tx: next_tx, next_state_utxo: next_state_utxo} <- Contracts.execute_trigger( trigger, contract, maybe_tx, maybe_recipient, + maybe_state_utxo, time_now: datetime ), - :ok <- check_valid_postcondition(contract, tx_or_nil, datetime), - tx_or_nil <- + :ok <- check_valid_postcondition(contract, next_tx, datetime), + next_tx <- PlaygroundTransaction.from_archethic( - tx_or_nil, + next_tx, + next_state_utxo, transaction_contract.seed, 1 + transaction_contract.index ) do - {:ok, tx_or_nil} + {:ok, next_tx} + else + %ActionWithoutTransaction{} -> + {:ok, nil} + + %Failure{user_friendly_error: reason} -> + {:error, reason} + + {:error, reason} -> + {:error, reason} end - catch - {:error, reason} -> - {:error, reason} end defp get_time_now(mocks) do diff --git a/lib/archethic_playground/mock_functions.ex b/lib/archethic_playground/mock_functions.ex index e7abc59..304845c 100644 --- a/lib/archethic_playground/mock_functions.ex +++ b/lib/archethic_playground/mock_functions.ex @@ -42,6 +42,16 @@ defmodule ArchethicPlayground.MockFunctions do get_mocked_value("Chain.get_genesis_address/1", [address]) end + @impl Library.Common.Chain + def get_last_address(address) do + get_mocked_value("Chain.get_last_address/1", [address]) + end + + @impl Library.Common.Chain + def get_last_transaction(address) do + get_mocked_value("Chain.get_last_transaction/1", [address]) + end + @impl Library.Common.Chain def get_first_transaction_address(address) do get_mocked_value("Chain.get_first_transaction_address/1", [address]) diff --git a/lib/archethic_playground/transaction.ex b/lib/archethic_playground/transaction.ex index 4061abd..c38ce4b 100644 --- a/lib/archethic_playground/transaction.ex +++ b/lib/archethic_playground/transaction.ex @@ -8,6 +8,7 @@ defmodule ArchethicPlayground.Transaction do alias __MODULE__.Ownership alias Archethic.Crypto + alias Archethic.Contracts.Contract.State alias Archethic.TransactionChain.Transaction, as: ArchethicTransaction alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.Recipient, as: ArchethicRecipient @@ -24,7 +25,8 @@ defmodule ArchethicPlayground.Transaction do token_transfers: list(TokenTransfer.t()), ownerships: list(Ownership.t()), seed: String.t(), - index: non_neg_integer() + index: non_neg_integer(), + state: String.t() } @derive {Jason.Encoder, except: [:__meta__, :id]} @@ -35,6 +37,7 @@ defmodule ArchethicPlayground.Transaction do field(:validation_timestamp, :string) field(:seed, :string) field(:index, :integer) + field(:state, :string) embeds_many(:recipients, Recipient, on_replace: :delete) embeds_many(:uco_transfers, UcoTransfer, on_replace: :delete) embeds_many(:token_transfers, TokenTransfer, on_replace: :delete) @@ -53,7 +56,7 @@ defmodule ArchethicPlayground.Transaction do def changeset(transaction, attrs \\ %{}) do transaction - |> cast(attrs, [:type, :content, :seed, :index, :code, :validation_timestamp]) + |> cast(attrs, [:type, :content, :state, :seed, :index, :code, :validation_timestamp]) |> cast_embed(:uco_transfers) |> cast_embed(:token_transfers) |> cast_embed(:recipients) @@ -163,6 +166,10 @@ defmodule ArchethicPlayground.Transaction do filter_code? = Keyword.get(opts, :filter_code, false) Map.from_struct(t) + |> Map.update(:state, nil, fn + nil -> nil + state -> Jason.decode!(state) + end) |> Enum.reject(fn {key, value} -> key in [:__meta__, :seed, :index] || value in [nil, [], ""] || (filter_code? && key == :code) @@ -248,21 +255,30 @@ defmodule ArchethicPlayground.Transaction do ArchethicTransaction.ValidationStamp.generate_dummy() | timestamp: Utils.Date.browser_timestamp_to_datetime(t.validation_timestamp), ledger_operations: %ArchethicTransaction.ValidationStamp.LedgerOperations{ - transaction_movements: ArchethicTransaction.get_movements(signed_tx) + transaction_movements: ArchethicTransaction.get_movements(signed_tx), + unspent_outputs: + case extract_state_utxo(t) do + {:ok, nil} -> [] + {:ok, state_utxo} -> [state_utxo] + {:error, :invalid_state} -> [] + end } } } end - def from_archethic(nil, _, _), do: nil - - def from_archethic(t = %ArchethicTransaction{}, seed, index) do + def from_archethic(t = %ArchethicTransaction{}, maybe_state_utxo, seed, index) do %__MODULE__{ seed: seed, index: index, type: Atom.to_string(t.type), code: t.data.code, content: t.data.content, + state: + case maybe_state_utxo do + nil -> nil + state_utxo -> State.from_utxo(state_utxo) |> Jason.encode!() + end, validation_timestamp: if t.validation_stamp != nil do Utils.Date.datetime_to_browser_timestamp(t.validation_stamp.timestamp) @@ -335,4 +351,20 @@ defmodule ArchethicPlayground.Transaction do defp bin_to_hex(nil), do: nil defp bin_to_hex(bin), do: Base.encode16(bin) + + def extract_state_utxo(tx) do + case tx.state do + nil -> + {:ok, nil} + + state_str -> + case Jason.decode(state_str) do + {:ok, map} when is_map(map) -> + State.to_utxo(map) + + _ -> + {:error, :invalid_state} + end + end + end end diff --git a/lib/archethic_playground_web/live/components/contract_component.html.heex b/lib/archethic_playground_web/live/components/contract_component.html.heex index 4725f6f..36bc95c 100644 --- a/lib/archethic_playground_web/live/components/contract_component.html.heex +++ b/lib/archethic_playground_web/live/components/contract_component.html.heex @@ -12,6 +12,7 @@ module={TransactionFormComponent} display_mocks={true} display_code={false} + display_state={true} global_variable="contract" parent={@myself} transaction={@transaction} diff --git a/lib/archethic_playground_web/live/components/mock_form_component/chain_get_transaction1.html.heex b/lib/archethic_playground_web/live/components/mock_form_component/chain_get_transaction1.html.heex index db8dbe0..7c0721e 100644 --- a/lib/archethic_playground_web/live/components/mock_form_component/chain_get_transaction1.html.heex +++ b/lib/archethic_playground_web/live/components/mock_form_component/chain_get_transaction1.html.heex @@ -10,6 +10,7 @@ module={TransactionFormComponent} display_mocks={true} display_code={true} + display_state={false} global_variable="transaction" parent={@myself} transaction={@transaction} diff --git a/lib/archethic_playground_web/live/components/transaction_form_component.html.heex b/lib/archethic_playground_web/live/components/transaction_form_component.html.heex index da3fb04..1ebd9a3 100644 --- a/lib/archethic_playground_web/live/components/transaction_form_component.html.heex +++ b/lib/archethic_playground_web/live/components/transaction_form_component.html.heex @@ -4,6 +4,17 @@ <.field field={@form[:index]} type="hidden" /> <.field field={@form[:type]} type="select" options={list_transaction_types()} label="Type*" /> + <%= if @display_state do %> + <.field + field={@form[:state]} + type="textarea" + label="State" + help_text="Remainder: a contract is always deployed without state" + /> + <% else %> + <.field field={@form[:state]} type="hidden" label="State" /> + <% end %> + <.field field={@form[:content]} type="textarea" label="Content" /> <%= if @display_code do %> @@ -24,7 +35,12 @@
@@ -184,17 +200,16 @@ /> <%= if length(input_value(f, :ownerships)) > 0 do %> -
- You must encode the secret with an AES key.
- Then you must encrypt the AES key for each public key. - - - <.icon - name={:question_mark_circle} - class="inline text-gray-500 cursor-pointer w-6 h-6" - /> - -
+
+ You must encode the secret with an AES key.
+ Then you must encrypt the AES key for each public key. + + <.icon + name={:question_mark_circle} + class="inline text-gray-500 cursor-pointer w-6 h-6" + /> + +
<% end %> <%= for ownership_form <- inputs_for f, :ownerships do %> diff --git a/lib/archethic_playground_web/live/components/trigger_component.html.heex b/lib/archethic_playground_web/live/components/trigger_component.html.heex index 84b8c29..2b11533 100644 --- a/lib/archethic_playground_web/live/components/trigger_component.html.heex +++ b/lib/archethic_playground_web/live/components/trigger_component.html.heex @@ -75,6 +75,7 @@ module={TransactionFormComponent} display_mocks={true} display_code={true} + display_state={false} parent={@myself} global_variable="transaction" transaction={@form[:transaction].value} diff --git a/lib/archethic_playground_web/live/editor_live.ex b/lib/archethic_playground_web/live/editor_live.ex index 8674eee..5171221 100644 --- a/lib/archethic_playground_web/live/editor_live.ex +++ b/lib/archethic_playground_web/live/editor_live.ex @@ -116,7 +116,11 @@ defmodule ArchethicPlaygroundWeb.EditorLive do case ArchethicPlayground.execute_function( socket.assigns.transaction_contract, function_name, - args_values + args_values, + case Transaction.extract_state_utxo(socket.assigns.transaction_contract) do + {:ok, maybe_state_utxo} -> maybe_state_utxo + {:error, :invalid_state} -> nil + end ) do {:ok, result} -> send(self(), {:console, :success, result})