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

Add validation of smart contract calls #802

Merged
Merged
35 changes: 34 additions & 1 deletion lib/archethic.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@ defmodule Archethic do
alias Archethic.SharedSecrets
alias __MODULE__.{Account, BeaconChain, Crypto, Election, P2P, P2P.Node, P2P.Message}
alias __MODULE__.{SelfRepair, TransactionChain}
alias __MODULE__.Contracts.Interpreter
alias __MODULE__.Contracts.Contract

alias Message.{NewTransaction, NotFound, StartMining}
alias Message.{Balance, GetBalance, GetTransactionSummary}
alias Message.{StartMining, Ok, TransactionSummaryMessage}

alias TransactionChain.{Transaction, TransactionInput, TransactionSummary}
alias TransactionChain.{
Transaction,
TransactionInput,
TransactionSummary
}

require Logger

Expand Down Expand Up @@ -302,6 +308,33 @@ defmodule Archethic do
end
end

@doc """
Parse the given transaction and return a contract if successful
"""
@spec parse_contract(Transaction.t()) :: {:ok, Contract.t()} | {:error, String.t()}
defdelegate parse_contract(contract_tx),
to: Interpreter,
as: :parse_transaction

@doc """
Execute the contract in the given transaction.
We assume the contract is parse-able.
"""
@spec execute_contract(
Contract.trigger_type(),
Contract.t(),
nil | Transaction.t()
) ::
{:ok, nil | Transaction.t()}
| {:error,
:invalid_triggers_execution
| :invalid_transaction_constraints
| :invalid_oracle_constraints
| :invalid_inherit_constraints}
defdelegate execute_contract(trigger_type, contract, maybe_tx),
to: Interpreter,
as: :execute

@doc """
Retrieve the number of transaction in a transaction chain from the closest nodes
"""
Expand Down
129 changes: 6 additions & 123 deletions lib/archethic/contracts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ defmodule Archethic.Contracts do
"""

alias __MODULE__.Contract
alias __MODULE__.ContractConditions, as: Conditions
alias __MODULE__.ContractConstants, as: Constants
alias __MODULE__.Interpreter
alias __MODULE__.Loader
Expand All @@ -18,134 +17,18 @@ defmodule Archethic.Contracts do
alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.TransactionData

alias Archethic.Contracts.ContractConstants, as: Constants

require Logger

@extended_mode? Mix.env() != :prod

@doc ~S"""
Parse a smart contract code and return its representation

## Examples

iex> "
...> condition inherit: [
...> content: regex_match?(\"^(Mr.X: ){1}([0-9]+), (Mr.Y: ){1}([0-9])+$\"),
...> origin_family: biometric
...> ]
...>
...> actions triggered_by: datetime, at: 1601039923 do
...> set_type hosting
...> set_content \"Mr.X: 10, Mr.Y: 8\"
...> end
...> "
...> |> Contracts.parse()
{
:ok,
%Archethic.Contracts.Contract{
conditions: %{
inherit: %Archethic.Contracts.ContractConditions{
address: nil,
authorized_keys: nil,
code: nil,
content: {
:==,
[line: 2],
[true, {{:., [line: 2], [{:__aliases__, [alias: Archethic.Contracts.Interpreter.Legacy.Library], [:Library]}, :regex_match?]}, [line: 2], [{:get_in, [line: 2], [{:scope, [line: 2], nil}, ["next", "content"]]}, "^(Mr.X: ){1}([0-9]+), (Mr.Y: ){1}([0-9])+$"]}]
},
origin_family: :biometric,
previous_public_key: nil,
secrets: nil,
timestamp: nil,
token_transfers: nil,
type: nil,
uco_transfers: nil
},
oracle: %Archethic.Contracts.ContractConditions{address: nil, authorized_keys: nil, code: nil, content: nil, origin_family: :all, previous_public_key: nil, secrets: nil, timestamp: nil, token_transfers: nil, type: nil, uco_transfers: nil},
transaction: %Archethic.Contracts.ContractConditions{address: nil, authorized_keys: nil, code: nil, content: nil, origin_family: :all, previous_public_key: nil, secrets: nil, timestamp: nil, token_transfers: nil, type: nil, uco_transfers: nil}
},
constants: %Archethic.Contracts.ContractConstants{contract: nil, transaction: nil},
next_transaction: %Archethic.TransactionChain.Transaction{
address: nil,
cross_validation_stamps: [],
data: %Archethic.TransactionChain.TransactionData{
code: "",
content: "",
ledger: %Archethic.TransactionChain.TransactionData.Ledger{token: %Archethic.TransactionChain.TransactionData.TokenLedger{transfers: []}, uco: %Archethic.TransactionChain.TransactionData.UCOLedger{transfers: []}},
ownerships: [],
recipients: []
},
origin_signature: nil,
previous_public_key: nil,
previous_signature: nil,
type: nil,
validation_stamp: nil,
version: 1
},
triggers: %{
{:datetime, ~U[2020-09-25 13:18:43Z]} => {
:__block__,
[],
[
{
:__block__,
[],
[
{
:=,
[line: 7],
[{:scope, [line: 7], nil}, {:update_in, [line: 7], [{:scope, [line: 7], nil}, ["next_transaction"], {:&, [line: 7], [{{:., [line: 7], [{:__aliases__, [alias: Archethic.Contracts.Interpreter.Legacy.TransactionStatements], [:TransactionStatements]}, :set_type]}, [line: 7], [{:&, [line: 7], [1]}, "hosting"]}]}]}]
},
{{:., [], [{:__aliases__, [alias: false], [:Function]}, :identity]}, [], [{:scope, [], nil}]}
]
},
{
:__block__,
[],
[
{
:=,
[line: 8],
[{:scope, [line: 8], nil}, {:update_in, [line: 8], [{:scope, [line: 8], nil}, ["next_transaction"], {:&, [line: 8], [{{:., [line: 8], [{:__aliases__, [alias: Archethic.Contracts.Interpreter.Legacy.TransactionStatements], [:TransactionStatements]}, :set_content]}, [line: 8], [{:&, [line: 8], [1]}, "Mr.X: 10, Mr.Y: 8"]}]}]}]
},
{{:., [], [{:__aliases__, [alias: false], [:Function]}, :identity]}, [], [{:scope, [], nil}]}
]
}
]
}
},
version: 0
}
}
@doc """
Parse a smart contract code and return a contract struct
"""
@spec parse(binary()) :: {:ok, Contract.t()} | {:error, binary()}
def parse(contract_code) when is_binary(contract_code) do
start = System.monotonic_time()

case Interpreter.parse(contract_code) do
{:ok,
contract = %Contract{
triggers: triggers,
conditions: %{transaction: transaction_conditions, oracle: oracle_conditions}
}} ->
:telemetry.execute([:archethic, :contract, :parsing], %{
duration: System.monotonic_time() - start
})

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

Map.has_key?(triggers, :oracle) and Conditions.empty?(oracle_conditions) ->
{:error, "missing oracle conditions"}

true ->
{:ok, contract}
end

{:error, _} = e ->
e
end
end
defdelegate parse(contract_code),
to: Interpreter

@doc """
Same a `parse/1` but raise if the contract is not valid
Expand Down
Loading