Skip to content

Commit

Permalink
SC: Add TransactionStatements functions to work with lists (#849)
Browse files Browse the repository at this point in the history
* SC: Add transactionstatements functions to work with lists

* add doctest

* Whitelist keywords and blacklist some keyword in functions

* blacklist even more strict

* blacklist was not allowing variables in keyword list
  • Loading branch information
bchamagne committed Feb 1, 2023
1 parent 4cc5bae commit 7ea16d9
Show file tree
Hide file tree
Showing 3 changed files with 330 additions and 76 deletions.
129 changes: 53 additions & 76 deletions lib/archethic/contracts/interpreter/action.ex
Original file line number Diff line number Diff line change
Expand Up @@ -146,132 +146,109 @@ defmodule Archethic.Contracts.ActionInterpreter do
{node, acc}
end

# Whitelist the add_uco_transfer function parameters
# Blacklist the add_uco_transfer argument list
defp prewalk(
node = {{:atom, "to"}, address},
acc = {:ok, %{scope: {:function, "add_uco_transfer", {:actions, _}}}}
_acc = {:ok, %{scope: {:function, "add_uco_transfer", {:actions, _}}}}
)
when is_binary(address) do
{node, acc}
end

defp prewalk(
node = {{:atom, "to"}, {{:atom, _}, _, _}},
acc = {:ok, %{scope: {:function, "add_uco_transfer", {:actions, _}}}}
) do
{node, acc}
when not is_tuple(address) and not is_binary(address) do
throw({:error, "invalid add_uco_transfer arguments", node})
end

defp prewalk(
node = {{:atom, "amount"}, amount},
acc = {:ok, %{scope: {:function, "add_uco_transfer", {:actions, _}}}}
_acc = {:ok, %{scope: {:function, "add_uco_transfer", {:actions, _}}}}
)
when is_integer(amount) and amount > 0 do
{node, acc}
when (not is_tuple(amount) and not is_integer(amount)) or amount <= 0 do
throw({:error, "invalid add_uco_transfer arguments", node})
end

defp prewalk(
node = {{:atom, "amount"}, {{:atom, _}, _, _}},
acc = {:ok, %{scope: {:function, "add_uco_transfer", {:actions, _}}}}
) do
{node, acc}
node = {{:atom, atom}, _},
_acc = {:ok, %{scope: {:function, "add_uco_transfer", {:actions, _}}}}
)
when atom != "to" and atom != "amount" do
throw({:error, "invalid add_uco_transfer arguments", node})
end

# Whitelist the add_token_transfer argument list
# Blacklist the add_token_transfer argument list
defp prewalk(
node = {{:atom, "to"}, address},
acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
_acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
)
when is_binary(address) do
{node, acc}
end

defp prewalk(
node = {{:atom, "to"}, {{:atom, _}, _, _}},
acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
) do
{node, acc}
when not is_tuple(address) and not is_binary(address) do
throw({:error, "invalid add_token_transfer arguments", node})
end

defp prewalk(
node = {{:atom, "amount"}, amount},
acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
_acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
)
when is_integer(amount) and amount > 0 do
{node, acc}
end

defp prewalk(
node = {{:atom, "amount"}, {{:atom, _}, _, _}},
acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
) do
{node, acc}
when (not is_tuple(amount) and not is_integer(amount)) or amount <= 0 do
throw({:error, "invalid add_token_transfer arguments", node})
end

defp prewalk(
node = {{:atom, "token_address"}, token_address},
acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
node = {{:atom, "token_address"}, address},
_acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
)
when is_binary(token_address) do
{node, acc}
when not is_tuple(address) and not is_binary(address) do
throw({:error, "invalid add_token_transfer arguments", node})
end

defp prewalk(
node = {{:atom, "token_address"}, {{:atom, _}, _, _}},
acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
) do
{node, acc}
end

defp prewalk(
node = {{:atom, "token_id"}, token_id},
acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
node = {{:atom, "token_id"}, id},
_acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
)
when is_integer(token_id) and token_id >= 0 do
{node, acc}
when (not is_tuple(id) and not is_integer(id)) or id < 0 do
throw({:error, "invalid add_token_transfer arguments", node})
end

defp prewalk(
node = {{:atom, "token_id"}, {{:atom, _}, _, _}},
acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
) do
{node, acc}
node = {{:atom, atom}, _},
_acc = {:ok, %{scope: {:function, "add_token_transfer", {:actions, _}}}}
)
when atom != "to" and atom != "amount" and atom != "token_address" and atom != "token_id" do
throw({:error, "invalid add_token_transfer arguments", node})
end

# Whitelist the add_ownership argument list
# Blacklist the add_ownership argument list
defp prewalk(
node = {{:atom, "secret"}, secret},
acc = {:ok, %{scope: {:function, "add_ownership", {:actions, _}}}}
_acc = {:ok, %{scope: {:function, "add_ownership", {:actions, _}}}}
)
when is_binary(secret) do
{node, acc}
when not is_tuple(secret) and not is_binary(secret) do
throw({:error, "invalid add_ownership arguments", node})
end

defp prewalk(
node = {{:atom, "secret"}, {{:atom, _}, _, _}},
acc = {:ok, %{scope: {:function, "add_ownership", {:actions, _}}}}
) do
{node, acc}
node = {{:atom, "secret_key"}, secret_key},
_acc = {:ok, %{scope: {:function, "add_ownership", {:actions, _}}}}
)
when not is_tuple(secret_key) and not is_binary(secret_key) do
throw({:error, "invalid add_ownership arguments", node})
end

defp prewalk(
node = {{:atom, "secret_key"}, _secret_key},
acc = {:ok, %{scope: {:function, "add_ownership", {:actions, _}}}}
) do
{node, acc}
node = {{:atom, "authorized_public_keys"}, authorized_public_keys},
_acc = {:ok, %{scope: {:function, "add_ownership", {:actions, _}}}}
)
when not is_tuple(authorized_public_keys) and not is_list(authorized_public_keys) do
throw({:error, "invalid add_ownership arguments", node})
end

defp prewalk(
node = {{:atom, "authorized_public_keys"}, authorized_public_keys},
acc = {:ok, %{scope: {:function, "add_ownership", {:actions, _}}}}
node = {{:atom, atom}, _},
_acc = {:ok, %{scope: {:function, "add_ownership", {:actions, _}}}}
)
when is_list(authorized_public_keys) do
{node, acc}
when atom != "secret" and atom != "secret_key" and atom != "authorized_public_keys" do
throw({:error, "invalid add_ownership arguments", node})
end

# Whitelist the keywords
defp prewalk(
node = {{:atom, "authorized_public_keys"}, {{:atom, _, _}}},
acc = {:ok, %{scope: {:function, "add_ownership", {:actions, _}}}}
node = {{:atom, _}, _},
acc = {:ok, _}
) do
{node, acc}
end
Expand Down
142 changes: 142 additions & 0 deletions lib/archethic/contracts/interpreter/transaction_statements.ex
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,146 @@ defmodule Archethic.Contracts.Interpreter.TransactionStatements do
&[recipient_address | &1]
)
end

@doc """
Add multiple recipients
## Examples
iex> address1 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>
iex> address2 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>
iex> TransactionStatements.add_recipients(%Transaction{data: %TransactionData{recipients: [address1]}}, [address2])
%Transaction{
data: %TransactionData{
recipients: [address2, address1]
}
}
"""
@spec add_recipients(Transaction.t(), list(binary())) :: Transaction.t()
def add_recipients(tx = %Transaction{}, args) when is_list(args) do
Enum.reduce(args, tx, &add_recipient(&2, &1))
end

@doc """
Add multiple ownerships
## Examples
iex> {pub_key1, _} = Archethic.Crypto.generate_deterministic_keypair("seed")
iex> {pub_key2, _} = Archethic.Crypto.generate_deterministic_keypair("seed2")
iex> %Transaction{
...> data: %TransactionData{
...> ownerships: [
...> %Ownership{
...> authorized_keys: %{
...> ^pub_key2 => _
...> },
...> secret: "ENCODED_SECRET2"
...> },
...> %Ownership{
...> authorized_keys: %{
...> ^pub_key1 => _
...> },
...> secret: "ENCODED_SECRET1"
...> }
...> ]
...> }
...> } = TransactionStatements.add_ownerships(%Transaction{data: %TransactionData{}}, [[
...> {"secret", "ENCODED_SECRET1"},
...> {"secret_key", :crypto.strong_rand_bytes(32)},
...> {"authorized_public_keys", [pub_key1]}
...> ],
...> [
...> {"secret", "ENCODED_SECRET2"},
...> {"secret_key", :crypto.strong_rand_bytes(32)},
...> {"authorized_public_keys", [pub_key2]}
...> ]
...> ])
"""
@spec add_ownerships(Transaction.t(), list(list())) :: Transaction.t()
def add_ownerships(tx = %Transaction{}, args) when is_list(args) do
Enum.reduce(args, tx, &add_ownership(&2, &1))
end

@doc """
Add multiple token transfers
## Examples
iex> address1 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>
iex> address2 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>
iex> address3 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>
iex> address4 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>
iex> %Transaction{
...> data: %TransactionData{
...> ledger: %Ledger{
...> token: %TokenLedger{
...> transfers: [
...> %TokenTransfer{
...> to: ^address3,
...> amount: 3,
...> token_address: ^address4,
...> token_id: 4
...> },
...> %TokenTransfer{
...> to: ^address1,
...> amount: 1,
...> token_address: ^address2,
...> token_id: 2
...> }
...> ]
...> }
...> }
...> }
...> } = TransactionStatements.add_token_transfers(%Transaction{data: %TransactionData{}}, [[
...> {"to", address1},
...> {"amount", 1},
...> {"token_address", address2},
...> {"token_id", 2}
...> ],
...> [
...> {"to", address3},
...> {"amount", 3},
...> {"token_address", address4},
...> {"token_id", 4}
...> ]])
"""
@spec add_token_transfers(Transaction.t(), list(list())) :: Transaction.t()
def add_token_transfers(tx = %Transaction{}, args) when is_list(args) do
Enum.reduce(args, tx, &add_token_transfer(&2, &1))
end

@doc """
Add multiple UCO transfers
## Examples
iex> address1 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>
iex> address2 = <<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>
iex> %Transaction{
...> data: %TransactionData{
...> ledger: %Ledger{
...> uco: %UCOLedger{
...> transfers: [
...> %UCOTransfer{
...> to: ^address2,
...> amount: 2
...> },
...> %UCOTransfer{
...> to: ^address1,
...> amount: 1
...> }
...> ]
...> }
...> }
...> }
...> } = TransactionStatements.add_uco_transfers(%Transaction{data: %TransactionData{}}, [
...> [{"to", address1}, {"amount", 1}],
...> [{"to", address2}, {"amount", 2}]
...> ])
"""
@spec add_uco_transfers(Transaction.t(), list(list())) :: Transaction.t()
def add_uco_transfers(tx = %Transaction{}, args) when is_list(args) do
Enum.reduce(args, tx, &add_uco_transfer(&2, &1))
end
end
Loading

0 comments on commit 7ea16d9

Please sign in to comment.