From 0395230d2ad585a1d1cfd91c5a68060876e1b0c9 Mon Sep 17 00:00:00 2001 From: Neylix Date: Thu, 17 Aug 2023 14:11:23 +0200 Subject: [PATCH 1/4] Refactor module function call --- .../interpreter/action_interpreter.ex | 24 +++- .../interpreter/common_interpreter.ex | 106 ++++++---------- .../interpreter/condition_interpreter.ex | 86 +++++-------- .../interpreter/function_interpreter.ex | 36 +++--- .../contracts/interpreter/library.ex | 114 ++++++++++++------ .../interpreter/function_interpreter_test.exs | 2 +- test/archethic/contracts/interpreter_test.exs | 6 +- 7 files changed, 186 insertions(+), 188 deletions(-) diff --git a/lib/archethic/contracts/interpreter/action_interpreter.ex b/lib/archethic/contracts/interpreter/action_interpreter.ex index 8ce16ff04..dea8c412c 100644 --- a/lib/archethic/contracts/interpreter/action_interpreter.ex +++ b/lib/archethic/contracts/interpreter/action_interpreter.ex @@ -9,6 +9,7 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do alias Archethic.Contracts.Interpreter alias Archethic.Contracts.Interpreter.ASTHelper, as: AST alias Archethic.Contracts.Interpreter.CommonInterpreter + alias Archethic.Contracts.Interpreter.Library alias Archethic.Contracts.Interpreter.Scope # # Module `Contract` is handled differently @@ -162,10 +163,26 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do # | .__/|_| \___| \_/\_/ \__,_|_|_|\_\ # |_| # ---------------------------------------------------------------------- + + # module call defp prewalk( - node, + node = + {{:., _meta, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _, + args}, acc ) do + arity = + if Library.function_tagged_with?(module_name, function_name, :write_contract), + do: length(args) + 1, + else: length(args) + + case Library.validate_module_call(module_name, function_name, arity) do + :ok -> {node, acc} + {:error, _reason, message} -> throw({:error, node, message}) + end + end + + defp prewalk(node, acc) do CommonInterpreter.prewalk(node, acc) end @@ -177,9 +194,8 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do # | .__/ \___/|___/\__| \_/\_/ \__,_|_|_|\_\ # |_| # ---------------------------------------------------------------------- - defp postwalk(node, acc) do - CommonInterpreter.postwalk(node, acc) - end + + defp postwalk(node, acc), do: CommonInterpreter.postwalk(node, acc) # keep only the transaction fields we are interested in # these are all the fields that are copied from `prev_tx` to `next_tx` diff --git a/lib/archethic/contracts/interpreter/common_interpreter.ex b/lib/archethic/contracts/interpreter/common_interpreter.ex index cfaae9327..ec6380706 100644 --- a/lib/archethic/contracts/interpreter/common_interpreter.ex +++ b/lib/archethic/contracts/interpreter/common_interpreter.ex @@ -90,14 +90,12 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do def prewalk(node = {{:atom, var_name}, _, nil}, acc) when is_binary(var_name), do: {node, acc} def prewalk(node = {:atom, var_name}, acc) when is_binary(var_name), do: {node, acc} - # module call - def prewalk(node = {{:., _, [{:__aliases__, _, _}, _]}, _, _}, acc), do: {node, acc} - def prewalk(node = {:., _, [{:__aliases__, _, _}, _]}, acc), do: {node, acc} - - # whitelisted modules - def prewalk(node = {:__aliases__, _, [atom: _module_name]}, acc), + def prewalk(node = {{:., _, [{:__aliases__, _, [atom]}, _]}, _, _}, acc) when is_atom(atom), do: {node, acc} + def prewalk(node = {:., _, [{:__aliases__, _, _}, _]}, acc), do: {node, acc} + def prewalk(node = {:__aliases__, _, [atom: _module_name]}, acc), do: {node, acc} + # internal modules (Process/Scope/Kernel) def prewalk(node = {:__aliases__, _, [atom]}, acc) when is_atom(atom), do: {node, acc} @@ -285,14 +283,41 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do {{:__block__, meta, expressions ++ new_expressions}, acc} end - # common modules call + # Module function call def postwalk( node = - {{:., _meta, [{:__aliases__, _, [atom: _module_name]}, {:atom, _function_name}]}, _, - _args}, + {{:., meta, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _, args}, acc ) do - {module_call(node), acc} + # Module and function has already been verified + module = Library.get_module!(module_name) + function = String.to_existing_atom(function_name) + + # check the type of the args + unless module.check_types(function, args) do + throw({:error, node, "invalid function arguments"}) + end + + new_node = + if Library.function_tagged_with?(module_name, function_name, :write_contract) do + quote do + # mark the next_tx as dirty + Scope.update_global([:next_transaction_changed], fn _ -> true end) + + # call the function with the next_transaction as the 1st argument + # and update it in the scope + Scope.update_global( + [:next_transaction], + &apply(unquote(module), unquote(function), [&1 | unquote(args)]) + ) + end + else + meta_with_alias = Keyword.put(meta, :alias, module) + + {{:., meta, [{:__aliases__, meta_with_alias, [module]}, function]}, meta, args} + end + + {new_node, acc} end # variable are read from scope @@ -369,65 +394,4 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do # whitelist rest def postwalk(node, acc), do: {node, acc} - - def module_call( - node = - {{:., meta, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _, args} - ) - when is_binary(module_name) do - {absolute_module_atom, _} = - case Library.get_module(module_name) do - {:ok, module_atom, module_atom_impl} -> {module_atom, module_atom_impl} - {:error, message} -> throw({:error, node, message}) - end - - # check function exists - unless Library.function_exists?(absolute_module_atom, function_name) do - throw({:error, node, "unknown function"}) - end - - # check function is available with given arity - unless Library.function_exists?(absolute_module_atom, function_name, length(args)) or - Library.function_exists?(absolute_module_atom, function_name, length(args) + 1) do - throw({:error, node, "invalid function arity"}) - end - - function_atom = String.to_existing_atom(function_name) - - # check the type of the args - unless absolute_module_atom.check_types(function_atom, args) do - throw({:error, node, "invalid function arguments"}) - end - - new_node = - if module_name == "Contract" do - quote do - # mark the next_tx as dirty - Scope.update_global([:next_transaction_changed], fn _ -> true end) - - # call the function with the next_transaction as the 1st argument - # and update it in the scope - Scope.update_global( - [:next_transaction], - &apply(unquote(absolute_module_atom), unquote(function_atom), [&1 | unquote(args)]) - ) - end - else - meta_with_alias = Keyword.put(meta, :alias, absolute_module_atom) - - {{:., meta, [{:__aliases__, meta_with_alias, [absolute_module_atom]}, function_atom]}, - meta, args} - end - - new_node - end - - # ---------------------------------------------------------------------- - # _ _ - # _ __ _ __(___ ____ _| |_ ___ - # | '_ \| '__| \ \ / / _` | __/ _ \ - # | |_) | | | |\ V | (_| | || __/ - # | .__/|_| |_| \_/ \__,_|\__\___| - # |_| - # ---------------------------------------------------------------------- end diff --git a/lib/archethic/contracts/interpreter/condition_interpreter.ex b/lib/archethic/contracts/interpreter/condition_interpreter.ex index 528763cf9..aa496ced9 100644 --- a/lib/archethic/contracts/interpreter/condition_interpreter.ex +++ b/lib/archethic/contracts/interpreter/condition_interpreter.ex @@ -166,29 +166,31 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do # |_| # ---------------------------------------------------------------------- + # Here we check arity and arity + 1 since we can automatically fill the first parameter + # with the subject of the condition defp prewalk( _subject, node = - {{:., _meta, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _, _}, + {{:., _meta, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _, + args}, acc ) do - {_absolute_module_atom, module_impl} = - case Library.get_module(module_name) do - {:ok, module_atom, module_atom_impl} -> {module_atom, module_atom_impl} - {:error, message} -> throw({:error, node, message}) - end - - function_atom = String.to_existing_atom(function_name) + arity = length(args) - if module_impl.tagged_with?(function_atom, :write_contract), + if Library.function_tagged_with?(module_name, function_name, :write_contract), do: throw({:error, node, "Write contract functions are not allowed in condition block"}) - CommonInterpreter.prewalk(node, acc) + with {:error, :invalid_function_arity, _} <- + Library.validate_module_call(module_name, function_name, arity), + :ok <- Library.validate_module_call(module_name, function_name, arity + 1) do + {node, acc} + else + {:error, _reason, message} -> throw({:error, node, message}) + :ok -> {node, acc} + end end - defp prewalk(_subject, node, acc) do - CommonInterpreter.prewalk(node, acc) - end + defp prewalk(_subject, node, acc), do: CommonInterpreter.prewalk(node, acc) # ---------------------------------------------------------------------- # _ _ _ @@ -244,46 +246,24 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do {{:., meta, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _, args}, acc ) do - # if function exist with arity => node - arity = length(args) - - {absolute_module_atom, _} = - case Library.get_module(module_name) do - {:ok, module_atom, module_atom_impl} -> {module_atom, module_atom_impl} - {:error, message} -> throw({:error, node, message}) - end - - new_node = - cond do - # check function is available with given arity - Library.function_exists?(absolute_module_atom, function_name, arity) -> - {new_node, _} = CommonInterpreter.postwalk(node, acc) - new_node - - # if function exist with arity+1 => prepend the key to args - Library.function_exists?(absolute_module_atom, function_name, arity + 1) -> - ast = - quote do - Scope.read_global(unquote(subject)) - end - - # add it as first function argument - node_with_key_appended = - {{:., meta, [{:__aliases__, meta, [atom: module_name]}, {:atom, function_name}]}, - meta, [ast | args]} - - {new_node, _} = CommonInterpreter.postwalk(node_with_key_appended, acc) - new_node - - # check function exists - Library.function_exists?(absolute_module_atom, function_name) -> - throw({:error, node, "invalid arity for function #{module_name}.#{function_name}"}) - - true -> - throw({:error, node, "unknown function: #{module_name}.#{function_name}"}) - end - - {new_node, acc} + # Module and function has already been verified do we get search for the good arity + case Library.validate_module_call(module_name, function_name, length(args)) do + :ok -> + CommonInterpreter.postwalk(node, acc) + + {:error, :invalid_function_arity, _} -> + ast = + quote do + Scope.read_global(unquote(subject)) + end + + # add it as first function argument + node_with_key_appended = + {{:., meta, [{:__aliases__, meta, [atom: module_name]}, {:atom, function_name}]}, meta, + [ast | args]} + + CommonInterpreter.postwalk(node_with_key_appended, acc) + end end defp postwalk(_subject, node, acc) do diff --git a/lib/archethic/contracts/interpreter/function_interpreter.ex b/lib/archethic/contracts/interpreter/function_interpreter.ex index 9f02c9aaa..8c8f4a4ed 100644 --- a/lib/archethic/contracts/interpreter/function_interpreter.ex +++ b/lib/archethic/contracts/interpreter/function_interpreter.ex @@ -97,33 +97,27 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreter do # | .__/|_| \___| \_/\_/ \__,_|_|_|\_\ # |_| # ---------------------------------------------------------------------- + + # Blacklist write_contract and IO function defp prewalk( node = - {{:., _meta, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _, _}, + {{:., _meta, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _, + args}, acc, - is_internal? + public? ) do - {_absolute_module_atom, module_impl} = - case Library.get_module(module_name) do - {:ok, module_atom, module_atom_impl} -> {module_atom, module_atom_impl} - {:error, message} -> throw({:error, node, message}) - end - - function_atom = String.to_existing_atom(function_name) - - if is_internal? do - if module_impl.tagged_with?(function_atom, :io), - do: throw({:error, node, "IO function calls not allowed in public functions"}) - - if module_impl.tagged_with?(function_atom, :write_contract), - do: throw({:error, node, "Write contract functions are not allowed in custom functions"}) - else - if module_impl.tagged_with?(function_atom, :write_contract) do - throw({:error, node, "Write contract functions are not allowed in custom functions"}) - end + if Library.function_tagged_with?(module_name, function_name, :write_contract), + do: throw({:error, node, "Write contract functions are not allowed in custom functions"}) + + if public? and Library.function_tagged_with?(module_name, function_name, :io), + do: throw({:error, node, "IO function calls not allowed in public functions"}) + + case Library.validate_module_call(module_name, function_name, length(args)) do + :ok -> :ok + {:error, _reason, message} -> throw({:error, node, message}) end - CommonInterpreter.prewalk(node, acc) + {node, acc} end defp prewalk(node = {{:atom, function_name}, _, args}, _acc, true) diff --git a/lib/archethic/contracts/interpreter/library.ex b/lib/archethic/contracts/interpreter/library.ex index 2c061753b..2c72e829b 100644 --- a/lib/archethic/contracts/interpreter/library.ex +++ b/lib/archethic/contracts/interpreter/library.ex @@ -13,61 +13,103 @@ defmodule Archethic.Contracts.Interpreter.Library do end @doc """ - Checks if a function exists in given module + Validate a module function call with arity """ - @spec function_exists?(module(), binary()) :: boolean() - def function_exists?(module, functionName) do - functionName in Enum.map(get_module_functions_as_string(module), &elem(&1, 0)) + @spec validate_module_call( + module_name :: binary(), + function_name :: binary(), + arity :: non_neg_integer() + ) :: + :ok + | {:error, :module_not_exists | :function_not_exists | :invalid_function_arity, + error_message :: binary()} + def validate_module_call(module_name, function_name, arity) do + with {:ok, module} <- get_module(module_name), + module_functions = get_module_functions_as_string(module), + {:ok, matched_functions} <- validate_function_exists(module_functions, function_name), + :ok <- validate_function_arity(matched_functions, arity) do + :ok + else + {:error, reason} -> + error_message = get_error_message(reason, module_name, function_name, arity) + {:error, reason, error_message} + end end @doc """ - Checks if a function with given arity exists in given module + Return a module fomr a given module_name. + Raise a no match error of module doesn't exist """ - @spec function_exists?(module(), binary(), integer) :: boolean() - def function_exists?(module, functionName, arity) do - arity in :proplists.get_all_values( - functionName, - get_module_functions_as_string(module) - ) + @spec get_module!(module_name :: binary()) :: module() + def get_module!(module_name) do + {:ok, module} = get_module(module_name) + module end @doc """ - Gets a module and its imlpementation when there is one + Return true if function is tagged with a specific tag, false otherwise + Raise an error if module or function does not exist """ - @spec get_module(binary()) :: {:ok, module(), module()} | {:error, binary()} - def get_module(module_name) do - try do - case Code.ensure_loaded( - String.to_existing_atom( - "Elixir.Archethic.Contracts.Interpreter.Library.Common.#{module_name}" - ) - ) do - {:module, module_atom} -> - module_atom_impl = - try do - %Knigge.Options{default: module} = Knigge.options!(module_atom) - module - rescue - _ -> - module_atom - end + @spec function_tagged_with?(module_name :: binary(), function_name :: binary(), tag :: atom()) :: + boolean() + def function_tagged_with?(module_name, function_name, tag) do + module_impl = get_module_impl!(module_name) + function = String.to_existing_atom(function_name) + module_impl.tagged_with?(function, tag) + rescue + _ -> false + end + + defp get_module(module_name) do + module = + "Elixir.Archethic.Contracts.Interpreter.Library.Common.#{module_name}" + |> String.to_existing_atom() + |> Code.ensure_loaded!() - {:ok, module_atom, module_atom_impl} + {:ok, module} + rescue + _ -> {:error, :module_not_exists} + end + + defp get_module_impl!(module_name) do + module = get_module!(module_name) - _ -> - {:error, "Module #{module_name} not found"} - end + try do + %Knigge.Options{default: module_impl} = Knigge.options!(module) + module_impl rescue - _ -> - {:error, "Module #{module_name} not found"} + _ -> module end end - # ---------------------------------------- defp get_module_functions_as_string(module) do module.__info__(:functions) |> Enum.map(fn {name, arity} -> {Atom.to_string(name), arity} end) end + + defp validate_function_exists(module_functions, function_name) do + case Enum.filter(module_functions, &(elem(&1, 0) == function_name)) do + [] -> {:error, :function_not_exists} + functions -> {:ok, functions} + end + end + + defp validate_function_arity(functions, arity) do + if Enum.any?(functions, &(elem(&1, 1) == arity)) do + :ok + else + {:error, :invalid_function_arity} + end + end + + defp get_error_message(:module_not_exists, module_name, _, _), + do: "Module #{module_name} does not exists" + + defp get_error_message(:function_not_exists, module_name, function_name, _), + do: "Function #{module_name}.#{function_name} does not exists" + + defp get_error_message(:invalid_function_arity, module_name, function_name, arity), + do: "Function #{module_name}.#{function_name} does not exists with #{arity} arguments" end diff --git a/test/archethic/contracts/interpreter/function_interpreter_test.exs b/test/archethic/contracts/interpreter/function_interpreter_test.exs index 5a9f2fb25..6c5872188 100644 --- a/test/archethic/contracts/interpreter/function_interpreter_test.exs +++ b/test/archethic/contracts/interpreter/function_interpreter_test.exs @@ -114,7 +114,7 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do end """ - assert {:error, _, "Module Hello not found"} = + assert {:error, _, "Module Hello does not exists"} = code |> Interpreter.sanitize_code() |> elem(1) diff --git a/test/archethic/contracts/interpreter_test.exs b/test/archethic/contracts/interpreter_test.exs index 69d7abb42..dfdef2b7a 100644 --- a/test/archethic/contracts/interpreter_test.exs +++ b/test/archethic/contracts/interpreter_test.exs @@ -195,7 +195,8 @@ defmodule Archethic.Contracts.InterpreterTest do end test "should return an human readable error if lib fn is called with bad arity" do - assert {:error, "invalid function arity - List.empty?([1], \"foobar\") - L4"} = + assert {:error, + "Function List.empty? does not exists with 2 arguments - List.empty?([1], \"foobar\") - L4"} = """ @version 1 condition transaction: [] @@ -207,7 +208,8 @@ defmodule Archethic.Contracts.InterpreterTest do end test "should return an human readable error if lib fn does not exists" do - assert {:error, "unknown function - List.non_existing([1, 2, 3]) - L4"} = + assert {:error, + "Function List.non_existing does not exists - List.non_existing([1, 2, 3]) - L4"} = """ @version 1 condition transaction: [] From df040b74d5a08a288d8cfba452f2182671cfce8a Mon Sep 17 00:00:00 2001 From: Neylix Date: Thu, 17 Aug 2023 16:25:06 +0200 Subject: [PATCH 2/4] Refactor function keys --- lib/archethic/contracts/interpreter.ex | 32 +++++++---- .../interpreter/action_interpreter.ex | 4 +- .../interpreter/common_interpreter.ex | 36 ++++++------- .../interpreter/condition_interpreter.ex | 40 +++++++------- .../interpreter/function_interpreter.ex | 24 ++++----- .../contracts/interpreter/function_keys.ex | 24 +++++++++ .../interpreter/action_interpreter_test.exs | 7 +-- .../condition_interpreter_test.exs | 3 +- .../interpreter/function_interpreter_test.exs | 11 ++-- .../interpreter/function_keys_test.exs | 54 +++++++++++++++++++ 10 files changed, 160 insertions(+), 75 deletions(-) create mode 100644 lib/archethic/contracts/interpreter/function_keys.ex create mode 100644 test/archethic/contracts/interpreter/function_keys_test.exs diff --git a/lib/archethic/contracts/interpreter.ex b/lib/archethic/contracts/interpreter.ex index 7ec694145..c37b9ff5c 100644 --- a/lib/archethic/contracts/interpreter.ex +++ b/lib/archethic/contracts/interpreter.ex @@ -7,6 +7,7 @@ defmodule Archethic.Contracts.Interpreter do alias __MODULE__.ActionInterpreter alias __MODULE__.ConditionInterpreter alias __MODULE__.FunctionInterpreter + alias __MODULE__.FunctionKeys alias __MODULE__.ConditionValidator @@ -22,7 +23,6 @@ defmodule Archethic.Contracts.Interpreter do @type version() :: integer() @type execute_opts :: [time_now: DateTime.t()] - @type function_key() :: {String.t(), integer()} @doc """ Dispatch through the correct interpreter. @@ -434,20 +434,30 @@ defmodule Archethic.Contracts.Interpreter do Utils.get_current_time_for_interval(interval) end - defp get_function_keys([{{:atom, "fun"}, _, [{{:atom, function_name}, _, args} | _]} | rest]) do - [{function_name, length(args), :private} | get_function_keys(rest)] + defp get_function_keys(blocks, function_keys \\ FunctionKeys.new()) + + defp get_function_keys( + [{{:atom, "fun"}, _, [{{:atom, function_name}, _, args} | _]} | rest], + function_keys + ) do + new_keys = FunctionKeys.add_private(function_keys, function_name, length(args)) + get_function_keys(rest, new_keys) end - defp get_function_keys([ - {{:atom, "export"}, _, - [{{:atom, "fun"}, _, [{{:atom, function_name}, _, args} | _]} | _]} - | rest - ]) do - [{function_name, length(args), :public} | get_function_keys(rest)] + defp get_function_keys( + [ + {{:atom, "export"}, _, + [{{:atom, "fun"}, _, [{{:atom, function_name}, _, args} | _]} | _]} + | rest + ], + function_keys + ) do + new_keys = FunctionKeys.add_public(function_keys, function_name, length(args)) + get_function_keys(rest, new_keys) end - defp get_function_keys([_ | rest]), do: get_function_keys(rest) - defp get_function_keys([]), do: [] + defp get_function_keys([_ | rest], function_keys), do: get_function_keys(rest, function_keys) + defp get_function_keys([], function_keys), do: function_keys # ----------------------------------------- # contract validation diff --git a/lib/archethic/contracts/interpreter/action_interpreter.ex b/lib/archethic/contracts/interpreter/action_interpreter.ex index dea8c412c..02ecc2dab 100644 --- a/lib/archethic/contracts/interpreter/action_interpreter.ex +++ b/lib/archethic/contracts/interpreter/action_interpreter.ex @@ -6,9 +6,9 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do alias Archethic.Contracts.Contract alias Archethic.Contracts.ContractConstants, as: Constants - alias Archethic.Contracts.Interpreter alias Archethic.Contracts.Interpreter.ASTHelper, as: AST alias Archethic.Contracts.Interpreter.CommonInterpreter + alias Archethic.Contracts.Interpreter.FunctionKeys alias Archethic.Contracts.Interpreter.Library alias Archethic.Contracts.Interpreter.Scope @@ -18,7 +18,7 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do @doc """ Parse the given node and return the trigger and the actions block. """ - @spec parse(any(), list(Interpreter.function_key())) :: + @spec parse(any(), FunctionKeys.t()) :: {:ok, Contract.trigger_type(), Macro.t()} | {:error, any(), String.t()} def parse({{:atom, "actions"}, _, [keyword, [do: block]]}, functions_keys) do trigger_type = extract_trigger(keyword) diff --git a/lib/archethic/contracts/interpreter/common_interpreter.ex b/lib/archethic/contracts/interpreter/common_interpreter.ex index ec6380706..563ffcc7d 100644 --- a/lib/archethic/contracts/interpreter/common_interpreter.ex +++ b/lib/archethic/contracts/interpreter/common_interpreter.ex @@ -8,6 +8,7 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do alias Archethic.Contracts.Interpreter.ASTHelper, as: AST alias Archethic.Contracts.Interpreter.Library + alias Archethic.Contracts.Interpreter.FunctionKeys alias Archethic.Contracts.Interpreter.Scope # ---------------------------------------------------------------------- @@ -252,7 +253,16 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do end # function call, should be placed after "for" prewalk - def prewalk(node = {{:atom, _}, _, args}, acc) when is_list(args), do: {node, acc} + def prewalk(node = {{:atom, function_name}, _, args}, acc = %{functions: functions}) + when is_list(args) do + arity = length(args) + + if FunctionKeys.exist?(functions, function_name, arity) do + {node, acc} + else + throw({:error, node, "The function #{function_name}/#{arity} does not exist"}) + end + end # blacklist rest def prewalk(node, _acc), do: throw({:error, node, "unexpected term"}) @@ -361,25 +371,13 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do {new_node, acc} end - def postwalk(node = {{:atom, function_name}, _, args}, acc = %{functions: functions}) - when is_list(args) do - arity = length(args) - - case Enum.find(functions, fn - {^function_name, ^arity, _} -> true - _ -> false - end) do - nil -> - throw({:error, node, "The function #{function_name}/#{arity} does not exist"}) - - {_, _, _} -> - new_node = - quote do - Scope.execute_function_ast(unquote(function_name), unquote(args)) - end + def postwalk({{:atom, function_name}, _, args}, acc) when is_list(args) do + new_node = + quote do + Scope.execute_function_ast(unquote(function_name), unquote(args)) + end - {new_node, acc} - end + {new_node, acc} end # BigInt mathematics to avoid floating point issues diff --git a/lib/archethic/contracts/interpreter/condition_interpreter.ex b/lib/archethic/contracts/interpreter/condition_interpreter.ex index aa496ced9..61216214f 100644 --- a/lib/archethic/contracts/interpreter/condition_interpreter.ex +++ b/lib/archethic/contracts/interpreter/condition_interpreter.ex @@ -2,12 +2,12 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do @moduledoc false alias Archethic.Contracts.Contract + alias Archethic.Contracts.ContractConditions.Subjects, as: ConditionsSubjects + alias Archethic.Contracts.Interpreter.ASTHelper, as: AST alias Archethic.Contracts.Interpreter.CommonInterpreter + alias Archethic.Contracts.Interpreter.FunctionKeys alias Archethic.Contracts.Interpreter.Library alias Archethic.Contracts.Interpreter.Scope - alias Archethic.Contracts.ContractConditions.Subjects, as: ConditionsSubjects - alias Archethic.Contracts.Interpreter.ASTHelper, as: AST - alias Archethic.Contracts.Interpreter @condition_fields ConditionsSubjects.__struct__() |> Map.keys() @@ -17,7 +17,7 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do @doc """ Parse the given node and return the trigger and the actions block. """ - @spec parse(any(), list(Interpreter.function_key())) :: + @spec parse(any(), FunctionKeys.t()) :: {:ok, Contract.condition_type(), ConditionsSubjects.t()} | {:error, any(), String.t()} def parse( node = {{:atom, "condition"}, _, [[{{:atom, condition_name}, keyword}]]}, @@ -140,7 +140,7 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do AST.wrap_in_block(ast), acc, fn node, acc -> - prewalk(subject, node, acc) + prewalk(node, acc) end, fn node, acc -> postwalk(subject, node, acc) @@ -150,13 +150,6 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do new_ast end - defp function_exists?(functions, function_name, arity) do - Enum.find(functions, fn - {^function_name, ^arity, _} -> true - _ -> false - end) != nil - end - # ---------------------------------------------------------------------- # _ _ # _ __ _ __ _____ ____ _| | | __ @@ -169,7 +162,6 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do # Here we check arity and arity + 1 since we can automatically fill the first parameter # with the subject of the condition defp prewalk( - _subject, node = {{:., _meta, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _, args}, @@ -190,7 +182,18 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do end end - defp prewalk(_subject, node, acc), do: CommonInterpreter.prewalk(node, acc) + defp prewalk(node = {{:atom, function_name}, _, args}, acc = %{functions: functions}) + when is_list(args) and function_name != "for" do + args_arity = length(args) + + cond do + FunctionKeys.exist?(functions, function_name, args_arity) -> {node, acc} + FunctionKeys.exist?(functions, function_name, args_arity + 1) -> {node, acc} + true -> CommonInterpreter.prewalk(node, acc) + end + end + + defp prewalk(node, acc), do: CommonInterpreter.prewalk(node, acc) # ---------------------------------------------------------------------- # _ _ _ @@ -212,12 +215,12 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do new_node = cond do - function_exists?(functions, function_name, arity) -> + FunctionKeys.exist?(functions, function_name, arity) -> {new_node, _} = CommonInterpreter.postwalk(node, acc) new_node # if function exist with arity+1 => prepend the key to args - function_exists?(functions, function_name, arity + 1) -> + FunctionKeys.exist?(functions, function_name, arity + 1) -> ast = quote do Scope.read_global(unquote(subject)) @@ -228,11 +231,6 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do {new_node, _} = CommonInterpreter.postwalk(node_subject_appened, acc) new_node - - true -> - reason = "The function #{function_name}/#{Integer.to_string(arity)} does not exist" - - throw({:error, node, reason}) end {new_node, acc} diff --git a/lib/archethic/contracts/interpreter/function_interpreter.ex b/lib/archethic/contracts/interpreter/function_interpreter.ex index 8c8f4a4ed..0bd36238b 100644 --- a/lib/archethic/contracts/interpreter/function_interpreter.ex +++ b/lib/archethic/contracts/interpreter/function_interpreter.ex @@ -1,16 +1,18 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreter do @moduledoc false - alias Archethic.Contracts.Interpreter.Library - alias Archethic.Contracts.Interpreter + alias Archethic.Contracts.Interpreter.ASTHelper, as: AST - alias Archethic.Contracts.Interpreter.Scope alias Archethic.Contracts.Interpreter.CommonInterpreter + alias Archethic.Contracts.Interpreter.FunctionKeys + alias Archethic.Contracts.Interpreter.Library + alias Archethic.Contracts.Interpreter.Scope + require Logger @doc """ Parse the given node and return the function name it's args as strings and the AST block. """ - @spec parse(ast :: any(), function_keys :: list(Interpreter.function_key())) :: + @spec parse(ast :: any(), function_keys :: FunctionKeys.t()) :: {:ok, function_name :: binary(), args :: list(), function_ast :: any()} | {:error, node :: any(), reason :: binary()} def parse({{:atom, "fun"}, _, [{{:atom, function_name}, _, args}, [do: block]]}, functions_keys) do @@ -128,15 +130,11 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreter do when is_list(args) and function_name != "for" do arity = length(args) - case Enum.find(functions, fn - {^function_name, ^arity, _} -> true - _ -> false - end) do - {_, _, :private} -> - throw({:error, node, "not allowed to call private function from a private function"}) - - _ -> - CommonInterpreter.prewalk(node, acc) + if FunctionKeys.exist?(functions, function_name, arity) and + FunctionKeys.private?(functions, function_name, arity) do + throw({:error, node, "not allowed to call private function from a private function"}) + else + CommonInterpreter.prewalk(node, acc) end end diff --git a/lib/archethic/contracts/interpreter/function_keys.ex b/lib/archethic/contracts/interpreter/function_keys.ex new file mode 100644 index 000000000..be94a05e0 --- /dev/null +++ b/lib/archethic/contracts/interpreter/function_keys.ex @@ -0,0 +1,24 @@ +defmodule Archethic.Contracts.Interpreter.FunctionKeys do + @moduledoc """ + This module is a helper module to manage function keys (name, arity, visibility) + """ + + @type t() :: %{ + {name :: binary(), arity :: non_neg_integer()} => :public | :private + } + + @spec new() :: t() + def new(), do: Map.new() + + @spec add_private(keys :: t(), function_name :: binary(), arity :: non_neg_integer()) :: t() + def add_private(keys, function_name, arity), do: Map.put(keys, {function_name, arity}, :private) + + @spec add_public(keys :: t(), function_name :: binary(), arity :: non_neg_integer()) :: t() + def add_public(keys, function_name, arity), do: Map.put(keys, {function_name, arity}, :public) + + @spec exist?(keys :: t(), name :: binary(), arity :: non_neg_integer()) :: boolean() + def exist?(keys, name, arity), do: Map.has_key?(keys, {name, arity}) + + @spec private?(keys :: t(), name :: binary(), arity :: non_neg_integer()) :: boolean() + def private?(keys, name, arity), do: Map.fetch!(keys, {name, arity}) == :private +end diff --git a/test/archethic/contracts/interpreter/action_interpreter_test.exs b/test/archethic/contracts/interpreter/action_interpreter_test.exs index 228734e9c..2aa67ae2e 100644 --- a/test/archethic/contracts/interpreter/action_interpreter_test.exs +++ b/test/archethic/contracts/interpreter/action_interpreter_test.exs @@ -7,6 +7,7 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreterTest do alias Archethic.Contracts.ContractConstants, as: Constants alias Archethic.Contracts.Interpreter alias Archethic.Contracts.Interpreter.ActionInterpreter + alias Archethic.Contracts.Interpreter.FunctionKeys alias Archethic.TransactionChain.Transaction alias Archethic.TransactionChain.TransactionData @@ -488,7 +489,7 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreterTest do code |> Interpreter.sanitize_code() |> elem(1) - |> ActionInterpreter.parse([]) + |> ActionInterpreter.parse(%{}) end test "should be able to use existing function" do @@ -504,7 +505,7 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark as existing - |> ActionInterpreter.parse([{"hello", 0, :public}]) + |> ActionInterpreter.parse(FunctionKeys.add_public(%{}, "hello", 0)) # private function assert {:ok, _, _} = @@ -512,7 +513,7 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark as existing - |> ActionInterpreter.parse([{"hello", 0, :private}]) + |> ActionInterpreter.parse(FunctionKeys.add_private(%{}, "hello", 0)) end test "should be able to use named action without argument" do diff --git a/test/archethic/contracts/interpreter/condition_interpreter_test.exs b/test/archethic/contracts/interpreter/condition_interpreter_test.exs index ff0107806..7b4cc6e10 100644 --- a/test/archethic/contracts/interpreter/condition_interpreter_test.exs +++ b/test/archethic/contracts/interpreter/condition_interpreter_test.exs @@ -4,6 +4,7 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreterTest do alias Archethic.Contracts.ContractConditions.Subjects, as: ConditionsSubjects alias Archethic.Contracts.Interpreter alias Archethic.Contracts.Interpreter.ConditionInterpreter + alias Archethic.Contracts.Interpreter.FunctionKeys doctest ConditionInterpreter @@ -128,7 +129,7 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark function as existing - |> ConditionInterpreter.parse([{"get_uco_transfers", 0, :public}]) + |> ConditionInterpreter.parse(FunctionKeys.add_public(%{}, "get_uco_transfers", 0)) assert is_tuple(ast) && :ok == Macro.validate(ast) end diff --git a/test/archethic/contracts/interpreter/function_interpreter_test.exs b/test/archethic/contracts/interpreter/function_interpreter_test.exs index 6c5872188..f7ef91551 100644 --- a/test/archethic/contracts/interpreter/function_interpreter_test.exs +++ b/test/archethic/contracts/interpreter/function_interpreter_test.exs @@ -4,8 +4,9 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do use ArchethicCase use ExUnitProperties - alias Archethic.Contracts.Interpreter.FunctionInterpreter alias Archethic.Contracts.Interpreter + alias Archethic.Contracts.Interpreter.FunctionKeys + alias Archethic.Contracts.Interpreter.FunctionInterpreter # ---------------------------------------------- # parse/2 @@ -172,7 +173,7 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do code |> Interpreter.sanitize_code() |> elem(1) - |> FunctionInterpreter.parse([]) + |> FunctionInterpreter.parse(%{}) end test "should be able to call declared public function from private function" do @@ -187,7 +188,7 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark function as declared - |> FunctionInterpreter.parse([{"hello", 0, :public}]) + |> FunctionInterpreter.parse(FunctionKeys.add_public(%{}, "hello", 0)) end test "should not be able to call declared private function from private function" do @@ -202,7 +203,7 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark function as declared - |> FunctionInterpreter.parse([{"hello", 0, :private}]) + |> FunctionInterpreter.parse(FunctionKeys.add_private(%{}, "hello", 0)) end test "should not be able to call declared function from public function" do @@ -246,7 +247,7 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # pass allowed function - |> FunctionInterpreter.parse([{"hello", 0, :public}]) + |> FunctionInterpreter.parse(FunctionKeys.add_public(%{}, "hello", 0)) function_constant = %{:functions => %{{"hello", 0} => %{args: [], ast: ast_hello}}} diff --git a/test/archethic/contracts/interpreter/function_keys_test.exs b/test/archethic/contracts/interpreter/function_keys_test.exs new file mode 100644 index 000000000..c4db11aba --- /dev/null +++ b/test/archethic/contracts/interpreter/function_keys_test.exs @@ -0,0 +1,54 @@ +defmodule Archethic.Contracts.Interpreter.FunctionKeysTest do + @moduledoc false + + use ArchethicCase + + alias Archethic.Contracts.Interpreter.FunctionKeys + + test "add_public/3 and add_private/3 should add function in map" do + function_keys = + %{} + |> FunctionKeys.add_private("private_function", 0) + |> FunctionKeys.add_public("public_function", 1) + |> FunctionKeys.add_private("private_function", 1) + + assert %{ + {"private_function", 0} => :private, + {"private_function", 1} => :private, + {"public_function", 1} => :public + } = function_keys + end + + test "new/0 should return an empty map" do + assert %{} = FunctionKeys.new() + end + + describe "exist?/3" do + test "should return true if function exists for arity" do + function_keys = %{{"function", 1} => :private, {"other_function", 0} => :private} + assert FunctionKeys.exist?(function_keys, "function", 1) + end + + test "should return false if function doesn't exist for arity" do + function_keys = %{{"function", 1} => :private, {"other_function", 0} => :private} + refute FunctionKeys.exist?(function_keys, "function", 0) + end + end + + describe "private?/3" do + test "should return true if function is private" do + function_keys = %{{"function", 1} => :private, {"other_function", 0} => :private} + assert FunctionKeys.private?(function_keys, "function", 1) + end + + test "should return false if function is not private" do + function_keys = %{{"function", 1} => :public, {"other_function", 0} => :private} + refute FunctionKeys.private?(function_keys, "function", 1) + end + + test "should raise an error if function doesn't exist" do + function_keys = %{{"function", 1} => :public, {"other_function", 0} => :private} + assert_raise(KeyError, fn -> FunctionKeys.private?(function_keys, "function", 0) end) + end + end +end From 92944a854a79c1c540310bd3b0cf119dd93840c5 Mon Sep 17 00:00:00 2001 From: Neylix Date: Thu, 17 Aug 2023 17:20:07 +0200 Subject: [PATCH 3/4] Fix nested dot access in SC --- .../interpreter/common_interpreter.ex | 17 +++++------ .../interpreter/action_interpreter_test.exs | 10 +++++++ .../interpreter/function_interpreter_test.exs | 30 +++++++++++++++++++ 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/lib/archethic/contracts/interpreter/common_interpreter.ex b/lib/archethic/contracts/interpreter/common_interpreter.ex index 563ffcc7d..1f1d38a34 100644 --- a/lib/archethic/contracts/interpreter/common_interpreter.ex +++ b/lib/archethic/contracts/interpreter/common_interpreter.ex @@ -177,15 +177,14 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do end # Dot access nested (x.y.z) - def prewalk({{:., _, [first_arg = {{:., _, _}, _, _}, {:atom, key_name}]}, _, []}, acc) do - {nested, new_acc} = prewalk(first_arg, acc) - + # or Module.function().z + def prewalk({{:., _, [first_arg, {:atom, key_name}]}, _, []}, acc) do new_node = quote do - get_in(unquote(nested), [unquote(key_name)]) + Map.get(unquote(first_arg), unquote(key_name)) end - {new_node, new_acc} + {new_node, acc} end # Map access non-nested (x[y]) @@ -204,17 +203,15 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do # Map access nested (x[y][z]) def prewalk( - _node = {{:., _, [Access, :get]}, _, [first_arg = {{:., _, _}, _, _}, accessor]}, + _node = {{:., _, [Access, :get]}, _, [first_arg, accessor]}, acc ) do - {nested, new_acc} = prewalk(first_arg, acc) - new_node = quote do - get_in(unquote(nested), [unquote(accessor)]) + Map.get(unquote(first_arg), unquote(accessor)) end - {new_node, new_acc} + {new_node, acc} end # for var in list diff --git a/test/archethic/contracts/interpreter/action_interpreter_test.exs b/test/archethic/contracts/interpreter/action_interpreter_test.exs index 2aa67ae2e..f95ce1496 100644 --- a/test/archethic/contracts/interpreter/action_interpreter_test.exs +++ b/test/archethic/contracts/interpreter/action_interpreter_test.exs @@ -881,6 +881,16 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreterTest do assert %Transaction{data: %TransactionData{content: "hello"}} = sanitize_parse_execute(code) + code = ~S""" + actions triggered_by: transaction do + a = [b: [c: [d: [e: [f: [g: [h: "hello"]]]]]]] + + Contract.set_content a.b.c["d"].e.f.g.h + end + """ + + assert %Transaction{data: %TransactionData{content: "hello"}} = sanitize_parse_execute(code) + code = ~S""" actions triggered_by: transaction do diff --git a/test/archethic/contracts/interpreter/function_interpreter_test.exs b/test/archethic/contracts/interpreter/function_interpreter_test.exs index f7ef91551..b565a74ba 100644 --- a/test/archethic/contracts/interpreter/function_interpreter_test.exs +++ b/test/archethic/contracts/interpreter/function_interpreter_test.exs @@ -108,6 +108,36 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do |> FunctionInterpreter.parse([]) end + test "should not be able to use IO functions in public function with dot access" do + code = ~S""" + export fun test do + Chain.get_transaction("hello").content + end + """ + + assert {:error, _, "IO function calls not allowed in public functions"} = + code + |> Interpreter.sanitize_code() + |> elem(1) + # mark function as declared + |> FunctionInterpreter.parse(FunctionKeys.add_public(%{}, "hello", 0)) + end + + test "should not be able to use IO functions in public function with dynamic access" do + code = ~S""" + export fun test do + Chain.get_transaction("hello")["content"] + end + """ + + assert {:error, _, "IO function calls not allowed in public functions"} = + code + |> Interpreter.sanitize_code() + |> elem(1) + # mark function as declared + |> FunctionInterpreter.parse(FunctionKeys.add_public(%{}, "hello", 0)) + end + test "should return an error if module is unknown" do code = ~S""" export fun test_public do From 48d094d48676d8eacdbb5ac760123f860a0947dc Mon Sep 17 00:00:00 2001 From: Bastien Chamagne Date: Tue, 22 Aug 2023 14:32:11 +0200 Subject: [PATCH 4/4] lint --- lib/archethic/contracts/interpreter.ex | 18 +++++++------ .../interpreter/action_interpreter_test.exs | 10 ++++++-- .../condition_interpreter_test.exs | 5 +++- .../interpreter/function_interpreter_test.exs | 25 +++++++++++++++---- .../interpreter/function_keys_test.exs | 2 +- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/lib/archethic/contracts/interpreter.ex b/lib/archethic/contracts/interpreter.ex index c37b9ff5c..6fd88be9d 100644 --- a/lib/archethic/contracts/interpreter.ex +++ b/lib/archethic/contracts/interpreter.ex @@ -341,7 +341,7 @@ defmodule Archethic.Contracts.Interpreter do end defp parse_contract(1, ast) do - functions_keys = get_function_keys(ast) + functions_keys = parse_functions_keys(ast) case parse_ast_block(ast, %Contract{}, functions_keys) do {:ok, contract} -> @@ -434,17 +434,17 @@ defmodule Archethic.Contracts.Interpreter do Utils.get_current_time_for_interval(interval) end - defp get_function_keys(blocks, function_keys \\ FunctionKeys.new()) + defp parse_functions_keys(blocks, function_keys \\ FunctionKeys.new()) - defp get_function_keys( + defp parse_functions_keys( [{{:atom, "fun"}, _, [{{:atom, function_name}, _, args} | _]} | rest], function_keys ) do new_keys = FunctionKeys.add_private(function_keys, function_name, length(args)) - get_function_keys(rest, new_keys) + parse_functions_keys(rest, new_keys) end - defp get_function_keys( + defp parse_functions_keys( [ {{:atom, "export"}, _, [{{:atom, "fun"}, _, [{{:atom, function_name}, _, args} | _]} | _]} @@ -453,11 +453,13 @@ defmodule Archethic.Contracts.Interpreter do function_keys ) do new_keys = FunctionKeys.add_public(function_keys, function_name, length(args)) - get_function_keys(rest, new_keys) + parse_functions_keys(rest, new_keys) end - defp get_function_keys([_ | rest], function_keys), do: get_function_keys(rest, function_keys) - defp get_function_keys([], function_keys), do: function_keys + defp parse_functions_keys([_ | rest], function_keys), + do: parse_functions_keys(rest, function_keys) + + defp parse_functions_keys([], function_keys), do: function_keys # ----------------------------------------- # contract validation diff --git a/test/archethic/contracts/interpreter/action_interpreter_test.exs b/test/archethic/contracts/interpreter/action_interpreter_test.exs index f95ce1496..5cf44281b 100644 --- a/test/archethic/contracts/interpreter/action_interpreter_test.exs +++ b/test/archethic/contracts/interpreter/action_interpreter_test.exs @@ -505,7 +505,10 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark as existing - |> ActionInterpreter.parse(FunctionKeys.add_public(%{}, "hello", 0)) + |> ActionInterpreter.parse( + FunctionKeys.new() + |> FunctionKeys.add_public("hello", 0) + ) # private function assert {:ok, _, _} = @@ -513,7 +516,10 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark as existing - |> ActionInterpreter.parse(FunctionKeys.add_private(%{}, "hello", 0)) + |> ActionInterpreter.parse( + FunctionKeys.new() + |> FunctionKeys.add_private("hello", 0) + ) end test "should be able to use named action without argument" do diff --git a/test/archethic/contracts/interpreter/condition_interpreter_test.exs b/test/archethic/contracts/interpreter/condition_interpreter_test.exs index 7b4cc6e10..0f23ac6de 100644 --- a/test/archethic/contracts/interpreter/condition_interpreter_test.exs +++ b/test/archethic/contracts/interpreter/condition_interpreter_test.exs @@ -129,7 +129,10 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark function as existing - |> ConditionInterpreter.parse(FunctionKeys.add_public(%{}, "get_uco_transfers", 0)) + |> ConditionInterpreter.parse( + FunctionKeys.new() + |> FunctionKeys.add_public("get_uco_transfers", 0) + ) assert is_tuple(ast) && :ok == Macro.validate(ast) end diff --git a/test/archethic/contracts/interpreter/function_interpreter_test.exs b/test/archethic/contracts/interpreter/function_interpreter_test.exs index b565a74ba..bd33f0e61 100644 --- a/test/archethic/contracts/interpreter/function_interpreter_test.exs +++ b/test/archethic/contracts/interpreter/function_interpreter_test.exs @@ -120,7 +120,10 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark function as declared - |> FunctionInterpreter.parse(FunctionKeys.add_public(%{}, "hello", 0)) + |> FunctionInterpreter.parse( + FunctionKeys.new() + |> FunctionKeys.add_public("hello", 0) + ) end test "should not be able to use IO functions in public function with dynamic access" do @@ -135,7 +138,10 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark function as declared - |> FunctionInterpreter.parse(FunctionKeys.add_public(%{}, "hello", 0)) + |> FunctionInterpreter.parse( + FunctionKeys.new() + |> FunctionKeys.add_public("hello", 0) + ) end test "should return an error if module is unknown" do @@ -218,7 +224,10 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark function as declared - |> FunctionInterpreter.parse(FunctionKeys.add_public(%{}, "hello", 0)) + |> FunctionInterpreter.parse( + FunctionKeys.new() + |> FunctionKeys.add_public("hello", 0) + ) end test "should not be able to call declared private function from private function" do @@ -233,7 +242,10 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # mark function as declared - |> FunctionInterpreter.parse(FunctionKeys.add_private(%{}, "hello", 0)) + |> FunctionInterpreter.parse( + FunctionKeys.new() + |> FunctionKeys.add_private("hello", 0) + ) end test "should not be able to call declared function from public function" do @@ -277,7 +289,10 @@ defmodule Archethic.Contracts.Interpreter.FunctionInterpreterTest do |> Interpreter.sanitize_code() |> elem(1) # pass allowed function - |> FunctionInterpreter.parse(FunctionKeys.add_public(%{}, "hello", 0)) + |> FunctionInterpreter.parse( + FunctionKeys.new() + |> FunctionKeys.add_public("hello", 0) + ) function_constant = %{:functions => %{{"hello", 0} => %{args: [], ast: ast_hello}}} diff --git a/test/archethic/contracts/interpreter/function_keys_test.exs b/test/archethic/contracts/interpreter/function_keys_test.exs index c4db11aba..2e0dcbe05 100644 --- a/test/archethic/contracts/interpreter/function_keys_test.exs +++ b/test/archethic/contracts/interpreter/function_keys_test.exs @@ -7,7 +7,7 @@ defmodule Archethic.Contracts.Interpreter.FunctionKeysTest do test "add_public/3 and add_private/3 should add function in map" do function_keys = - %{} + FunctionKeys.new() |> FunctionKeys.add_private("private_function", 0) |> FunctionKeys.add_public("public_function", 1) |> FunctionKeys.add_private("private_function", 1)