Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smart Contract: Better error printing #988

Merged
merged 2 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions lib/archethic/contracts/interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,26 @@ defmodule Archethic.Contracts.Interpreter do
do_format_error_reason(reason, module, metadata)
end

def format_error_reason(
{{:., metadata, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _,
args},
reason
) do
# this cover the following case:
#
# code: List.empty?(12)
# ast:{{:., [line: 4], [{:__aliases__, [line: 4], [atom: "List"]}, {:atom, "empty?"}]}, [line: 4], '\f'}
#
# macro.to_string would return this:
# "{:atom, \"List\"} . :atom => \"empty?\"(12)"
#
# this code return this:
# List.empty?(12)
args_str = Enum.map_join(args, ", ", &Macro.to_string/1)

do_format_error_reason(reason, "#{module_name}.#{function_name}(#{args_str})", metadata)
end

def format_error_reason(ast_node = {_, metadata, _}, reason) do
node_msg =
try do
Expand Down
6 changes: 3 additions & 3 deletions lib/archethic/contracts/interpreter/action_interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -186,20 +186,20 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do

# check function exists
unless Library.function_exists?(absolute_module_atom, function_name) do
throw({:error, node, "unknown function: Contract.#{function_name}"})
throw({:error, node, "unknown function"})
end

# check function is available with given arity
# (we add 1 to arity because we add the contract as 1st argument implicitely)
unless Library.function_exists?(absolute_module_atom, function_name, length(args) + 1) do
throw({:error, node, "invalid arity for function Contract.#{function_name}"})
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 arguments for function Contract.#{function_name}"})
throw({:error, node, "invalid function arguments"})
end

new_node =
Expand Down
6 changes: 3 additions & 3 deletions lib/archethic/contracts/interpreter/common_interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -295,20 +295,20 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do

# check function exists
unless Library.function_exists?(absolute_module_atom, function_name) do
throw({:error, node, "unknown function: #{module_name}.#{function_name}"})
throw({:error, node, "unknown function"})
end

# check function is available with given arity
unless Library.function_exists?(absolute_module_atom, function_name, length(args)) do
throw({:error, node, "invalid arity for function #{module_name}.#{function_name}"})
throw({:error, node, "invalid function arity"})
end

module_atom = String.to_existing_atom(module_name)
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 arguments for function #{module_name}.#{function_name}"})
throw({:error, node, "invalid function arguments"})
end

meta_with_alias = Keyword.put(meta, :alias, absolute_module_atom)
Expand Down
36 changes: 36 additions & 0 deletions test/archethic/contracts/interpreter_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,42 @@ defmodule Archethic.Contracts.InterpreterTest do
"""
|> Interpreter.parse()
end

test "should return an human readable error if lib fn is called with bad arg" do
assert {:error, "invalid function arguments - List.empty?(12) - L4"} =
"""
@version 1
condition transaction: []
actions triggered_by: transaction do
x = List.empty?(12)
end
"""
|> Interpreter.parse()
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"} =
"""
@version 1
condition transaction: []
actions triggered_by: transaction do
x = List.empty?([1], "foobar")
end
"""
|> Interpreter.parse()
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"} =
"""
@version 1
condition transaction: []
actions triggered_by: transaction do
x = List.non_existing([1,2,3])
end
"""
|> Interpreter.parse()
end
end

describe "parse code v0" do
Expand Down