From 6cf92958966b6b610aee341ba55fe0bc94f1854d Mon Sep 17 00:00:00 2001 From: hswick Date: Sun, 15 Apr 2018 02:28:50 -0500 Subject: [PATCH] Added bytes to string method --- lib/exw3.ex | 170 +++++++++++++++++++++++++++------------------ mix.exs | 4 +- mix.lock | 1 + test/exw3_test.exs | 31 ++++++--- 4 files changed, 129 insertions(+), 77 deletions(-) diff --git a/lib/exw3.ex b/lib/exw3.ex index 0322e5e..10dc99c 100644 --- a/lib/exw3.ex +++ b/lib/exw3.ex @@ -1,78 +1,96 @@ defmodule ExW3 do - - def reformat_abi abi do - Map.new Enum.map(abi, fn x -> {x["name"], x} end) + def reformat_abi(abi) do + Map.new(Enum.map(abi, fn x -> {x["name"], x} end)) end - def load_abi file_path do - file = File.read Path.join(System.cwd, file_path) + def load_abi(file_path) do + file = File.read(Path.join(System.cwd(), file_path)) + case file do - {:ok, abi} -> reformat_abi Poison.Parser.parse! abi + {:ok, abi} -> reformat_abi(Poison.Parser.parse!(abi)) err -> err end end - def decode_output abi, name, output do - {:ok, trim_output} = String.slice(output, 2..String.length(output)) |> Base.decode16(case: :lower) - output_types = Enum.map abi[name]["outputs"], fn x -> x["type"] end - output_signature = Enum.join [name, "(", Enum.join(output_types, ")"), ")"] + def decode_output(abi, name, output) do + {:ok, trim_output} = + String.slice(output, 2..String.length(output)) |> Base.decode16(case: :lower) + + output_types = Enum.map(abi[name]["outputs"], fn x -> x["type"] end) + output_signature = Enum.join([name, "(", Enum.join(output_types, ")"), ")"]) ABI.decode(output_signature, trim_output) end - def encode_inputs abi, name, inputs do - input_types = Enum.map abi[name]["inputs"], fn x -> x["type"] end - input_signature = Enum.join [name, "(", Enum.join(input_types, ","), ")"] - ABI.encode(input_signature, inputs) |> Base.encode16(case: :lower) + def encode_input(abi, name, input) do + if abi[name]["inputs"] do + input_types = Enum.map(abi[name]["inputs"], fn x -> x["type"] end) + input_signature = Enum.join([name, "(", Enum.join(input_types, ","), ")"]) + ABI.encode(input_signature, input) |> Base.encode16(case: :lower) + else + raise "#{name} method not found with the given abi" + end + end + + def bytes_to_string bytes do + bytes + |> Base.encode16(case: :lower) + |> String.replace_trailing("0", "") + |> Hexate.decode end def accounts do - case Ethereumex.HttpClient.eth_accounts do + case Ethereumex.HttpClient.eth_accounts() do {:ok, accounts} -> accounts err -> err end end - #Converts ethereum hex string to decimal number - def to_decimal hex_string do + # Converts ethereum hex string to decimal number + def to_decimal(hex_string) do hex_string |> String.slice(2..-1) |> String.to_integer(16) end def block_number do - case Ethereumex.HttpClient.eth_block_number do - {:ok, block_number} -> + case Ethereumex.HttpClient.eth_block_number() do + {:ok, block_number} -> block_number |> to_decimal - err -> err + + err -> + err end end - def balance account do + def balance(account) do case Ethereumex.HttpClient.eth_get_balance(account) do - {:ok, balance} -> + {:ok, balance} -> balance |> to_decimal - err -> err + + err -> + err end end - def keys_to_decimal map, keys do + def keys_to_decimal(map, keys) do Map.new( - Enum.map keys, fn k -> - { k, Map.get(map, k) |> to_decimal } - end + Enum.map(keys, fn k -> + {k, Map.get(map, k) |> to_decimal} + end) ) end - def tx_receipt tx_hash do + def tx_receipt(tx_hash) do case Ethereumex.HttpClient.eth_get_transaction_receipt(tx_hash) do - {:ok, receipt} -> - receipt - #Map.merge receipt, keys_to_decimal(receipt, ["blockNumber", "cumulativeGasUsed", "gasUsed"]) - err -> err + {:ok, receipt} -> + Map.merge receipt, keys_to_decimal(receipt, ["blockNumber", "cumulativeGasUsed", "gasUsed"]) + + err -> + err end end - def block block_number do + def block(block_number) do case Ethereumex.HttpClient.eth_get_block_by_number(block_number, true) do {:ok, block} -> block err -> err @@ -91,9 +109,9 @@ defmodule ExW3 do defmodule Contract do use Agent - + def at(abi, address) do - { :ok, pid } = Agent.start_link(fn -> %{abi: abi, address: address} end) + {:ok, pid} = Agent.start_link(fn -> %{abi: abi, address: address} end) pid end @@ -110,40 +128,55 @@ defmodule ExW3 do def deploy(bin_filename, options) do {:ok, bin} = File.read(Path.join(System.cwd(), bin_filename)) + tx = %{ from: options[:from], data: bin, gas: options[:gas] } + {:ok, tx_receipt_id} = Ethereumex.HttpClient.eth_send_transaction(tx) {:ok, tx_receipt} = Ethereumex.HttpClient.eth_get_transaction_receipt(tx_receipt_id) tx_receipt["contractAddress"] end - def method(contract_agent, name, args \\ [], options \\ %{}) do - if get(contract_agent, :abi)[name]["constant"] do - data = ExW3.encode_inputs(get(contract_agent, :abi), name, args) - {:ok, output } = Ethereumex.HttpClient.eth_call(%{ - to: get(contract_agent, :address), - data: data - }) - [ :ok ] ++ ExW3.decode_output(get(contract_agent, :abi), name, output) |> List.to_tuple + def method(contract_agent, method_name, args \\ [], options \\ %{}) do + method_name = + case method_name |> is_atom do + true -> Inflex.camelize(method_name, :lower) + false -> method_name + end + + input = ExW3.encode_input(get(contract_agent, :abi), method_name, args) + + if get(contract_agent, :abi)[method_name]["constant"] do + {:ok, output} = + Ethereumex.HttpClient.eth_call(%{ + to: get(contract_agent, :address), + data: input + }) + + ([:ok] ++ ExW3.decode_output(get(contract_agent, :abi), method_name, output)) |> List.to_tuple() else - Ethereumex.HttpClient.eth_send_transaction(Map.merge(%{ - to: get(contract_agent, :address), - data: ExW3.encode_inputs(get(contract_agent, :abi), name, args) - }, options)) + Ethereumex.HttpClient.eth_send_transaction( + Map.merge( + %{ + to: get(contract_agent, :address), + data: input + }, + options + ) + ) end end - end defmodule EventPublisher do use GenServer def start_link do - GenServer.start_link(__MODULE__, %{ block_number: ExW3.block_number}) + GenServer.start_link(__MODULE__, %{block_number: ExW3.block_number()}) end def init(state) do @@ -157,16 +190,15 @@ defmodule ExW3 do end def handle_info(:block, state) do - block_number = ExW3.block_number - block = ExW3.block block_number + block_number = ExW3.block_number() + block = ExW3.block(block_number) - tx_receipts = Enum.map block["transactions"], fn tx -> ExW3.tx_receipt(tx["hash"]) end + tx_receipts = Enum.map(block["transactions"], fn tx -> ExW3.tx_receipt(tx["hash"]) end) for logs <- Enum.map(tx_receipts, fn receipt -> receipt["logs"] end) do for log <- logs do for topic <- log["topics"] do - - PubSub.publish String.slice(topic, 2..-1), log["data"] + PubSub.publish(String.slice(topic, 2..-1), log["data"]) end end end @@ -178,30 +210,32 @@ defmodule ExW3 do defp schedule_block() do Process.send_after(self(), :block, 1000) end + end + def decode_event(data, signature) do + fs = ABI.FunctionSelector.decode(signature) + + data + |> Base.decode16!(case: :lower) + |> ABI.TypeDecoder.decode(fs) end defmodule EventSubscriber do - - def start(state) do - spawn(fn -> loop(state) end) + def start_link(signature, callback) do + pid = spawn(fn -> loop(%{callback: callback, signature: signature}) end) + ExW3.EventPublisher.subscribe(pid, ExW3.encode_event(signature)) + {:ok, pid} end def loop(state) do receive do message -> - apply(state[:callback], [message]) + apply(state[:callback], [ + ExW3.decode_event(String.slice(message, 2..-1), state[:signature]) + ]) + loop(state) end end - - end - - def subscribe signature, callback do - - pid = ExW3.EventSubscriber.start %{callback: callback} - - ExW3.EventPublisher.subscribe pid, ExW3.encode_event(signature) - end -end \ No newline at end of file +end diff --git a/mix.exs b/mix.exs index 68c6f7a..7b2f4f9 100644 --- a/mix.exs +++ b/mix.exs @@ -31,7 +31,9 @@ defmodule ExW3.Mixfile do {:ethereumex, "~> 0.3.2"}, {:abi, "~> 0.1.8"}, {:poison, "~> 3.1"}, - {:pubsub, "~> 1.0"} + {:pubsub, "~> 1.0"}, + {:inflex, "~> 1.10.0" }, + {:hexate, ">= 0.6.0"} ] end end diff --git a/mix.lock b/mix.lock index cfba413..5a9a33f 100644 --- a/mix.lock +++ b/mix.lock @@ -8,6 +8,7 @@ "hexate": {:hex, :hexate, "0.6.1", "1cea42e462c1daa32223127d4752e71016c3d933d492b9bb7fa4709a4a0fd50d", [:mix], [], "hexpm"}, "httpoison": {:hex, :httpoison, "1.0.0", "1f02f827148d945d40b24f0b0a89afe40bfe037171a6cf70f2486976d86921cd", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "inflex": {:hex, :inflex, "1.10.0", "8366a7696e70e1813aca102e61274addf85d99f4a072b2f9c7984054ea1b9d29", [:mix], [], "hexpm"}, "keccakf1600": {:hex, :keccakf1600_orig, "2.0.0", "0a7217ddb3ee8220d449bbf7575ec39d4e967099f220a91e3dfca4dbaef91963", [:rebar3], [], "hexpm"}, "libsecp256k1": {:hex, :libsecp256k1, "0.1.3", "57468b986af7c9633527875f71c7ca08bf4150b07b38a60d5bd48fba299ff6c1", [:rebar3], [], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, diff --git a/test/exw3_test.exs b/test/exw3_test.exs index 8e59884..ea7fc02 100644 --- a/test/exw3_test.exs +++ b/test/exw3_test.exs @@ -50,17 +50,17 @@ defmodule EXW3Test do storage = ExW3.Contract.at context[:simple_storage_abi], contract_address - {:ok, result} = ExW3.Contract.method(storage, "get") + {:ok, result} = ExW3.Contract.method(storage, :get) assert result == 0 - {:ok, tx_hash} = ExW3.Contract.method(storage, "set", [1], %{from: Enum.at(context[:accounts], 0)}) + {:ok, tx_hash} = ExW3.Contract.method(storage, :set, [1], %{from: Enum.at(context[:accounts], 0)}) receipt = ExW3.tx_receipt tx_hash #IO.inspect ExW3.block receipt["blockNumber"] - {:ok, result} = ExW3.Contract.method(storage, "get") + {:ok, result} = ExW3.Contract.method(storage, :get) assert result == 1 @@ -79,9 +79,24 @@ defmodule EXW3Test do {:ok, event_pub} = ExW3.EventPublisher.start_link - ExW3.subscribe("Simple(uint256,bytes32)", fn event -> IO.inspect event end) + {:ok, pid} = ExW3.EventSubscriber.start_link( + "Simple(uint256,bytes32)", + fn event_data -> + str = + event_data + |> Enum.at(1) + |> ExW3.bytes_to_string - {:ok, tx_hash} = ExW3.Contract.method(event_tester, "simple", ["hello, there!"], %{from: Enum.at(context[:accounts], 0)}) + assert str == "Hello, World!" + end + ) + + {:ok, tx_hash} = ExW3.Contract.method( + event_tester, + :simple, + ["Hello, World!"], + %{from: Enum.at(context[:accounts], 0)} + ) receipt = ExW3.tx_receipt tx_hash @@ -91,7 +106,7 @@ defmodule EXW3Test do assert String.slice(Enum.at(topic, 0), 2..-1) == ExW3.encode_event("Simple(uint256,bytes32)") - :timer.sleep(5000) + :timer.sleep(2000) end @@ -108,12 +123,12 @@ defmodule EXW3Test do arr = [1, 2, 3, 4, 5] - {:ok, result} = ExW3.Contract.method(array_tester, "staticUint", [arr]) + {:ok, result} = ExW3.Contract.method array_tester, :static_int, [arr] assert result == arr #0x5d4e0342 - {:ok, result} = ExW3.Contract.method(array_tester, "dynamicUint", [arr]) + {:ok, result} = ExW3.Contract.method array_tester, :dynamic_uint, [arr] assert result == arr