Skip to content

Commit

Permalink
Merge pull request #273 from kommitters/v0.18
Browse files Browse the repository at this point in the history
Release v0.18.0
  • Loading branch information
EdwinGuayacan committed Jan 31, 2023
2 parents a3bd870 + 4fd1285 commit 5482acb
Show file tree
Hide file tree
Showing 41 changed files with 930 additions and 33 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 0.18.0 (31.01.2023)

- Added [YAML reader](https://github.com/kommitters/kadena.ex/issues/253)
- Added [YAML Execution command](https://github.com/kommitters/kadena.ex/issues/208)

## 0.17.1 (25.01.2023)

- Updated README
Expand Down
101 changes: 99 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Add `kadena` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:kadena, "~> 0.17.1"}
{:kadena, "~> 0.18.0"}
]
end
```
Expand Down Expand Up @@ -288,6 +288,10 @@ Pact.ContCommand.new() |> Pact.ContCommand.set_pact_tx_hash(pact_tx_hash)

### Building an Execution Command

There are two ways to create an ExecCommand.

#### Using [attributes](#attributes) structures

```elixir
alias Kadena.Cryptography
alias Kadena.Pact
Expand Down Expand Up @@ -346,6 +350,99 @@ env_data = %{accounts_admin_keyset: [keypair.pub_key]}
}}
```

#### With a `YAML` file

YAML struct:

- `networkId`: [NetworkID](#networkid) value.
- `code`: there are two ways to set the code from the `YAML` file:
- `code`: [Code](#code) value.
- `codeFile`: The name of a `pact` file in the same directory as the `YAML` file.
- `data`: there are two ways to set the data from the `YAML` file:
- `data`: [EnvData](#envdata) value.
- `dataFile`: The name of a `json` file in the same directory as the `YAML` file.
- `nonce`: [Nonce](#nonce) value.
- `publicMeta`: [Metadata](#metadata) value.
- `keyPairs`: [KeyPairs](#keypairs) values.
- `signers`: [Signers](#signers) values.

The scheme below shows how to set the different values of an `ExecCommand`

```YAML
networkId:
code/codeFile:
data/dataFile:
nonce:
publicMeta:
creationTime:
chainId:
gasLimit:
gasPrice:
ttl:
sender:
keyPairs:
- public:
secret:
signers:
- publicKey:
scheme:
addr:
capsList:
- name:
args:
-
```
**Example**

YAML file:
```YAML
networkId: :testnet04
code: "(+ 1 2)"
data:
accounts_admin_keyset:
- 6ffea3fabe4e7fe6a89f88fc6d662c764ed1359fbc03a28afdac3935415347d7
nonce: 2023-01-01 00:00:00.000000 UTC
publicMeta:
creationTime: 1667249173
chainId: "0"
gasLimit: 2500
gasPrice: 0.01
ttl: 28800
sender: k:6ffea3fabe4e7fe6a89f88fc6d662c764ed1359fbc03a28afdac3935415347d7
keyPairs:
- public: 6ffea3fabe4e7fe6a89f88fc6d662c764ed1359fbc03a28afdac3935415347d7
secret: 99f7e1e8f2f334ae8374aa28bebdb997271a0e0a5e92c80be9609684a3d6f0d4
capsList:
name: coin.GAS
args:
- 6ffea3fabe4e7fe6a89f88fc6d662c764ed1359fbc03a28afdac3935415347d7

```

```elixir
alias Kadena.Pact.ExecCommand

"~/your_file_path"
|> ExecCommand.from_yaml()
|> ExecCommand.build()

{:ok,
%Kadena.Types.Command{
cmd:
"{\"meta\":{\"chainId\":\"0\",\"creationTime\":1667249173,\"gasLimit\":2500,\"gasPrice\":0.01,\"sender\":\"k:6ffea3fabe4e7fe6a89f88fc6d662c764ed1359fbc03a28afdac3935415347d7\",\"ttl\":28800},\"networkId\":\"testnet04\",\"nonce\":\"2023-01-01 00:00:00.000000 UTC\",\"payload\":{\"exec\":{\"code\":\"(+ 1 2)\",\"data\":{\"accounts_admin_keyset\":[\"6ffea3fabe4e7fe6a89f88fc6d662c764ed1359fbc03a28afdac3935415347d7\"]}}},\"signers\":[{\"addr\":null,\"clist\":[{\"args\":[\"6ffea3fabe4e7fe6a89f88fc6d662c764ed1359fbc03a28afdac3935415347d7\"],\"name\":\"coin.GAS\"}],\"pubKey\":\"6ffea3fabe4e7fe6a89f88fc6d662c764ed1359fbc03a28afdac3935415347d7\",\"scheme\":\"ED25519\"}]}",
hash: %Kadena.Types.PactTransactionHash{
hash: "ZOsqP9Wkfj5NnY9WS_XMnO9KfYv0GvK_8QMPTX6BfaA"
},
sigs: [
%Kadena.Types.Signature{
sig:
"5b0635c2376949103d8ce8243a1fd34a1a8964900d69eebb1ff4d38ffde437a317f887f864fa9c1b27a97e8d9e57eef8dd58b054edf30b4e6fe89d0208290f02"
}
]
}}

```

### Building a Continuation Command

```elixir
Expand Down Expand Up @@ -1742,7 +1839,7 @@ Chainweb.Peer.new()

## Roadmap

The latest updated branch to target a PR is `v0.18`
The latest updated branch to target a PR is `v0.19`

You can see a big picture of the roadmap here: [**ROADMAP**][roadmap]

Expand Down
2 changes: 1 addition & 1 deletion lib/pact/command/command.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ defmodule Kadena.Pact.Command do
@type exec_request :: ExecCommand.t()
@type error :: {:error, Keyword.t()}
@type cmd_request :: cont_request() | exec_request()

@type string_value :: String.t()
@type meta_data :: MetaData.t()
@type keypair :: KeyPair.t()
Expand All @@ -23,6 +22,7 @@ defmodule Kadena.Pact.Command do
@type hash :: String.t()

@callback new() :: cmd_request()
@callback from_yaml(path :: string_value()) :: cmd_request()
@callback set_network(cmd :: cmd_request(), network :: atom()) :: cmd_request()
@callback set_data(cmd :: cmd_request(), data :: map()) :: cmd_request()
@callback set_nonce(cmd :: cmd_request(), nonce :: string_value()) :: cmd_request()
Expand Down
3 changes: 3 additions & 0 deletions lib/pact/command/cont_command.ex
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ defmodule Kadena.Pact.ContCommand do

def new(_opts), do: %__MODULE__{}

@impl true
def from_yaml(_path), do: new()

@impl true
def set_network(%__MODULE__{} = cmd_request, network) do
case NetworkID.new(network) do
Expand Down
70 changes: 69 additions & 1 deletion lib/pact/command/exec_command.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Kadena.Pact.ExecCommand do

alias Kadena.Chainweb.Pact.CommandPayload
alias Kadena.Cryptography.{Sign, Utils}
alias Kadena.Pact.Command.Hash
alias Kadena.Pact.Command.{Hash, YamlReader}

alias Kadena.Types.{
Command,
Expand Down Expand Up @@ -87,6 +87,30 @@ defmodule Kadena.Pact.ExecCommand do

def new(_opts), do: %__MODULE__{}

@impl true
def from_yaml(path) when is_binary(path) do
with {:ok, map_result} <- YamlReader.read(path) do
network_id = Map.get(map_result, "networkId")
code = Map.get(map_result, "code", "")
data = Map.get(map_result, "data")
nonce = Map.get(map_result, "nonce", "")
meta_data = Map.get(map_result, "publicMeta", MetaData.new())
keypairs = Map.get(map_result, "keyPairs", [])
signers = Map.get(map_result, "signers", [])

%__MODULE__{}
|> process_metadata(meta_data)
|> process_keypairs(keypairs)
|> process_signers(signers)
|> set_network(network_id)
|> set_data(data)
|> set_code(code)
|> set_nonce(nonce)
end
end

def from_yaml(_path), do: {:error, [path: :invalid]}

@impl true
def set_network(%__MODULE__{} = cmd_request, network) do
case NetworkID.new(network) do
Expand Down Expand Up @@ -267,4 +291,48 @@ defmodule Kadena.Pact.ExecCommand do

defp build_signatures([%SignCommand{sig: sig} | rest], result),
do: build_signatures(rest, result ++ [Signature.new(sig)])

defp process_metadata(%__MODULE__{} = cmd_request, %MetaData{} = metadata),
do: set_metadata(cmd_request, metadata)

defp process_metadata(%__MODULE__{} = cmd_request, %{} = metadata) do
case MetaData.new(metadata) do
%MetaData{} = result -> %{cmd_request | meta_data: result}
{:error, reason} -> {:error, [meta_data: :invalid] ++ reason}
end
end

defp process_metadata(%__MODULE__{}, _metadata), do: {:error, [metadata: :invalid]}

defp process_keypairs(%__MODULE__{} = cmd_request, [%{} = keypair_data | rest]) do
case KeyPair.new(keypair_data) do
%KeyPair{} = result ->
cmd_request
|> add_keypair(result)
|> process_keypairs(rest)

{:error, reason} ->
{:error, [keypair: :invalid] ++ reason}
end
end

defp process_keypairs(%__MODULE__{} = cmd_request, []), do: cmd_request
defp process_keypairs(%__MODULE__{}, _keypair), do: {:error, [keypair: :invalid]}
defp process_keypairs({:error, reason}, _keypairs), do: {:error, reason}

defp process_signers(%__MODULE__{} = cmd_request, [%{} = signers_data | rest]) do
case Signer.new(signers_data) do
%Signer{} = result ->
cmd_request
|> add_signer(result)
|> process_signers(rest)

{:error, reason} ->
{:error, [signers: :invalid] ++ reason}
end
end

defp process_signers(%__MODULE__{} = cmd_request, []), do: cmd_request
defp process_signers({:error, reason}, _signers), do: {:error, reason}
defp process_signers(%__MODULE__{}, _signers), do: {:error, [signers: :invalid]}
end
72 changes: 72 additions & 0 deletions lib/pact/command/yaml_reader.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
defmodule Kadena.Pact.Command.YamlReader do
@moduledoc """
Reads YAML file to create a command.
"""

@type path :: String.t()
@type root_path :: String.t()
@type map_result :: map()
@type processed_map :: {:ok, map_result()} | {:error, Keyword.t() | struct()}

@spec read(path :: path()) :: processed_map()
def read(path) do
root_path = Path.dirname(path)

with {:ok, map_result} <- YamlElixir.read_from_file(path, atoms: true),
{:ok, map_result} <- process_code(map_result, root_path) do
process_data(map_result, root_path)
end
end

@spec process_code(map_result :: map_result(), root_path :: root_path()) :: processed_map()
defp process_code(%{"codeFile" => code_file} = map_result, root_path)
when is_binary(code_file) do
case String.match?(code_file, ~r{^.*\.(pact)$}) do
true ->
map_result
|> Map.put("code", File.read!(root_path <> "/#{code_file}"))
|> Map.delete("codeFile")
|> (&{:ok, &1}).()

false ->
{:error, [code_file: :not_a_pact_file]}
end
end

defp process_code(%{"codeFile" => nil} = map_result, _root_path),
do: {:ok, Map.delete(map_result, "codeFile")}

defp process_code(%{"code" => code} = map_result, _root_path) when is_binary(code),
do: {:ok, map_result}

defp process_code(%{"code" => nil} = map_result, _root_path),
do: {:ok, Map.delete(map_result, "code")}

defp process_code(map_result, _root_path), do: {:ok, map_result}

@spec process_data(map_result :: map_result(), root_path :: root_path()) :: processed_map()
defp process_data(%{"dataFile" => data_file} = yaml, root_path) when is_binary(data_file) do
case String.match?(data_file, ~r{^.*\.(json)$}) do
true ->
(root_path <> "/#{data_file}")
|> File.read!()
|> Jason.decode!()
|> (&Map.put(yaml, "data", &1)).()
|> Map.delete("dataFile")
|> (&{:ok, &1}).()

false ->
{:error, [data_file: :not_a_json_file]}
end
end

defp process_data(%{"dataFile" => nil} = map_result, _root_path),
do: {:ok, Map.delete(map_result, "dataFile")}

defp process_data(%{"data" => %{}} = map_result, _root_path), do: {:ok, map_result}

defp process_data(%{"data" => nil} = map_result, _root_path),
do: {:ok, Map.delete(map_result, "data")}

defp process_data(map_result, _root_path), do: {:ok, map_result}
end
4 changes: 2 additions & 2 deletions lib/types/cap.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ defmodule Kadena.Types.Cap do
end

def new(args) when is_map(args) do
name = Map.get(args, :name)
args = Map.get(args, :args)
name = Map.get(args, "name")
args = Map.get(args, "args")

with {:ok, name} <- validate_name(name),
{:ok, args} <- validate_args(args) do
Expand Down
22 changes: 21 additions & 1 deletion lib/types/keypair.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,19 @@ defmodule Kadena.Types.KeyPair do
end
end

def new(_args), do: {:error, [args: :not_a_list]}
def new(args) when is_map(args) do
pub_key_arg = Map.get(args, "public")
secret_key_arg = Map.get(args, "secret")
clist_arg = Map.get(args, "capsList")

with {:ok, pub_key} <- validate_key({:pub_key, pub_key_arg}),
{:ok, secret_key} <- validate_key({:secret_key, secret_key_arg}),
{:ok, clist} <- create_clist({:clist, clist_arg}) do
%__MODULE__{pub_key: pub_key, secret_key: secret_key, clist: clist}
end
end

def new(_args), do: {:error, [args: :invalid]}

@spec add_caps(keypair :: t(), caps :: clist()) :: validated_keypair()
def add_caps(%__MODULE__{} = keypair, caps) do
Expand All @@ -49,4 +61,12 @@ defmodule Kadena.Types.KeyPair do
defp validate_caps_list({_arg, nil}), do: {:ok, nil}
defp validate_caps_list({_arg, [%Cap{} | _tail] = clist}), do: {:ok, clist}
defp validate_caps_list({arg, _clist}), do: {:error, [{arg, :invalid}]}

defp create_clist({arg, caps}) when is_list(caps) do
caps
|> Enum.map(fn cap -> Cap.new(cap) end)
|> (&validate_caps_list({arg, &1})).()
end

defp create_clist({_arg, nil}), do: {:ok, nil}
end
Loading

0 comments on commit 5482acb

Please sign in to comment.