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

Catch error in proof of work to get contracts parsing error #628

Merged
merged 15 commits into from
Dec 2, 2022
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
2 changes: 0 additions & 2 deletions .dialyzer_ignore.exs

This file was deleted.

2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ jobs:
mkdir -p priv/plts
mix dialyzer --plt
- name: Run dialyzer
run: mix dialyzer --no-check
run: mix dialyzer --no-check --ignore-exit-status
4 changes: 4 additions & 0 deletions lib/archethic/account/mem_tables_loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ defmodule Archethic.Account.MemTablesLoader do
}
} ->
TokenLedger.add_unspent_output(address, unspent_output)

_ ->
# Ignore smart contract calls
:ignore
end)
end

Expand Down
44 changes: 23 additions & 21 deletions lib/archethic/contracts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ defmodule Archethic.Contracts do
"""

alias __MODULE__.Contract
alias __MODULE__.Contract.Conditions
alias __MODULE__.Contract.Constants
alias __MODULE__.Contract.Trigger
alias __MODULE__.ContractConditions, as: Conditions
alias __MODULE__.ContractConstants, as: Constants
alias __MODULE__.ConditionInterpreter
alias __MODULE__.Interpreter
alias __MODULE__.Loader
alias __MODULE__.TransactionLookup
Expand Down Expand Up @@ -59,9 +59,8 @@ defmodule Archethic.Contracts do
contract: nil,
transaction: nil
},
triggers: [
%Trigger{
actions: {:__block__, [], [
triggers: %{
{:datetime, ~U[2020-09-25 13:18:43Z]} => {:__block__, [], [
{
:=,
[line: 7],
Expand All @@ -79,11 +78,9 @@ defmodule Archethic.Contracts do
]
}
]},
opts: [at: ~U[2020-09-25 13:18:43Z]],
type: :datetime
}
]
}}
}
}
"""
@spec parse(binary()) :: {:ok, Contract.t()} | {:error, binary()}
def parse(contract_code) when is_binary(contract_code) do
Expand All @@ -100,11 +97,10 @@ defmodule Archethic.Contracts do
})

cond do
Enum.any?(triggers, &(&1.type == :transaction)) and
Conditions.empty?(transaction_conditions) ->
Map.has_key?(triggers, :transaction) and Conditions.empty?(transaction_conditions) ->
{:error, "missing transaction conditions"}

Enum.any?(triggers, &(&1.type == :oracle)) and Conditions.empty?(oracle_conditions) ->
Map.has_key?(triggers, :oracle) and Conditions.empty?(oracle_conditions) ->
{:error, "missing oracle conditions"}

true ->
Expand Down Expand Up @@ -168,18 +164,20 @@ defmodule Archethic.Contracts do
end

defp validate_conditions(inherit_conditions, constants) do
if Interpreter.valid_conditions?(inherit_conditions, constants) do
if ConditionInterpreter.valid_conditions?(inherit_conditions, constants) do
:ok
else
Logger.error("Inherit constraints not respected")
{:error, :invalid_inherit_constraints}
end
end

defp validate_triggers([], _, _), do: :ok
defp validate_triggers(triggers, _next_tx, _date) when map_size(triggers) == 0, do: :ok

defp validate_triggers(triggers, next_tx, date) do
if Enum.any?(triggers, &valid_from_trigger?(&1, next_tx, date)) do
if Enum.any?(triggers, fn {trigger_type, _} ->
valid_from_trigger?(trigger_type, next_tx, date)
end) do
:ok
else
Logger.error("Transaction not processed by a valid smart contract trigger")
Expand All @@ -188,7 +186,7 @@ defmodule Archethic.Contracts do
end

defp valid_from_trigger?(
%Trigger{type: :datetime, opts: [at: datetime]},
{:datetime, datetime},
%Transaction{},
validation_date = %DateTime{}
) do
Expand All @@ -198,21 +196,25 @@ defmodule Archethic.Contracts do
end

defp valid_from_trigger?(
%Trigger{type: :interval, opts: [at: interval]},
{:interval, interval},
%Transaction{},
validation_date = %DateTime{}
) do
interval
|> CronParser.parse!(true)
|> CronParser.parse!()
|> CronDateChecker.matches_date?(DateTime.to_naive(validation_date))
end

defp valid_from_trigger?(%Trigger{type: :transaction}, _, _), do: true
defp valid_from_trigger?(_, _, _), do: true

@doc """
List the address of the transaction which has contacted a smart contract
"""
@spec list_contract_transactions(binary()) :: list({binary(), DateTime.t()})
@spec list_contract_transactions(contract_address :: binary()) ::
list(
{transaction_address :: binary(), transaction_timestamp :: DateTime.t(),
protocol_version :: non_neg_integer()}
)
defdelegate list_contract_transactions(address),
to: TransactionLookup,
as: :list_contract_transactions
Expand Down
54 changes: 16 additions & 38 deletions lib/archethic/contracts/contract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ defmodule Archethic.Contracts.Contract do
Represents a smart contract
"""

alias Archethic.Contracts.Contract.Conditions
alias Archethic.Contracts.Contract.Constants
alias Archethic.Contracts.Contract.Trigger
alias Archethic.Contracts.ContractConditions, as: Conditions
alias Archethic.Contracts.ContractConstants, as: Constants

alias Archethic.Contracts.Interpreter

Expand All @@ -14,7 +13,7 @@ defmodule Archethic.Contracts.Contract do
alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.TransactionData

defstruct triggers: [],
defstruct triggers: %{},
conditions: %{
transaction: %Conditions{},
inherit: %Conditions{},
Expand All @@ -23,12 +22,15 @@ defmodule Archethic.Contracts.Contract do
constants: %Constants{},
next_transaction: %Transaction{data: %TransactionData{}}

@type trigger_type() :: :datetime | :interval | :transaction
@type trigger_type() ::
{:datetime, DateTime.t()} | {:interval, String.t()} | :transaction | :oracle
@type condition() :: :transaction | :inherit | :oracle
@type origin_family :: SharedSecrets.origin_family()

@type t() :: %__MODULE__{
triggers: list(Trigger.t()),
triggers: %{
trigger_type() => Macro.t()
},
conditions: %{
transaction: Conditions.t(),
inherit: Conditions.t(),
Expand All @@ -55,48 +57,24 @@ defmodule Archethic.Contracts.Contract do
@doc """
Add a trigger to the contract
"""
@spec add_trigger(t(), Trigger.type(), Keyword.t(), Macro.t()) :: t()
@spec add_trigger(map(), trigger_type(), Macro.t()) :: t()
def add_trigger(
contract = %__MODULE__{},
:datetime,
opts = [at: _datetime = %DateTime{}],
type,
actions
) do
do_add_trigger(contract, %Trigger{type: :datetime, opts: opts, actions: actions})
end

def add_trigger(
contract = %__MODULE__{},
:interval,
opts = [at: interval],
actions
)
when is_binary(interval) do
do_add_trigger(contract, %Trigger{type: :interval, opts: opts, actions: actions})
end

def add_trigger(contract = %__MODULE__{}, :transaction, _, actions) do
do_add_trigger(contract, %Trigger{type: :transaction, actions: actions})
end

def add_trigger(contract = %__MODULE__{}, :oracle, _, actions) do
do_add_trigger(contract, %Trigger{type: :oracle, actions: actions})
end

defp do_add_trigger(contract, trigger = %Trigger{}) do
Map.update!(contract, :triggers, &(&1 ++ [trigger]))
Map.update!(contract, :triggers, &Map.put(&1, type, actions))
end

@doc """
Add a condition to the contract
"""
@spec add_condition(t(), condition(), any()) :: t()
@spec add_condition(map(), condition(), Conditions.t()) :: t()
def add_condition(
contract = %__MODULE__{conditions: conditions},
contract = %__MODULE__{},
condition_name,
condition
)
when condition_name in [:transaction, :inherit, :oracle] do
%{contract | conditions: Map.put(conditions, condition_name, condition)}
conditions = %Conditions{}
) do
Map.update!(contract, :conditions, &Map.put(&1, condition_name, conditions))
end
end
5 changes: 4 additions & 1 deletion lib/archethic/contracts/contract/conditions.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
defmodule Archethic.Contracts.Contract.Conditions do
defmodule Archethic.Contracts.ContractConditions do
@moduledoc """
Represents the smart contract conditions
"""

defstruct [
:address,
:type,
:content,
:code,
Expand All @@ -20,6 +21,7 @@ defmodule Archethic.Contracts.Contract.Conditions do
alias Archethic.TransactionChain.Transaction

@type t :: %__MODULE__{
address: binary() | Macro.t() | nil,
type: Transaction.transaction_type() | nil,
content: binary() | Macro.t() | nil,
code: binary() | Macro.t() | nil,
Expand All @@ -33,6 +35,7 @@ defmodule Archethic.Contracts.Contract.Conditions do
}

def empty?(%__MODULE__{
address: nil,
type: nil,
content: nil,
code: nil,
Expand Down
67 changes: 65 additions & 2 deletions lib/archethic/contracts/contract/constants.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
defmodule Archethic.Contracts.Contract.Constants do
defmodule Archethic.Contracts.ContractConstants do
@moduledoc """
Represents the smart contract constants and bindings
"""

defstruct [:contract, :transaction]

@type t :: %__MODULE__{
contract: map(),
contract: map() | nil,
transaction: map() | nil
}

Expand Down Expand Up @@ -153,4 +153,67 @@ defmodule Archethic.Contracts.Contract.Constants do
previous_public_key: Map.get(constants, "previous_public_key")
}
end

@doc """
Stringify binary transaction values
"""
@spec stringify(map()) :: map()
def stringify(constants = %{}) do
%{
"address" => apply_not_nil(constants, "address", &Base.encode16/1),
"type" => Map.get(constants, "type"),
"content" => Map.get(constants, "content"),
"code" => Map.get(constants, "code"),
"authorized_keys" =>
apply_not_nil(constants, "authorized_keys", fn authorized_keys ->
authorized_keys
|> Enum.map(fn {public_key, encrypted_secret_key} ->
{Base.encode16(public_key), Base.encode16(encrypted_secret_key)}
end)
|> Enum.into(%{})
end),
"authorized_public_keys" =>
apply_not_nil(constants, "authorized_public_keys", fn public_keys ->
Enum.map(public_keys, &Base.encode16/1)
end),
"secrets" =>
apply_not_nil(constants, "secrets", fn secrets ->
Enum.map(secrets, &Base.encode16/1)
end),
"previous_public_key" => apply_not_nil(constants, "previous_public_key", &Base.encode16/1),
"recipients" =>
apply_not_nil(constants, "recipients", fn recipients ->
Enum.map(recipients, &Base.encode16/1)
end),
"uco_transfers" =>
apply_not_nil(constants, "uco_transfers", fn transfers ->
transfers
|> Enum.map(fn {to, amount} ->
{Base.encode16(to), amount}
end)
|> Enum.into(%{})
end),
"token_transfers" =>
apply_not_nil(constants, "token_transfers", fn transfers ->
transfers
|> Enum.map(fn {to, transfers} ->
{Base.encode16(to),
Enum.map(transfers, fn transfer ->
Map.update!(transfer, "token_address", &Base.encode16/1)
end)}
end)
end),
"timestamp" => Map.get(constants, "timestamp")
}
end

defp apply_not_nil(map, key, fun) do
case Map.get(map, key) do
nil ->
nil

val ->
fun.(val)
end
end
end
19 changes: 0 additions & 19 deletions lib/archethic/contracts/contract/trigger.ex

This file was deleted.

Loading