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

SmartContracts: Pass the calls to Interpreter.execute/3 & create corresponding message #967

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
5 changes: 3 additions & 2 deletions lib/archethic.ex
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,8 @@ defmodule Archethic do
@spec execute_contract(
Contract.trigger_type(),
Contract.t(),
nil | Transaction.t()
nil | Transaction.t(),
[Transaction.t()]
) ::
{:ok, nil | Transaction.t()}
| {:error,
Expand All @@ -332,7 +333,7 @@ defmodule Archethic do
| :invalid_transaction_constraints
| :invalid_oracle_constraints
| :invalid_inherit_constraints}
defdelegate execute_contract(trigger_type, contract, maybe_tx),
defdelegate execute_contract(trigger_type, contract, maybe_trigger_tx, calls),
to: Interpreter,
as: :execute

Expand Down
53 changes: 28 additions & 25 deletions lib/archethic/contracts/interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,13 @@ defmodule Archethic.Contracts.Interpreter do
Execution the given contract's trigger.
Checks all conditions block.

`maybe_tx` must be
- the incoming transaction when trigger_type: transaction
- the oracle transaction when trigger_type: oracle
- nil for the other trigger_types

/!\ The transaction returned is not complete, only the `type` and `data` are filled-in.
"""
@spec execute(
Contract.trigger_type(),
Contract.t(),
nil | Transaction.t(),
[Transaction.t()],
execute_opts()
) ::
{:ok, nil | Transaction.t()}
Expand All @@ -134,7 +130,8 @@ defmodule Archethic.Contracts.Interpreter do
def execute(
trigger_type,
contract = %Contract{triggers: triggers},
maybe_tx,
maybe_trigger_tx,
calls,
opts \\ []
) do
case triggers[trigger_type] do
Expand All @@ -146,8 +143,8 @@ defmodule Archethic.Contracts.Interpreter do
trigger_type,
trigger_code,
contract,
maybe_tx,
contract,
maybe_trigger_tx,
calls,
opts
)
end
Expand Down Expand Up @@ -221,24 +218,24 @@ defmodule Archethic.Contracts.Interpreter do
defp do_execute(
:transaction,
trigger_code,
contract,
incoming_tx = %Transaction{},
%Contract{
contract = %Contract{
version: version,
conditions: conditions,
constants: %Constants{
contract: contract_constants
}
},
trigger_tx = %Transaction{},
calls,
opts
) do
constants = %{
"transaction" => Constants.from_transaction(incoming_tx),
"transaction" => Constants.from_transaction(trigger_tx),
"contract" => contract_constants
}

if valid_conditions?(version, conditions.transaction, constants) do
case execute_trigger(version, trigger_code, contract, incoming_tx) do
case execute_trigger(version, trigger_code, contract, trigger_tx, calls) do
nil ->
{:ok, nil}

Expand All @@ -261,24 +258,24 @@ defmodule Archethic.Contracts.Interpreter do
defp do_execute(
:oracle,
trigger_code,
contract,
oracle_tx = %Transaction{},
%Contract{
contract = %Contract{
version: version,
conditions: conditions,
constants: %Constants{
contract: contract_constants
}
},
trigger_tx = %Transaction{},
calls,
opts
) do
constants = %{
"transaction" => Constants.from_transaction(oracle_tx),
"transaction" => Constants.from_transaction(trigger_tx),
"contract" => contract_constants
}

if valid_conditions?(version, conditions.oracle, constants) do
case execute_trigger(version, trigger_code, contract, oracle_tx) do
case execute_trigger(version, trigger_code, contract, trigger_tx, calls) do
nil ->
{:ok, nil}

Expand All @@ -297,12 +294,12 @@ defmodule Archethic.Contracts.Interpreter do
defp do_execute(
_trigger_type,
trigger_code,
contract,
contract = %Contract{version: version},
nil,
%Contract{version: version},
calls,
opts
) do
case execute_trigger(version, trigger_code, contract) do
case execute_trigger(version, trigger_code, contract, nil, calls) do
nil ->
{:ok, nil}

Expand All @@ -319,13 +316,19 @@ defmodule Archethic.Contracts.Interpreter do
version,
trigger_code,
contract,
maybe_tx \\ nil
maybe_trigger_tx,
calls
) do
constants_trigger = %{
"calls" => Enum.map(calls, &Constants.from_transaction/1),
bchamagne marked this conversation as resolved.
Show resolved Hide resolved
"transaction" =>
case maybe_tx do
nil -> nil
tx -> Constants.from_transaction(tx)
case maybe_trigger_tx do
nil ->
nil

trigger_tx ->
# :oracle & :transaction
Constants.from_transaction(trigger_tx)
end,
"contract" => contract.constants.contract
}
Expand Down
11 changes: 4 additions & 7 deletions lib/archethic/contracts/interpreter/action_interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do
# because it is mutable.
#
# constants should already contains the global variables:
# - "calls": the transactions that called this exact contract version
# - "contract": current contract transaction
# - "transaction": the incoming transaction (when trigger=transaction)
# - "transaction": the incoming transaction (when trigger=transaction|oracle)
Scope.init(Map.put(constants, "next_transaction", next_tx))

# we can ignore the result & binding
Expand Down Expand Up @@ -150,19 +151,15 @@ defmodule Archethic.Contracts.Interpreter.ActionInterpreter do
# | .__/ \___/|___/\__| \_/\_/ \__,_|_|_|\_\
# |_|
# ----------------------------------------------------------------------
# Contract.get_calls() => Contract.get_calls(contract.address)
# Contract.get_calls()
defp postwalk(
_node =
{{:., _meta, [{:__aliases__, _, [atom: "Contract"]}, {:atom, "get_calls"}]}, _, []},
acc
) do
# contract is one of the "magic" variables that we expose to the user's code
# it is bound in the root scope
new_node =
quote do
Archethic.Contracts.Interpreter.Library.Contract.get_calls(
Scope.read_global(["contract", "address"])
)
Archethic.Contracts.Interpreter.Library.Contract.get_calls()
end

{new_node, acc}
Expand Down
18 changes: 0 additions & 18 deletions lib/archethic/contracts/interpreter/condition_interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -171,24 +171,6 @@ defmodule Archethic.Contracts.Interpreter.ConditionInterpreter do
{new_node, acc}
end

# Override Contract.get_calls()
defp postwalk(
_subject = [_global_variable, _],
node =
{{:., _meta, [{:__aliases__, _, [atom: "Contract"]}, {:atom, "get_calls"}]}, _, []},
_acc
) do
# new_node =
# quote do
# Archethic.Contracts.Interpreter.Library.Contract.get_calls(
# Scope.read_global([unquote(global_variable), "address"])
# )
# end

# {new_node, acc}
throw({:error, node, "Contract.get_calls() not yet implemented in the conditions"})
end

defp postwalk(_subject, node, acc) do
CommonInterpreter.postwalk(node, acc)
end
Expand Down
28 changes: 0 additions & 28 deletions lib/archethic/contracts/interpreter/legacy/action_interpreter.ex
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,6 @@ defmodule Archethic.Contracts.Interpreter.Legacy.ActionInterpreter do
{node, {:ok, %{context | scope: {:function, function, parent_scope}}}}
end

# Whitelist the get_calls/0 (this will be expanded to a get_calls(contract.address) in postwalk)
defp prewalk(
node = {{:atom, "get_calls"}, _, []},
acc = {:ok, %{scope: {:actions, _}}}
) do
{node, acc}
end

# Blacklist the add_uco_transfer argument list
defp prewalk(
node = {{:atom, "to"}, address},
Expand Down Expand Up @@ -294,26 +286,6 @@ defmodule Archethic.Contracts.Interpreter.Legacy.ActionInterpreter do
end
end

# expand get_calls() to get_calls(contract.address)
defp postwalk(
{{:atom, "get_calls"}, meta, []},
acc
) do
node = {
{:atom, "get_calls"},
meta,
[
{:get_in, meta,
[
{:scope, meta, nil},
["contract", "address"]
]}
]
}

{node, acc}
end

defp postwalk(node, acc) do
UtilsInterpreter.postwalk(node, acc)
end
Expand Down
19 changes: 0 additions & 19 deletions lib/archethic/contracts/interpreter/legacy/library.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ defmodule Archethic.Contracts.Interpreter.Legacy.Library do
P2P.Message.GetFirstPublicKey,
P2P.Message.FirstPublicKey,
TransactionChain,
Contracts.ContractConstants,
Contracts.TransactionLookup,
Utils
}

Expand Down Expand Up @@ -190,23 +188,6 @@ defmodule Archethic.Contracts.Interpreter.Legacy.Library do
def size(list) when is_list(list), do: length(list)
def size(map) when is_map(map), do: map_size(map)

@doc """
Get the inputs(type= :call) of the given transaction

This is useful for contracts that want to throttle their calls
"""
@spec get_calls(binary()) :: list(map())
def get_calls(contract_address) do
contract_address
|> UtilsInterpreter.maybe_decode_hex()
|> TransactionLookup.list_contract_transactions()
|> Enum.map(fn {address, _, _} ->
# TODO: parallelize
{:ok, tx} = TransactionChain.get_transaction(address, [], :io)
ContractConstants.from_transaction(tx)
end)
end

@doc """
Get the genesis public key
"""
Expand Down
17 changes: 11 additions & 6 deletions lib/archethic/contracts/interpreter/library/contract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ defmodule Archethic.Contracts.Interpreter.Library.Contract do
"""
@behaviour Archethic.Contracts.Interpreter.Library

alias Archethic.Contracts.Interpreter.Scope
alias Archethic.Contracts.Interpreter.ASTHelper, as: AST
alias Archethic.TransactionChain.Transaction
alias Archethic.Contracts.Interpreter.Legacy
alias Archethic.Contracts.Interpreter.Legacy.TransactionStatements

# get_calls has it's own postwalk (to inject the address),
# it does not require a check_types
@spec get_calls(binary()) :: list(map())
defdelegate get_calls(contract_address),
to: Legacy.Library
@spec get_calls() :: list(map())
def get_calls() do
# DISCUSS:
# this function is not really needed, we might just tell the users there is a "calls" global variable?
Scope.read_global(["calls"])
end

@spec set_type(Transaction.t(), binary()) :: Transaction.t()
defdelegate set_type(next_tx, type),
Expand Down Expand Up @@ -120,5 +121,9 @@ defmodule Archethic.Contracts.Interpreter.Library.Contract do
AST.is_list?(first) || AST.is_variable_or_function_call?(first)
end

def check_types(:get_calls, []) do
true
end

def check_types(_, _), do: false
end
23 changes: 17 additions & 6 deletions lib/archethic/contracts/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,25 @@ defmodule Archethic.Contracts.Worker do

# TRIGGER: TRANSACTION
def handle_cast(
{:execute, incoming_tx = %Transaction{}},
{:execute, trigger_tx = %Transaction{}},
state = %{contract: contract}
) do
contract_tx = Constants.to_transaction(contract.constants.contract)

meta = log_metadata(contract_tx, incoming_tx)
meta = log_metadata(contract_tx, trigger_tx)
Logger.debug("Contract execution started", meta)

with true <- enough_funds?(contract_tx.address),
{:ok, calls} <- TransactionChain.fetch_contract_calls(contract_tx.address),
{:ok, next_tx = %Transaction{}} <-
Interpreter.execute(:transaction, contract, incoming_tx, skip_inherit_check?: true),
Interpreter.execute(
:transaction,
contract,
trigger_tx,
# we append the trigger_tx to the calls in case it is missing due to race condition
Enum.uniq([trigger_tx | calls]),
skip_inherit_check?: true
),
{:ok, next_tx} <- chain_transaction(next_tx, contract_tx),
:ok <- ensure_enough_funds(next_tx, contract_tx.address),
:ok <- handle_new_transaction(next_tx) do
Expand All @@ -110,8 +118,9 @@ defmodule Archethic.Contracts.Worker do
Logger.debug("Contract execution started", meta)

with true <- enough_funds?(contract_tx.address),
{:ok, calls} <- TransactionChain.fetch_contract_calls(contract_tx.address),
{:ok, next_tx = %Transaction{}} <-
Interpreter.execute(trigger_type, contract, nil, skip_inherit_check?: true),
Interpreter.execute(trigger_type, contract, nil, calls, skip_inherit_check?: true),
{:ok, next_tx} <- chain_transaction(next_tx, contract_tx),
:ok <- ensure_enough_funds(next_tx, contract_tx.address),
:ok <- handle_new_transaction(next_tx) do
Expand All @@ -135,8 +144,9 @@ defmodule Archethic.Contracts.Worker do
Logger.debug("Contract execution started", meta)

with true <- enough_funds?(contract_tx.address),
{:ok, calls} <- TransactionChain.fetch_contract_calls(contract_tx.address),
{:ok, next_tx = %Transaction{}} <-
Interpreter.execute(trigger_type, contract, nil, skip_inherit_check?: true),
Interpreter.execute(trigger_type, contract, nil, calls, skip_inherit_check?: true),
{:ok, next_tx} <- chain_transaction(next_tx, contract_tx),
:ok <- ensure_enough_funds(next_tx, contract_tx.address),
:ok <- handle_new_transaction(next_tx) do
Expand All @@ -162,8 +172,9 @@ defmodule Archethic.Contracts.Worker do
Logger.debug("Contract execution started", meta)

with true <- enough_funds?(contract_tx.address),
{:ok, calls} <- TransactionChain.fetch_contract_calls(contract_tx.address),
{:ok, next_tx = %Transaction{}} <-
Interpreter.execute(:oracle, contract, oracle_tx, skip_inherit_check?: true),
Interpreter.execute(:oracle, contract, oracle_tx, calls, skip_inherit_check?: true),
{:ok, next_tx} <- chain_transaction(next_tx, contract_tx),
:ok <- ensure_enough_funds(next_tx, contract_tx.address),
:ok <- handle_new_transaction(next_tx) do
Expand Down
Loading