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 functions without args #1181

Merged
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
8e24e91
function parsing
herissondev Jul 18, 2023
e9489cc
function parsing tests
herissondev Jul 18, 2023
118c66e
interpreter parse functions
herissondev Jul 18, 2023
c978cd7
contract add functions to struct
herissondev Jul 18, 2023
771dd41
function_interpreter check if function exist
herissondev Jul 18, 2023
8ae77bc
get available functions and pass them to parsers
herissondev Jul 18, 2023
5dda404
add functions to scope
herissondev Jul 18, 2023
6f8f34a
add functions contract struct
herissondev Jul 18, 2023
c8f6e21
allow call to functions
herissondev Jul 18, 2023
4e976fb
postwalk functions common_interpreter.ex
herissondev Jul 19, 2023
54ee718
remove functions postwalk function_interpreter.ex
herissondev Jul 19, 2023
fed01e9
allow custom function call in action_interpreter.ex
herissondev Jul 19, 2023
8ff63af
function call return value common_interpreter.ex
herissondev Jul 19, 2023
4f840c4
function_interpreter.ex add constants in scope
herissondev Jul 19, 2023
ca0f58a
remove add_function as not used
herissondev Jul 19, 2023
cf0a99c
add possibility to pass existing custom functions to sanitize_parse_e…
herissondev Jul 19, 2023
196c391
pass functions to action interpreter scope's
herissondev Jul 19, 2023
ca7f8f1
move function prewalk to not catch "for var..."
herissondev Jul 19, 2023
2d42231
function_interpreter test parsing and execute
herissondev Jul 19, 2023
75c8601
create execute_function_ast for scope
herissondev Jul 19, 2023
3d42c95
custom function matches check_types
herissondev Jul 19, 2023
11c36cd
test function parsing in action_interpreter
herissondev Jul 19, 2023
f58aaaf
test function execution in action_interpreter
herissondev Jul 19, 2023
28910d1
format
herissondev Jul 19, 2023
df7da9f
allow function calls in condition block
herissondev Jul 19, 2023
c9143cb
fix function test
herissondev Jul 20, 2023
5b212ca
test function in conditions
herissondev Jul 20, 2023
06210d5
add functions to condition constatns
herissondev Jul 20, 2023
890501f
add functions test in interpreter
herissondev Jul 20, 2023
879db45
fucntions key to string
herissondev Jul 20, 2023
52adfe1
function_key function
herissondev Jul 20, 2023
c188430
format
herissondev Jul 20, 2023
b4c723f
case to if
herissondev Jul 24, 2023
5e62951
function_key to tuple
herissondev Jul 24, 2023
529fe36
add get_function_ast doc
herissondev Jul 24, 2023
179efd2
merge public_functions and private_functions into functions
herissondev Jul 24, 2023
f1ddd7a
remove default functions_keys
herissondev Jul 24, 2023
172fd77
remove unused function
herissondev Jul 24, 2023
1e11cff
move test to interpreter_test.exs
herissondev Jul 24, 2023
0c2dc1f
format
herissondev Jul 24, 2023
232f24d
pass function_keys in condition_interpreter.ex's postwalk
herissondev Jul 25, 2023
49e2cf3
add function_key type
herissondev Jul 25, 2023
8822dee
fix spec and doc
herissondev Jul 25, 2023
02bb00f
scope test
herissondev Jul 25, 2023
1b469f6
Wrap single line function in ast block
Neylix Jul 25, 2023
5f73ac4
Fix @spec
Neylix Jul 25, 2023
89bc9f2
Rename get_functions to get_functions_keys
Neylix Jul 25, 2023
fa13e29
Use String interpolation
Neylix Jul 25, 2023
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
18 changes: 14 additions & 4 deletions lib/archethic/contracts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -255,27 +255,37 @@ defmodule Archethic.Contracts do

defp get_condition_constants(
:inherit,
%Contract{constants: %Constants{contract: contract_constant}},
%Contract{
constants: %Constants{contract: contract_constant},
public_functions: public_functions,
bchamagne marked this conversation as resolved.
Show resolved Hide resolved
private_functions: private_functions
},
transaction,
datetime
) do
%{
"previous" => contract_constant,
"next" => Constants.from_transaction(transaction),
"_time_now" => DateTime.to_unix(datetime)
"_time_now" => DateTime.to_unix(datetime),
"functions" => Map.merge(public_functions, private_functions)
}
end

defp get_condition_constants(
_,
%Contract{constants: %Constants{contract: contract_constant}},
%Contract{
constants: %Constants{contract: contract_constant},
public_functions: public_functions,
private_functions: private_functions
},
transaction,
datetime
) do
%{
"transaction" => Constants.from_transaction(transaction),
"contract" => contract_constant,
"_time_now" => DateTime.to_unix(datetime)
"_time_now" => DateTime.to_unix(datetime),
"functions" => Map.merge(public_functions, private_functions)
}
end
end
28 changes: 28 additions & 0 deletions lib/archethic/contracts/contract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ defmodule Archethic.Contracts.Contract do
alias Archethic.Contracts.ContractConstants, as: Constants

alias Archethic.Contracts.Interpreter
alias Archethic.Contracts.Interpreter.Scope

alias Archethic.SharedSecrets

alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.TransactionData

defstruct triggers: %{},
public_functions: %{},
private_functions: %{},
version: 0,
conditions: %{},
constants: %Constants{},
Expand Down Expand Up @@ -94,4 +97,29 @@ defmodule Archethic.Contracts.Contract do
) do
Map.update!(contract, :conditions, &Map.put(&1, condition_name, conditions))
end

@doc """
Add a public or private function to the contract
"""
def add_function(
bchamagne marked this conversation as resolved.
Show resolved Hide resolved
contract = %__MODULE__{},
:public,
function_name,
ast,
args
) do
function_key = Scope.function_to_function_key(function_name, args)
Map.update!(contract, :public_functions, &Map.put(&1, function_key, %{ast: ast, args: args}))
end

def add_function(
contract = %__MODULE__{},
:private,
function_name,
ast,
args
) do
function_key = Scope.function_to_function_key(function_name, args)
Map.update!(contract, :private_functions, &Map.put(&1, function_key, %{ast: ast, args: args}))
end
end
71 changes: 59 additions & 12 deletions lib/archethic/contracts/interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ defmodule Archethic.Contracts.Interpreter do
alias __MODULE__.Legacy
alias __MODULE__.ActionInterpreter
alias __MODULE__.ConditionInterpreter
alias __MODULE__.FunctionInterpreter
alias __MODULE__.Scope

alias __MODULE__.ConditionValidator

alias Archethic.Contracts.Contract
Expand Down Expand Up @@ -117,7 +120,9 @@ defmodule Archethic.Contracts.Interpreter do
%Contract{
version: version,
triggers: triggers,
constants: %Constants{contract: contract_constants}
constants: %Constants{contract: contract_constants},
public_functions: public_functions,
private_functions: private_functions
},
maybe_trigger_tx,
opts \\ []
Expand Down Expand Up @@ -150,7 +155,8 @@ defmodule Archethic.Contracts.Interpreter do
Constants.from_transaction(trigger_tx)
end,
"contract" => contract_constants,
"_time_now" => timestamp_now
"_time_now" => timestamp_now,
"functions" => Map.merge(public_functions, private_functions)
}

result =
Expand Down Expand Up @@ -291,7 +297,9 @@ defmodule Archethic.Contracts.Interpreter do
end

defp parse_contract(1, ast) do
case parse_ast_block(ast, %Contract{}) do
functions_keys = get_functions(ast)

case parse_ast_block(ast, %Contract{}, functions_keys) do
{:ok, contract} ->
{:ok, %{contract | version: 1}}

Expand All @@ -304,20 +312,20 @@ defmodule Archethic.Contracts.Interpreter do
{:error, "@version not supported"}
end

defp parse_ast_block([ast | rest], contract) do
case parse_ast(ast, contract) do
defp parse_ast_block([ast | rest], contract, functions_keys) do
case parse_ast(ast, contract, functions_keys) do
{:ok, contract} ->
parse_ast_block(rest, contract)
parse_ast_block(rest, contract, functions_keys)

{:error, _, _} = e ->
e
end
end

defp parse_ast_block([], contract), do: {:ok, contract}
defp parse_ast_block([], contract, _), do: {:ok, contract}

defp parse_ast(ast = {{:atom, "condition"}, _, _}, contract) do
case ConditionInterpreter.parse(ast) do
defp parse_ast(ast = {{:atom, "condition"}, _, _}, contract, functions_keys) do
case ConditionInterpreter.parse(ast, functions_keys) do
{:ok, condition_type, condition} ->
{:ok, Contract.add_condition(contract, condition_type, condition)}

Expand All @@ -326,8 +334,8 @@ defmodule Archethic.Contracts.Interpreter do
end
end

defp parse_ast(ast = {{:atom, "actions"}, _, _}, contract) do
case ActionInterpreter.parse(ast) do
defp parse_ast(ast = {{:atom, "actions"}, _, _}, contract, functions_keys) do
case ActionInterpreter.parse(ast, functions_keys) do
{:ok, trigger_type, actions} ->
{:ok, Contract.add_trigger(contract, trigger_type, actions)}

Expand All @@ -336,7 +344,31 @@ defmodule Archethic.Contracts.Interpreter do
end
end

defp parse_ast(ast, _), do: {:error, ast, "unexpected term"}
defp parse_ast(
ast = {{:atom, "export"}, _, [{{:atom, "fun"}, _, _} | _]},
contract,
functions_keys
) do
case FunctionInterpreter.parse(ast, functions_keys) do
{:ok, function_name, args, ast} ->
{:ok, Contract.add_function(contract, :public, function_name, ast, args)}

{:error, _, _} = e ->
e
end
end

defp parse_ast(ast = {{:atom, "fun"}, _, _}, contract, functions_keys) do
case FunctionInterpreter.parse(ast, functions_keys) do
{:ok, function_name, args, ast} ->
{:ok, Contract.add_function(contract, :private, function_name, ast, args)}

{:error, _, _} = e ->
e
end
end

defp parse_ast(ast, _, _), do: {:error, ast, "unexpected term"}

defp time_now(:transaction, %Transaction{
validation_stamp: %ValidationStamp{timestamp: timestamp}
Expand All @@ -358,6 +390,21 @@ defmodule Archethic.Contracts.Interpreter do
Utils.get_current_time_for_interval(interval)
end

defp get_functions([{{:atom, "fun"}, _, [{{:atom, function_name}, _, args} | _]} | rest]) do
[Scope.function_to_function_key(function_name, args) | get_functions(rest)]
end

defp get_functions([
{{:atom, "export"}, _,
[{{:atom, "fun"}, _, [{{:atom, function_name}, _, args} | _]} | _]}
| rest
]) do
[Scope.function_to_function_key(function_name, args) | get_functions(rest)]
end

defp get_functions([_ | rest]), do: get_functions(rest)
defp get_functions([]), do: []

# -----------------------------------------
# contract validation
# -----------------------------------------
Expand Down
23 changes: 13 additions & 10 deletions lib/archethic/contracts/interpreter/action_interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do
@doc """
Parse the given node and return the trigger and the actions block.
"""
@spec parse(any()) :: {:ok, atom(), any()} | {:error, any(), String.t()}
def parse({{:atom, "actions"}, _, [keyword, [do: block]]}) do
@spec parse(any(), list) :: {:ok, atom(), any()} | {:error, any(), String.t()}
Neylix marked this conversation as resolved.
Show resolved Hide resolved
def parse(_, _ \\ [])
bchamagne marked this conversation as resolved.
Show resolved Hide resolved

def parse({{:atom, "actions"}, _, [keyword, [do: block]]}, functions_keys) do
trigger_type = extract_trigger(keyword)

# We only parse the do..end block with the macro.traverse
# this help us keep a clean accumulator that is used only for scoping.
actions_ast = parse_block(AST.wrap_in_block(block))
actions_ast = parse_block(AST.wrap_in_block(block), functions_keys)

{:ok, trigger_type, actions_ast}
catch
Expand All @@ -30,7 +32,7 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do
{:error, node, reason}
end

def parse(node) do
def parse(node, _) do
{:error, node, "unexpected term"}
end

Expand Down Expand Up @@ -127,7 +129,7 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do
throw({:error, node, "Invalid trigger"})
end

defp parse_block(ast) do
defp parse_block(ast, functions_keys) do
# here the accumulator is an list of parent scopes & current scope
# where we can access variables from all of them
# `acc = [ref1]` means read variable from scope.ref1 or scope
Expand All @@ -142,7 +144,7 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do
prewalk(node, acc)
end,
fn node, acc ->
postwalk(node, acc)
postwalk(node, acc, functions_keys)
end
)

Expand Down Expand Up @@ -187,7 +189,8 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do
defp postwalk(
node =
{{:., _meta, [{:__aliases__, _, [atom: "Contract"]}, {:atom, function_name}]}, _, args},
acc
acc,
_
) do
absolute_module_atom = Archethic.Contracts.Interpreter.Library.Contract

Expand All @@ -204,7 +207,7 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do

function_atom = String.to_existing_atom(function_name)

# check the type of the args
# check the type of the args, and allow custom function call as args
unless absolute_module_atom.check_types(function_atom, args) do
throw({:error, node, "invalid function arguments"})
end
Expand All @@ -226,8 +229,8 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do
end

# --------------- catch all -------------------
defp postwalk(node, acc) do
CommonInterpreter.postwalk(node, acc)
defp postwalk(node, acc, functions_keys) do
CommonInterpreter.postwalk(node, acc, functions_keys)
end

# keep only the transaction fields we are interested in
Expand Down
39 changes: 33 additions & 6 deletions lib/archethic/contracts/interpreter/common_interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do
{ast, acc}
end

# function call, should be placed after "for" prewalk
def prewalk(node = {{:atom, _}, _, args}, acc) when is_list(args), do: {node, acc}

# log (not documented, only useful for developer debugging)
# will soon be updated to log into the playground console
def prewalk(_node = {{:atom, "log"}, _, [data]}, acc) do
Expand All @@ -272,9 +275,12 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do
# |_|
# ----------------------------------------------------------------------
# exit block == set parent scope
def postwalk(_, _, function_keys \\ [])
bchamagne marked this conversation as resolved.
Show resolved Hide resolved

def postwalk(
node = {:__block__, _, _},
acc
acc,
_
) do
{node, List.delete_at(acc, -1)}
end
Expand All @@ -283,7 +289,8 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do
def postwalk(
node =
{{:., meta, [{:__aliases__, _, [atom: module_name]}, {:atom, function_name}]}, _, args},
acc
acc,
_
)
when module_name in @modules_whitelisted do
absolute_module_atom =
Expand All @@ -307,6 +314,7 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do
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
Expand All @@ -322,7 +330,8 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do
# variable are read from scope
def postwalk(
_node = {{:atom, var_name}, _, nil},
acc
acc,
_
) do
new_node =
quote do
Expand All @@ -340,7 +349,8 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do
{:%{}, _, [{var_name, list}]},
[do: block]
]},
acc
acc,
_
) do
# FIXME: here acc is already the parent acc, it is not the acc of the do block
# FIXME: this means that our `var_name` will live in the parent scope
Expand All @@ -360,8 +370,25 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do
{new_node, acc}
end

def postwalk(node = {{:atom, function_name}, _, args}, acc, function_keys) when is_list(args) do
function_key = Scope.function_to_function_key(function_name, args)

case Enum.member?(function_keys, function_key) do
bchamagne marked this conversation as resolved.
Show resolved Hide resolved
true ->
new_node =
quote do
Scope.execute_function_ast(unquote(function_name), unquote(args))
end

{new_node, acc}

false ->
throw({:error, node, "The function " <> function_key <> " does not exist"})
end
end

# BigInt mathematics to avoid floating point issues
def postwalk(_node = {ast, meta, [lhs, rhs]}, acc) when ast in [:*, :/, :+, :-] do
def postwalk(_node = {ast, meta, [lhs, rhs]}, acc, _) when ast in [:*, :/, :+, :-] do
new_node =
quote line: Keyword.fetch!(meta, :line) do
AST.decimal_arithmetic(unquote(ast), unquote(lhs), unquote(rhs))
Expand All @@ -371,7 +398,7 @@ defmodule Archethic.Contracts.Interpreter.CommonInterpreter do
end

# whitelist rest
def postwalk(node, acc), do: {node, acc}
def postwalk(node, acc, _), do: {node, acc}

# ----------------------------------------------------------------------
# _ _
Expand Down
Loading
Loading