Skip to content

Commit

Permalink
update tx code to hold json
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelmanzanera committed Sep 11, 2024
1 parent a8a9432 commit 83206bb
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 68 deletions.
50 changes: 26 additions & 24 deletions lib/archethic/contracts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,13 @@ defmodule Archethic.Contracts do
Parse a smart contract code and return a contract struct
"""
@spec parse(binary()) :: {:ok, Contract.t() | WasmContract.t()} | {:error, String.t()}
def parse(contract_code, tx_content \\ "") do
if String.printable?(contract_code) do
Interpreter.parse(contract_code)
else
with {:ok, json} <- Jason.decode(tx_content),
{:ok, module} <- WasmModule.parse(contract_code, WasmSpec.from_manifest(json)) do
{:ok, %WasmContract{module: module}}
else
{:error, reason} ->
{:error, "#{inspect(reason)}"}
end
def parse(contract_code) do
case Jason.decode(contract_code) do
{:ok, contract_json} ->
WasmContract.parse(contract_json)

_ ->
Interpreter.parse(contract_code)
end
end

Expand Down Expand Up @@ -241,21 +237,27 @@ defmodule Archethic.Contracts do
end)

if trigger != nil do
argument =
case maybe_recipient do
%Recipient{args: args = [_ | _]} ->
case Enum.at(args, 0) do
arg when is_map(arg) ->
arg

argument = case maybe_recipient do
%Recipient{args: args = [_|_]} ->
case Enum.at(args, 0) do
arg when is_map(arg) -> arg
_ ->
%WasmSpec.Trigger{input: input} = trigger
_ ->
%WasmSpec.Trigger{input: input} = trigger

input
|> Map.keys()
|> Enum.with_index()
|> Enum.reduce(%{}, fn {key, index}, acc -> Map.put(acc, key, Enum.at(args, index)) end)
end
_ -> nil
end
input
|> Map.keys()
|> Enum.with_index()
|> Enum.reduce(%{}, fn {key, index}, acc ->
Map.put(acc, key, Enum.at(args, index))
end)
end

_ ->
nil
end

WasmModule.execute(
module,
Expand Down
48 changes: 35 additions & 13 deletions lib/archethic/contracts/wasm/contract.ex
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,48 @@ defmodule Archethic.Contracts.WasmContract do
end

@doc """
Create a contract from a transaction
Parse smart contract json block and return a contract struct
"""
@spec from_transaction(Transaction.t()) :: {:ok, t()} | {:error, String.t()}
def from_transaction(tx = %Transaction{data: %TransactionData{code: code, content: content}}) do
with {:ok, manifest_json} <- Jason.decode(content),
spec = WasmSpec.from_manifest(manifest_json),
{:ok, module} <- WasmModule.parse(code, spec) do
contract = %__MODULE__{
module: module,
state: get_state_from_tx(tx),
transaction: tx
}

{:ok, contract}
@spec parse(contract_json :: binary() | map()) :: {:ok, t()} | {:error, String.t()}
def parse(contract_json) when is_binary(contract_json) do
case Jason.decode(contract_json) do
{:ok, contract_json} -> parse(contract_json)
{:error, reason} -> {:error, "#{inspect(reason)}"}
end
end

def parse(%{"manifest" => manifest, "bytecode" => bytecode}) do
with {:ok, bytes} <- Base.decode16(bytecode, case: :mixed),
spec = WasmSpec.from_manifest(manifest),
uncompressed_bytes = :zlib.unzip(bytes),
{:ok, module} <- WasmModule.parse(uncompressed_bytes, spec) do
{:ok,
%__MODULE__{
module: module
}}
else
:error ->
{:error, "Invalid Smart Contract JSON"}

{:error, reason} ->
{:error, "#{inspect(reason)}"}
end
end

@doc """
Create a contract from a transaction
"""
@spec from_transaction(Transaction.t()) :: {:ok, t()} | {:error, String.t()}
def from_transaction(tx = %Transaction{data: %TransactionData{code: code}}) do
case parse(code) do
{:ok, contract} ->
{:ok, %{contract | state: get_state_from_tx(tx), transaction: tx}}

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

defp get_state_from_tx(%Transaction{
validation_stamp: %ValidationStamp{
ledger_operations: %LedgerOperations{unspent_outputs: utxos}
Expand Down
21 changes: 15 additions & 6 deletions lib/archethic/contracts/wasm/spec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,22 @@ defmodule Archethic.Contracts.WasmSpec do
version = Map.get(manifest, "version", 1)
upgrade_opts = Map.get(manifest, "upgrade_opts")

Enum.reduce(functions, %__MODULE__{version: version, upgrade_opts: upgrade_opts, triggers: [], public_functions: []}, fn
{name, function_abi = %{"type" => "action"}}, acc ->
Map.update!(acc, :triggers, &[Trigger.cast(name, function_abi) | &1])
Enum.reduce(
functions,
%__MODULE__{
version: version,
upgrade_opts: upgrade_opts,
triggers: [],
public_functions: []
},
fn
{name, function_abi = %{"type" => "action"}}, acc ->
Map.update!(acc, :triggers, &[Trigger.cast(name, function_abi) | &1])

{name, function_abi = %{"type" => "publicFunction"}}, acc ->
Map.update!(acc, :public_functions, &[Function.cast(name, function_abi) | &1])
end)
{name, function_abi = %{"type" => "publicFunction"}}, acc ->
Map.update!(acc, :public_functions, &[Function.cast(name, function_abi) | &1])
end
)
end

@doc """
Expand Down
8 changes: 4 additions & 4 deletions lib/archethic/mining/pending_transaction_validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,10 @@ defmodule Archethic.Mining.PendingTransactionValidation do
defp validate_contract(%Transaction{data: %TransactionData{code: ""}}), do: :ok

defp validate_contract(%Transaction{
data: %TransactionData{code: code, content: content, ownerships: ownerships}
data: %TransactionData{code: code, ownerships: ownerships}
}) do
with :ok <- validate_code_size(code),
{:ok, contract} <- parse_contract(code, content) do
{:ok, contract} <- parse_contract(code) do
validate_contract_ownership(contract, ownerships)
end
end
Expand All @@ -155,8 +155,8 @@ defmodule Archethic.Mining.PendingTransactionValidation do
else: {:error, "Invalid transaction, code exceed max size"}
end

defp parse_contract(code, content) do
case Contracts.parse(code, content) do
defp parse_contract(code) do
case Contracts.parse(code) do
{:ok, contract} -> {:ok, contract}
{:error, reason} -> {:error, "Smart contract invalid #{inspect(reason)}"}
end
Expand Down
6 changes: 2 additions & 4 deletions lib/archethic/mining/proof_of_work.ex
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,9 @@ defmodule Archethic.Mining.ProofOfWork do
Smart contract code can defined which family to use (like security level)
"""
@spec list_origin_public_keys_candidates(Transaction.t()) :: list(Crypto.key())
def list_origin_public_keys_candidates(
tx = %Transaction{data: %TransactionData{code: code, content: content}}
)
def list_origin_public_keys_candidates(tx = %Transaction{data: %TransactionData{code: code}})
when code != "" do
case Contracts.parse(code, content) do
case Contracts.parse(code) do
{:ok,
%Contract{
conditions: %{inherit: %Conditions{subjects: %ConditionsSubjects{origin_family: family}}}
Expand Down
10 changes: 7 additions & 3 deletions lib/archethic/transaction_chain/transaction/data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ defmodule Archethic.TransactionChain.TransactionData do
:zlib.unzip(code)
end

@spec code_size_valid?(String.t()) :: bool()
def code_size_valid?(code) do
compress_code(code) |> byte_size() < @code_max_size
@spec code_size_valid?(code :: binary(), compressed :: boolean()) :: boolean()
def code_size_valid?(code, compressed? \\ true) do
if compressed? do
code |> byte_size() < @code_max_size
else
compress_code(code) |> byte_size() < @code_max_size
end
end

@doc """
Expand Down
14 changes: 11 additions & 3 deletions lib/archethic_web/api/jsonrpc/schemas/transaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ defmodule ArchethicWeb.API.JsonRPC.TransactionSchema do
:encrypted_secret_key,
:origin_signature,
:previous_public_key,
:previous_signature,
:code
:previous_signature
]

@doc """
Expand Down Expand Up @@ -52,7 +51,16 @@ defmodule ArchethicWeb.API.JsonRPC.TransactionSchema do
:ok

code ->
if TransactionData.code_size_valid?(code) do
valid_size? =
case Jason.decode(code) do
{:ok, %{"bytecode" => bytecode}} ->
TransactionData.code_size_valid?(Base.decode16!(bytecode, case: :mixed))

_ ->
TransactionData.code_size_valid?(code)
end

if valid_size? do
:ok
else
{:error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,11 @@
<div></div>
<% else %>
<div x-show="show" class="is-relative">
<%= if String.printable?(@transaction.data.code) do %>
<pre id="copy-code" class="language-elixir" phx-hook="CodeViewer"><%= @transaction.data.code %></pre>
<% else %>
<span id="copy-code">
<%= Base.encode16(@transaction.data.code) %>
</span>
<%= case Jason.decode(@transaction.data.code) do %>
<% {:ok, json} -> %>
<pre id="copy-code" class="language-json" phx-hook="CodeViewer"><%= Jason.encode!(json, pretty: true) %></pre>
<% _ -> %>
<pre id="copy-code" class="language-elixir" phx-hook="CodeViewer"><%= @transaction.data.code %></pre>
<% end %>
</div>
<% end %>
Expand Down
50 changes: 45 additions & 5 deletions test/archethic/contracts_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,15 @@ defmodule Archethic.ContractsTest do
test "should execute trigger for wasm contract" do
bytes = File.read!("test/support/contract.wasm")
manifest = File.read!("test/support/contract_manifest.json")
contract_tx = ContractFactory.create_valid_contract_tx(bytes, content: manifest)

contract_tx =
ContractFactory.create_valid_contract_tx(
Jason.encode!(%{
manifest: Jason.decode!(manifest),
bytecode: Base.encode16(:zlib.zip(bytes))
})
)

contract = WasmContract.from_transaction!(contract_tx)
incoming_tx = TransactionFactory.create_valid_transaction()

Expand All @@ -841,7 +849,15 @@ defmodule Archethic.ContractsTest do
test "should execute trigger for wasm contract with no object rpc call params" do
bytes = File.read!("test/support/contract.wasm")
manifest = File.read!("test/support/contract_manifest.json")
contract_tx = ContractFactory.create_valid_contract_tx(bytes, content: manifest)

contract_tx =
ContractFactory.create_valid_contract_tx(
Jason.encode!(%{
manifest: Jason.decode!(manifest),
bytecode: Base.encode16(:zlib.zip(bytes))
})
)

contract = WasmContract.from_transaction!(contract_tx)
incoming_tx = TransactionFactory.create_valid_transaction()

Expand Down Expand Up @@ -872,7 +888,15 @@ defmodule Archethic.ContractsTest do

bytes = File.read!("test/support/contract.wasm")
manifest = File.read!("test/support/contract_manifest.json")
contract_tx = ContractFactory.create_valid_contract_tx(bytes, content: manifest)

contract_tx =
ContractFactory.create_valid_contract_tx(
Jason.encode!(%{
manifest: Jason.decode!(manifest),
bytecode: Base.encode16(:zlib.zip(bytes))
})
)

contract = WasmContract.from_transaction!(contract_tx)
incoming_tx = TransactionFactory.create_valid_transaction()

Expand Down Expand Up @@ -1027,7 +1051,15 @@ defmodule Archethic.ContractsTest do
test "should execute function for wasm contract" do
bytes = File.read!("test/support/contract.wasm")
manifest = File.read!("test/support/contract_manifest.json")
contract_tx = ContractFactory.create_valid_contract_tx(bytes, content: manifest)

contract_tx =
ContractFactory.create_valid_contract_tx(
Jason.encode!(%{
manifest: Jason.decode!(manifest),
bytecode: Base.encode16(:zlib.zip(bytes))
})
)

contract = WasmContract.from_transaction!(contract_tx)

assert {:ok, 24, _} =
Expand Down Expand Up @@ -1065,7 +1097,15 @@ defmodule Archethic.ContractsTest do
test "should execute function for wasm contract with inputs" do
bytes = File.read!("test/support/contract.wasm")
manifest = File.read!("test/support/contract_manifest.json")
contract_tx = ContractFactory.create_valid_contract_tx(bytes, content: manifest)

contract_tx =
ContractFactory.create_valid_contract_tx(
Jason.encode!(%{
manifest: Jason.decode!(manifest),
bytecode: Base.encode16(:zlib.zip(bytes))
})
)

contract = WasmContract.from_transaction!(contract_tx)

assert {:ok, "UCO: 500000000", _} =
Expand Down

0 comments on commit 83206bb

Please sign in to comment.