From 43dcd0de42dc55a883c72aba55a322e85a0e7083 Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Tue, 17 May 2022 15:34:45 -0300 Subject: [PATCH 01/14] add function to get from non-etherscan addresses --- eth_client/lib/eth_client/abi.ex | 18 ++++++++++++++++++ eth_client/mix.exs | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/eth_client/lib/eth_client/abi.ex b/eth_client/lib/eth_client/abi.ex index d65915e..b6bf780 100644 --- a/eth_client/lib/eth_client/abi.ex +++ b/eth_client/lib/eth_client/abi.ex @@ -3,9 +3,27 @@ defmodule EthClient.ABI do """ alias EthClient.Context + # get whether it's an etherscan-linked bc + # if it is, use provided ABI + # elsewise, get ABI through invoking panoramix on rpc provider def get("0x" <> _ = address), do: get_etherscan(address) + + + def get(abi_path), do: get_local(abi_path) + defp get_non_etherscan(address) do + # if it exists, use provided ABI (.bin -> .abi) (?) + # elsewise, get ABI through invoking panoramix on rpc provider (if possible) + + {path, _} = System.cmd("command", ["-v", "python3"]) + + {:ok, python_pid} = :python.start(python: '/opt/homebrew/bin/python3') + :python.call(python_pid, :'panoramix.decompiler', :decompile_address, [<
>]) + :python.stop(python_pid) + + end + defp get_etherscan(address) do api_key = Context.etherscan_api_key() diff --git a/eth_client/mix.exs b/eth_client/mix.exs index 076e2f8..c095fdf 100644 --- a/eth_client/mix.exs +++ b/eth_client/mix.exs @@ -28,7 +28,8 @@ defmodule EthClient.MixProject do {:ex_abi, "~> 0.5"}, {:rustler, "~> 0.25.0"}, {:ex_rlp, "~> 0.5.4"}, - {:dialyxir, "~> 1.0", only: [:dev], runtime: false} + {:dialyxir, "~> 1.0", only: [:dev], runtime: false}, + {:erlport, "~>0.10.1"} ] end end From 126ab9df0c75cbbbf21a21527ac5773225808d8c Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Thu, 19 May 2022 15:34:02 -0300 Subject: [PATCH 02/14] add script to decode address --- eth_client/lib/eth_client/abi.ex | 22 +++++++++++++--------- eth_client/mix.exs | 3 +-- eth_client/priv/decode_address.py | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 eth_client/priv/decode_address.py diff --git a/eth_client/lib/eth_client/abi.ex b/eth_client/lib/eth_client/abi.ex index b6bf780..8cff86a 100644 --- a/eth_client/lib/eth_client/abi.ex +++ b/eth_client/lib/eth_client/abi.ex @@ -12,15 +12,19 @@ defmodule EthClient.ABI do def get(abi_path), do: get_local(abi_path) - defp get_non_etherscan(address) do - # if it exists, use provided ABI (.bin -> .abi) (?) - # elsewise, get ABI through invoking panoramix on rpc provider (if possible) - - {path, _} = System.cmd("command", ["-v", "python3"]) - - {:ok, python_pid} = :python.start(python: '/opt/homebrew/bin/python3') - :python.call(python_pid, :'panoramix.decompiler', :decompile_address, [<
>]) - :python.stop(python_pid) + def get_non_etherscan(address) do + # if it exists, use provided ABI (.bin -> .abi) (?) + # elsewise, get ABI through invoking panoramix on rpc provider (if possible) + decode_path = Application.app_dir(:eth_client, "priv/decode_address.py") + + case System.cmd("python3", [decode_path, address]) do + {hashes, 0} -> + {:ok, hashlist} = hashes + |> Jason.decode() + + {_, _} -> + {:error, :abi_unavailable} + end end diff --git a/eth_client/mix.exs b/eth_client/mix.exs index c095fdf..076e2f8 100644 --- a/eth_client/mix.exs +++ b/eth_client/mix.exs @@ -28,8 +28,7 @@ defmodule EthClient.MixProject do {:ex_abi, "~> 0.5"}, {:rustler, "~> 0.25.0"}, {:ex_rlp, "~> 0.5.4"}, - {:dialyxir, "~> 1.0", only: [:dev], runtime: false}, - {:erlport, "~>0.10.1"} + {:dialyxir, "~> 1.0", only: [:dev], runtime: false} ] end end diff --git a/eth_client/priv/decode_address.py b/eth_client/priv/decode_address.py new file mode 100644 index 0000000..079e8cb --- /dev/null +++ b/eth_client/priv/decode_address.py @@ -0,0 +1,15 @@ +import sys +import json +from panoramix.decompiler import decompile_address + +def get_hash(function_def): + return function_def['hash'] + +if len(sys.argv) != 2: + print("usage: python3 decode_address.py
", sys.argv) +else: + decompilation = decompile_address(sys.argv[1]) + + # Despite its name, it's not a json. At least, Jason was complaining. + hashes = list(map(get_hash, decompilation.json["functions"])) + print(json.dumps(hashes)) From ff9df931d88fb072b281eb79bfab9a53dcc030cd Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Thu, 19 May 2022 17:04:11 -0300 Subject: [PATCH 03/14] add name to decode function only in those with known names --- eth_client/lib/eth_client/abi.ex | 25 ++++++++++++++++++------- eth_client/priv/decode_address.py | 6 +++++- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/eth_client/lib/eth_client/abi.ex b/eth_client/lib/eth_client/abi.ex index 8cff86a..e9a8212 100644 --- a/eth_client/lib/eth_client/abi.ex +++ b/eth_client/lib/eth_client/abi.ex @@ -8,22 +8,33 @@ defmodule EthClient.ABI do # elsewise, get ABI through invoking panoramix on rpc provider def get("0x" <> _ = address), do: get_etherscan(address) - - def get(abi_path), do: get_local(abi_path) + defp filter_unnamed(function_def) do + "0x" <> function_hash = function_def["hash"] + unknown_name = "unknown" <> function_hash <> "()" + case Map.get(function_def, "name") do + ^unknown_name -> Map.delete(function_def, "name") |> IO.inspect() + _ -> function_def + end + end + def get_non_etherscan(address) do - # if it exists, use provided ABI (.bin -> .abi) (?) - # elsewise, get ABI through invoking panoramix on rpc provider (if possible) + # TODO: panoramix uses localhost as RPC provider decode_path = Application.app_dir(:eth_client, "priv/decode_address.py") case System.cmd("python3", [decode_path, address]) do {hashes, 0} -> - {:ok, hashlist} = hashes + {:ok, funclist} = hashes |> Jason.decode() - {_, _} -> - {:error, :abi_unavailable} + funclist = funclist + |> IO.inspect() + |> Enum.filter(fn funcdef -> Map.get(funcdef, "hash") != "_fallback()" end) + |> Enum.map(&filter_unnamed/1) + + {_, _} -> + {:error, :abi_unavailable} end end diff --git a/eth_client/priv/decode_address.py b/eth_client/priv/decode_address.py index 079e8cb..75a2b27 100644 --- a/eth_client/priv/decode_address.py +++ b/eth_client/priv/decode_address.py @@ -3,7 +3,11 @@ from panoramix.decompiler import decompile_address def get_hash(function_def): - return function_def['hash'] + func = {} + func['hash'] = function_def['hash'] + # We do want the name, but if it's unknown <> hash then it's useless + func['name'] = function_def['abi_name'] + return func if len(sys.argv) != 2: print("usage: python3 decode_address.py
", sys.argv) From 3e1e54061db7f4dd03f666eecd51ea940a18f854 Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Fri, 20 May 2022 12:01:56 -0300 Subject: [PATCH 04/14] WIP add get_non_etherscan function and work on build_function_by_selector --- eth_client/lib/eth_client.ex | 14 +++++++++++++ eth_client/lib/eth_client/abi.ex | 25 +++++++++++++--------- eth_client/lib/eth_client/contract.ex | 12 +++++++++++ eth_client/lib/eth_client/rpc.ex | 1 + eth_client/priv/decode_address.py | 19 ----------------- eth_client/priv/decompile.py | 30 +++++++++++++++++++++++++++ 6 files changed, 72 insertions(+), 29 deletions(-) delete mode 100644 eth_client/priv/decode_address.py create mode 100644 eth_client/priv/decompile.py diff --git a/eth_client/lib/eth_client.ex b/eth_client/lib/eth_client.ex index 1780cff..75f72c9 100644 --- a/eth_client/lib/eth_client.ex +++ b/eth_client/lib/eth_client.ex @@ -90,12 +90,26 @@ defmodule EthClient do wei_to_ether(balance) end + def invoke_by_selector(selector, types, arguments, amount) do + arguments = + ABI.TypeEncoder.encode_raw(arguments, types) + |> Base.encode16(case: :lower) + + data = selector <> arguments + + invoke_with_data(data, amount) + end + def invoke(method, arguments, amount) do data = ABI.encode(method, arguments) |> Base.encode16(case: :lower) |> add_0x() + invoke_with_data(data, amount) + end + + defp invoke_with_data(data, amount) do caller = Context.user_account() caller_address = String.downcase(caller.address) contract_address = Context.contract().address diff --git a/eth_client/lib/eth_client/abi.ex b/eth_client/lib/eth_client/abi.ex index e9a8212..1e654b5 100644 --- a/eth_client/lib/eth_client/abi.ex +++ b/eth_client/lib/eth_client/abi.ex @@ -2,11 +2,16 @@ defmodule EthClient.ABI do @moduledoc """ """ alias EthClient.Context - - # get whether it's an etherscan-linked bc - # if it is, use provided ABI - # elsewise, get ABI through invoking panoramix on rpc provider - def get("0x" <> _ = address), do: get_etherscan(address) + alias EthClient.Rpc + + # get whether it's an etherscan-linked bc: get_etherscan_api_key... + def get("0x" <> _ = address) do + if Context.etherscan_api_key() do + get_etherscan(address) + else + get_non_etherscan(address) + end + end def get(abi_path), do: get_local(abi_path) @@ -20,18 +25,18 @@ defmodule EthClient.ABI do end def get_non_etherscan(address) do - # TODO: panoramix uses localhost as RPC provider - decode_path = Application.app_dir(:eth_client, "priv/decode_address.py") + decode_path = Application.app_dir(:eth_client, "priv/decompile.py") + {:ok, bytecode} = Rpc.get_code(address) - case System.cmd("python3", [decode_path, address]) do + case System.cmd("python3", [decode_path, bytecode]) do {hashes, 0} -> {:ok, funclist} = hashes |> Jason.decode() funclist = funclist - |> IO.inspect() - |> Enum.filter(fn funcdef -> Map.get(funcdef, "hash") != "_fallback()" end) + |> Enum.filter(fn %{"hash" => hash} -> hash != "_fallback()" end) |> Enum.map(&filter_unnamed/1) + #TODO: see if it's view or pure for call/invoke distinction {_, _} -> {:error, :abi_unavailable} diff --git a/eth_client/lib/eth_client/contract.ex b/eth_client/lib/eth_client/contract.ex index 38edde3..a458d42 100644 --- a/eth_client/lib/eth_client/contract.ex +++ b/eth_client/lib/eth_client/contract.ex @@ -29,6 +29,7 @@ defmodule EthClient.Contract do name_snake_case = String.to_atom(Macro.underscore(method_map["name"])) function = build_function(method) + # TODO: use hashes to make the function call acc = Map.put(acc, name_snake_case, Code.eval_quoted(function) |> elem(0)) parse_abi(tail, acc) @@ -60,4 +61,15 @@ defmodule EthClient.Contract do end end end + + defp build_function_by_hash(method) do + args = Macro.generate_arguments(length(method.inputs), __MODULE__) + method_signature = "#{method.name}(#{Enum.join(method.input_types, ",")})" + + quote do + fn unquote_splicing(args), amount -> + EthClient.invoke_by_selector(unquote(method_signature), unquote(args), , amount) + end + end + end end diff --git a/eth_client/lib/eth_client/rpc.ex b/eth_client/lib/eth_client/rpc.ex index 9eef202..1ad775b 100644 --- a/eth_client/lib/eth_client/rpc.ex +++ b/eth_client/lib/eth_client/rpc.ex @@ -18,6 +18,7 @@ defmodule EthClient.Rpc do def gas_price, do: send_request("eth_gasPrice", []) def get_transaction_by_hash(tx_hash), do: send_request("eth_getTransactionByHash", [tx_hash]) def get_transaction_receipt(tx_hash), do: send_request("eth_getTransactionReceipt", [tx_hash]) + def get_code(contract), do: send_request("eth_getCode", [contract, "latest"]) def call(call_map), do: send_request("eth_call", [call_map, "latest"]) def get_logs(log_map), do: send_request("eth_getLogs", [log_map]) diff --git a/eth_client/priv/decode_address.py b/eth_client/priv/decode_address.py deleted file mode 100644 index 75a2b27..0000000 --- a/eth_client/priv/decode_address.py +++ /dev/null @@ -1,19 +0,0 @@ -import sys -import json -from panoramix.decompiler import decompile_address - -def get_hash(function_def): - func = {} - func['hash'] = function_def['hash'] - # We do want the name, but if it's unknown <> hash then it's useless - func['name'] = function_def['abi_name'] - return func - -if len(sys.argv) != 2: - print("usage: python3 decode_address.py
", sys.argv) -else: - decompilation = decompile_address(sys.argv[1]) - - # Despite its name, it's not a json. At least, Jason was complaining. - hashes = list(map(get_hash, decompilation.json["functions"])) - print(json.dumps(hashes)) diff --git a/eth_client/priv/decompile.py b/eth_client/priv/decompile.py new file mode 100644 index 0000000..60f2515 --- /dev/null +++ b/eth_client/priv/decompile.py @@ -0,0 +1,30 @@ +import sys +import json +from panoramix.decompiler import decompile_bytecode + +def get_hash(function_def): + func = {} + func['hash'] = function_def['hash'] + # We do want the name, but if it's unknown <> hash then it's useless + func['name'] = function_def['abi_name'] + #TODO: get pure/view + # NB: if that breaks, it might be because of the usage of const/payable as fields of the ABI + # use stateMutability instead (might need to fork panoramix) + + # method = %{ + # name: String.to_atom(method_map["name"]), + # state_mutability: method_map["stateMutability"], + # inputs: Enum.map(method_map["inputs"], fn input -> input["name"] end), + # # What's the difference between type and internal type? + # input_types: Enum.map(method_map["inputs"], fn input -> input["internalType"] end) + # } + return func + +if len(sys.argv) != 2: + print("usage: python3 decode_address.py
", sys.argv) +else: + decompilation = decompile_bytecode(sys.argv[1]) + + # Despite its name, it's not a json + hashes = list(map(get_hash, decompilation.json["functions"])) + print(json.dumps(hashes)) From 764f32b4aa5f23cd76307cc0b36c2026d3388508 Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Mon, 23 May 2022 17:00:00 -0300 Subject: [PATCH 05/14] WIP add other params in decompiler output --- eth_client/lib/eth_client.ex | 15 +++++++++++ eth_client/lib/eth_client/contract.ex | 38 +++++++++++++++++++-------- eth_client/priv/decompile.py | 10 +++++-- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/eth_client/lib/eth_client.ex b/eth_client/lib/eth_client.ex index 75f72c9..0077cea 100644 --- a/eth_client/lib/eth_client.ex +++ b/eth_client/lib/eth_client.ex @@ -81,6 +81,21 @@ defmodule EthClient do |> Rpc.call() end + def call_by_selector(selector, types, arguments) do + arguments = + ABI.TypeEncoder.encode_raw(arguments, types) + |> Base.encode16(case: :lower) + + data = selector <> arguments + + %{ + from: Context.user_account().address, + to: Context.contract().address, + data: data + } + |> Rpc.call() + end + def get_balance(address) do {balance, _lead} = Rpc.get_balance(address) diff --git a/eth_client/lib/eth_client/contract.ex b/eth_client/lib/eth_client/contract.ex index a458d42..809f15f 100644 --- a/eth_client/lib/eth_client/contract.ex +++ b/eth_client/lib/eth_client/contract.ex @@ -14,7 +14,7 @@ defmodule EthClient.Contract do end end - defp parse_abi(abi), do: parse_abi(abi, %{}) + # defp parse_abi(abi), do: parse_abi(abi, %{}) defp parse_abi([], acc), do: {:ok, acc} @@ -35,41 +35,57 @@ defmodule EthClient.Contract do parse_abi(tail, acc) end + defp parse_abi() do + + end + defp parse_abi([_head | tail], acc) do parse_abi(tail, acc) end - defp build_function(%{state_mutability: mutability} = method) - when mutability in ["pure", "view"] do + defp build_function_by_hash(%{selector: selector, state_mutability: mutability} = method) + when mutability in ["prue", "view"] do args = Macro.generate_arguments(length(method.inputs), __MODULE__) - method_signature = "#{method.name}(#{Enum.join(method.input_types, ",")})" quote do - fn unquote_splicing(args) -> - EthClient.call(unquote(method_signature), unquote(args)) + fn types, unquote_splicing(args) -> + EthClient.call_by_selector(unquote(selector), types, unquote(args)) end end end - defp build_function(method) do + defp build_function_by_hash(%{selector: selector}) do + args = Macro.generate_arguments(length(method.inputs), __MODULE__) + selector = "#{method.name}(#{Enum.join(method.input_types, ",")})" + + quote do + fn types, unquote_splicing(args), amount -> + EthClient.invoke_by_selector(unquote(selector), types, unquote(args), amount) + end + end + end + + defp build_function(%{state_mutability: mutability} = method) + when mutability in ["pure", "view"] do args = Macro.generate_arguments(length(method.inputs), __MODULE__) method_signature = "#{method.name}(#{Enum.join(method.input_types, ",")})" quote do - fn unquote_splicing(args), amount -> - EthClient.invoke(unquote(method_signature), unquote(args), amount) + fn unquote_splicing(args) -> + EthClient.call(unquote(method_signature), unquote(args)) end end end - defp build_function_by_hash(method) do + defp build_function(method) do args = Macro.generate_arguments(length(method.inputs), __MODULE__) method_signature = "#{method.name}(#{Enum.join(method.input_types, ",")})" quote do fn unquote_splicing(args), amount -> - EthClient.invoke_by_selector(unquote(method_signature), unquote(args), , amount) + EthClient.invoke(unquote(method_signature), unquote(args), amount) end end end + end diff --git a/eth_client/priv/decompile.py b/eth_client/priv/decompile.py index 60f2515..378fbed 100644 --- a/eth_client/priv/decompile.py +++ b/eth_client/priv/decompile.py @@ -4,9 +4,16 @@ def get_hash(function_def): func = {} - func['hash'] = function_def['hash'] + func['selector'] = function_def['hash'] # We do want the name, but if it's unknown <> hash then it's useless func['name'] = function_def['abi_name'] + func['stateMutability'] = "pure" + func['inputs'] = function_def['params'] + func['input_types'] = function_def['params'] + + # inputs: Enum.map(method_map["inputs"], fn input -> input["name"] end), + # # What's the difference between type and internal type? + # input_types: Enum.map(method_map["inputs"], fn input -> input["internalType"] end) #TODO: get pure/view # NB: if that breaks, it might be because of the usage of const/payable as fields of the ABI # use stateMutability instead (might need to fork panoramix) @@ -24,7 +31,6 @@ def get_hash(function_def): print("usage: python3 decode_address.py
", sys.argv) else: decompilation = decompile_bytecode(sys.argv[1]) - # Despite its name, it's not a json hashes = list(map(get_hash, decompilation.json["functions"])) print(json.dumps(hashes)) From 8cffaca2627060cb27a2de9de159b86a316884e1 Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Tue, 24 May 2022 17:25:59 -0300 Subject: [PATCH 06/14] add parse_abi for unknown functions, add get_params to ABI module and add mutability to decompile.py --- eth_client/lib/eth_client/abi.ex | 24 ++++++++++++++++++----- eth_client/lib/eth_client/contract.ex | 28 +++++++++++++++++++-------- eth_client/priv/decompile.py | 27 +++++++++----------------- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/eth_client/lib/eth_client/abi.ex b/eth_client/lib/eth_client/abi.ex index 1e654b5..0be2472 100644 --- a/eth_client/lib/eth_client/abi.ex +++ b/eth_client/lib/eth_client/abi.ex @@ -15,11 +15,24 @@ defmodule EthClient.ABI do def get(abi_path), do: get_local(abi_path) + defp get_params(function_def) do + # it'd be nice to take from the types in the param + [name | params] = String.split(function_def["name"], ~r{\(|\)}, trim: true) + + param_types = params + |> List.to_string() + |> String.split(",", trim: true) + |> Enum.map(fn param -> %{"name" => param} end) + + Map.put(function_def, "inputs", param_types) + |> Map.put("name", name) + end + defp filter_unnamed(function_def) do - "0x" <> function_hash = function_def["hash"] - unknown_name = "unknown" <> function_hash <> "()" + "0x" <> function_hash = function_def["selector"] + unknown_name = "unknown" <> function_hash case Map.get(function_def, "name") do - ^unknown_name -> Map.delete(function_def, "name") |> IO.inspect() + ^unknown_name -> Map.delete(function_def, "name") _ -> function_def end end @@ -34,10 +47,11 @@ defmodule EthClient.ABI do |> Jason.decode() funclist = funclist - |> Enum.filter(fn %{"hash" => hash} -> hash != "_fallback()" end) + |> Enum.filter(fn %{"selector" => hash} -> hash != "_fallback()" end) + |> Enum.map(&get_params/1) |> Enum.map(&filter_unnamed/1) - #TODO: see if it's view or pure for call/invoke distinction + {:ok, funclist} {_, _} -> {:error, :abi_unavailable} end diff --git a/eth_client/lib/eth_client/contract.ex b/eth_client/lib/eth_client/contract.ex index 809f15f..aba6d03 100644 --- a/eth_client/lib/eth_client/contract.ex +++ b/eth_client/lib/eth_client/contract.ex @@ -10,21 +10,22 @@ defmodule EthClient.Contract do def get_functions(address_or_path) do with {:ok, abi} <- ABI.get(address_or_path) do + # abi |> IO.inspect(label: "ordenanza") parse_abi(abi) end end - # defp parse_abi(abi), do: parse_abi(abi, %{}) + defp parse_abi(abi), do: parse_abi(abi, %{}) defp parse_abi([], acc), do: {:ok, acc} - defp parse_abi([%{"type" => "function"} = method_map | tail], acc) do + defp parse_abi([%{"type" => "function", "name" => name} = method_map | tail], acc) do method = %{ - name: String.to_atom(method_map["name"]), + name: name, state_mutability: method_map["stateMutability"], inputs: Enum.map(method_map["inputs"], fn input -> input["name"] end), # What's the difference between type and internal type? - input_types: Enum.map(method_map["inputs"], fn input -> input["internalType"] end) + input_types: Enum.map(method_map["inputs"], fn input -> input["name"] end) } name_snake_case = String.to_atom(Macro.underscore(method_map["name"])) @@ -35,8 +36,20 @@ defmodule EthClient.Contract do parse_abi(tail, acc) end - defp parse_abi() do + defp parse_abi([%{"type" => "function", "selector" => selector} = method_map | tail], acc) do + # method_map |> IO.inspect(label: "asd") + method = %{ + selector: String.to_atom(selector), + state_mutability: method_map["stateMutability"], + inputs: Enum.map(method_map["inputs"], fn input -> input["name"] end), + } + function = build_function_by_hash(method) + + selector_atom = String.to_atom(Macro.underscore(method_map["selector"])) + acc = Map.put(acc, selector_atom, Code.eval_quoted(function) |> elem(0)) + + parse_abi(tail, acc) end defp parse_abi([_head | tail], acc) do @@ -44,7 +57,7 @@ defmodule EthClient.Contract do end defp build_function_by_hash(%{selector: selector, state_mutability: mutability} = method) - when mutability in ["prue", "view"] do + when mutability in ["pure", "view"] do args = Macro.generate_arguments(length(method.inputs), __MODULE__) quote do @@ -54,9 +67,8 @@ defmodule EthClient.Contract do end end - defp build_function_by_hash(%{selector: selector}) do + defp build_function_by_hash(%{selector: selector} = method) do args = Macro.generate_arguments(length(method.inputs), __MODULE__) - selector = "#{method.name}(#{Enum.join(method.input_types, ",")})" quote do fn types, unquote_splicing(args), amount -> diff --git a/eth_client/priv/decompile.py b/eth_client/priv/decompile.py index 378fbed..6e5265d 100644 --- a/eth_client/priv/decompile.py +++ b/eth_client/priv/decompile.py @@ -5,26 +5,17 @@ def get_hash(function_def): func = {} func['selector'] = function_def['hash'] - # We do want the name, but if it's unknown <> hash then it's useless func['name'] = function_def['abi_name'] - func['stateMutability'] = "pure" - func['inputs'] = function_def['params'] - func['input_types'] = function_def['params'] - # inputs: Enum.map(method_map["inputs"], fn input -> input["name"] end), - # # What's the difference between type and internal type? - # input_types: Enum.map(method_map["inputs"], fn input -> input["internalType"] end) - #TODO: get pure/view - # NB: if that breaks, it might be because of the usage of const/payable as fields of the ABI - # use stateMutability instead (might need to fork panoramix) - - # method = %{ - # name: String.to_atom(method_map["name"]), - # state_mutability: method_map["stateMutability"], - # inputs: Enum.map(method_map["inputs"], fn input -> input["name"] end), - # # What's the difference between type and internal type? - # input_types: Enum.map(method_map["inputs"], fn input -> input["internalType"] end) - # } + if function_def['payable']: + func['stateMutability'] = "payable" + else: + func['stateMutability'] = "nonpayable" + if function_def['const']: + func['stateMutability'] = "view" + + func['type'] = "function" + return func if len(sys.argv) != 2: From 6c6e6a86db7f551677f1e192a9057bc08c0cbb94 Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Tue, 24 May 2022 18:29:06 -0300 Subject: [PATCH 07/14] WIP param encoding for calls --- eth_client/lib/eth_client/contract.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eth_client/lib/eth_client/contract.ex b/eth_client/lib/eth_client/contract.ex index aba6d03..cc0c536 100644 --- a/eth_client/lib/eth_client/contract.ex +++ b/eth_client/lib/eth_client/contract.ex @@ -37,7 +37,6 @@ defmodule EthClient.Contract do end defp parse_abi([%{"type" => "function", "selector" => selector} = method_map | tail], acc) do - # method_map |> IO.inspect(label: "asd") method = %{ selector: String.to_atom(selector), state_mutability: method_map["stateMutability"], @@ -58,6 +57,7 @@ defmodule EthClient.Contract do defp build_function_by_hash(%{selector: selector, state_mutability: mutability} = method) when mutability in ["pure", "view"] do + # TODO: use param encoding to encode things here args = Macro.generate_arguments(length(method.inputs), __MODULE__) quote do @@ -69,6 +69,7 @@ defmodule EthClient.Contract do defp build_function_by_hash(%{selector: selector} = method) do args = Macro.generate_arguments(length(method.inputs), __MODULE__) + # TODO: use param encoding to encode things here quote do fn types, unquote_splicing(args), amount -> From f1649d56970ca3041de898de4f40efddcc131307 Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Thu, 26 May 2022 19:19:58 -0300 Subject: [PATCH 08/14] fix build_function_by_hash and use ex_abi parser to build function selector for parse_abi --- eth_client/lib/eth_client.ex | 19 ++++------ eth_client/lib/eth_client/abi.ex | 23 +++++------- eth_client/lib/eth_client/contract.ex | 54 +++++++++++++++++---------- 3 files changed, 50 insertions(+), 46 deletions(-) diff --git a/eth_client/lib/eth_client.ex b/eth_client/lib/eth_client.ex index 0077cea..68f7b9d 100644 --- a/eth_client/lib/eth_client.ex +++ b/eth_client/lib/eth_client.ex @@ -81,12 +81,9 @@ defmodule EthClient do |> Rpc.call() end - def call_by_selector(selector, types, arguments) do - arguments = - ABI.TypeEncoder.encode_raw(arguments, types) - |> Base.encode16(case: :lower) - - data = selector <> arguments + def call_by_selector(selector, arguments) do + encoded_arguments = ABI.TypeEncoder.encode_raw(arguments, selector.types) + data = selector.method_id <> encoded_arguments %{ from: Context.user_account().address, @@ -105,12 +102,10 @@ defmodule EthClient do wei_to_ether(balance) end - def invoke_by_selector(selector, types, arguments, amount) do - arguments = - ABI.TypeEncoder.encode_raw(arguments, types) - |> Base.encode16(case: :lower) - - data = selector <> arguments + def invoke_by_selector(selector, arguments, amount) do + encoded_arguments = ABI.TypeEncoder.encode_raw(arguments, selector.types) + data = selector.method_id <> encoded_arguments + |> IO.inspect(label: "morbusssy", binaries: :as_strings) invoke_with_data(data, amount) end diff --git a/eth_client/lib/eth_client/abi.ex b/eth_client/lib/eth_client/abi.ex index 0be2472..b9da95e 100644 --- a/eth_client/lib/eth_client/abi.ex +++ b/eth_client/lib/eth_client/abi.ex @@ -15,24 +15,19 @@ defmodule EthClient.ABI do def get(abi_path), do: get_local(abi_path) - defp get_params(function_def) do - # it'd be nice to take from the types in the param - [name | params] = String.split(function_def["name"], ~r{\(|\)}, trim: true) + def to_selector(function_def) do + selector = ABI.FunctionSelector.decode(function_def["name"]) + selector + |> Map.put(:method_id, function_def["selector"]) + |> Map.put(:state_mutability, function_def["stateMutability"]) - param_types = params - |> List.to_string() - |> String.split(",", trim: true) - |> Enum.map(fn param -> %{"name" => param} end) - - Map.put(function_def, "inputs", param_types) - |> Map.put("name", name) end defp filter_unnamed(function_def) do - "0x" <> function_hash = function_def["selector"] + "0x" <> function_hash = function_def.method_id unknown_name = "unknown" <> function_hash - case Map.get(function_def, "name") do - ^unknown_name -> Map.delete(function_def, "name") + case Map.get(function_def, :function) do + ^unknown_name -> Map.delete(function_def, :function) _ -> function_def end end @@ -48,7 +43,7 @@ defmodule EthClient.ABI do funclist = funclist |> Enum.filter(fn %{"selector" => hash} -> hash != "_fallback()" end) - |> Enum.map(&get_params/1) + |> Enum.map(&to_selector/1) |> Enum.map(&filter_unnamed/1) {:ok, funclist} diff --git a/eth_client/lib/eth_client/contract.ex b/eth_client/lib/eth_client/contract.ex index cc0c536..b9e4de8 100644 --- a/eth_client/lib/eth_client/contract.ex +++ b/eth_client/lib/eth_client/contract.ex @@ -10,7 +10,6 @@ defmodule EthClient.Contract do def get_functions(address_or_path) do with {:ok, abi} <- ABI.get(address_or_path) do - # abi |> IO.inspect(label: "ordenanza") parse_abi(abi) end end @@ -28,7 +27,7 @@ defmodule EthClient.Contract do input_types: Enum.map(method_map["inputs"], fn input -> input["name"] end) } - name_snake_case = String.to_atom(Macro.underscore(method_map["name"])) + name_snake_case = String.to_atom(Macro.underscore(name)) function = build_function(method) # TODO: use hashes to make the function call acc = Map.put(acc, name_snake_case, Code.eval_quoted(function) |> elem(0)) @@ -36,16 +35,24 @@ defmodule EthClient.Contract do parse_abi(tail, acc) end - defp parse_abi([%{"type" => "function", "selector" => selector} = method_map | tail], acc) do - method = %{ - selector: String.to_atom(selector), - state_mutability: method_map["stateMutability"], - inputs: Enum.map(method_map["inputs"], fn input -> input["name"] end), - } + defp parse_abi([%{function: name} = method_map | tail], acc) do + function = build_function_by_hash(method_map) - function = build_function_by_hash(method) + selector_atom = name + |> Macro.underscore() + |> String.to_atom() + + acc = Map.put(acc, selector_atom, Code.eval_quoted(function) |> elem(0)) + + parse_abi(tail, acc) + end + + defp parse_abi([%{type: function, method_id: selector} = method_map | tail], acc) do + function = build_function_by_hash(method_map) + + selector_atom = selector + |> String.to_atom() - selector_atom = String.to_atom(Macro.underscore(method_map["selector"])) acc = Map.put(acc, selector_atom, Code.eval_quoted(function) |> elem(0)) parse_abi(tail, acc) @@ -55,25 +62,32 @@ defmodule EthClient.Contract do parse_abi(tail, acc) end - defp build_function_by_hash(%{selector: selector, state_mutability: mutability} = method) + defp build_function_by_hash(%{method_id: selector, state_mutability: mutability} = method) when mutability in ["pure", "view"] do - # TODO: use param encoding to encode things here - args = Macro.generate_arguments(length(method.inputs), __MODULE__) + args = method.types + |> length() + |> Macro.generate_arguments(__MODULE__) + + bound_method = Macro.escape(method) quote do - fn types, unquote_splicing(args) -> - EthClient.call_by_selector(unquote(selector), types, unquote(args)) + fn unquote_splicing(args) -> + EthClient.call_by_selector(unquote(bound_method), unquote(args)) end end end - defp build_function_by_hash(%{selector: selector} = method) do - args = Macro.generate_arguments(length(method.inputs), __MODULE__) - # TODO: use param encoding to encode things here + defp build_function_by_hash(%{method_id: selector} = method) do + args = method + |> Map.get(:types) + |> length() + |> Macro.generate_arguments(__MODULE__) + + bound_method = Macro.escape(method) quote do - fn types, unquote_splicing(args), amount -> - EthClient.invoke_by_selector(unquote(selector), types, unquote(args), amount) + fn unquote_splicing(args), amount -> + EthClient.invoke_by_selector(unquote(bound_method), unquote(args), amount) end end end From f3c85188880761608d4d73862b9c9b013fb0e2f5 Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Fri, 27 May 2022 14:50:14 -0300 Subject: [PATCH 09/14] fix introduced bug in plocal ABI parsing and fix method encoding --- eth_client/lib/eth_client.ex | 8 +++++--- eth_client/lib/eth_client/contract.ex | 3 +-- eth_client/priv/decompile.py | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/eth_client/lib/eth_client.ex b/eth_client/lib/eth_client.ex index 68f7b9d..0dec8e6 100644 --- a/eth_client/lib/eth_client.ex +++ b/eth_client/lib/eth_client.ex @@ -83,6 +83,8 @@ defmodule EthClient do def call_by_selector(selector, arguments) do encoded_arguments = ABI.TypeEncoder.encode_raw(arguments, selector.types) + |> Base.encode16(case: :lower) + data = selector.method_id <> encoded_arguments %{ @@ -104,15 +106,15 @@ defmodule EthClient do def invoke_by_selector(selector, arguments, amount) do encoded_arguments = ABI.TypeEncoder.encode_raw(arguments, selector.types) + |> Base.encode16(case: :lower) data = selector.method_id <> encoded_arguments - |> IO.inspect(label: "morbusssy", binaries: :as_strings) invoke_with_data(data, amount) end def invoke(method, arguments, amount) do - data = - ABI.encode(method, arguments) + data = method + |> ABI.encode(arguments) |> Base.encode16(case: :lower) |> add_0x() diff --git a/eth_client/lib/eth_client/contract.ex b/eth_client/lib/eth_client/contract.ex index b9e4de8..4e32241 100644 --- a/eth_client/lib/eth_client/contract.ex +++ b/eth_client/lib/eth_client/contract.ex @@ -24,12 +24,11 @@ defmodule EthClient.Contract do state_mutability: method_map["stateMutability"], inputs: Enum.map(method_map["inputs"], fn input -> input["name"] end), # What's the difference between type and internal type? - input_types: Enum.map(method_map["inputs"], fn input -> input["name"] end) + input_types: Enum.map(method_map["inputs"], fn input -> input["internalType"] end) } name_snake_case = String.to_atom(Macro.underscore(name)) function = build_function(method) - # TODO: use hashes to make the function call acc = Map.put(acc, name_snake_case, Code.eval_quoted(function) |> elem(0)) parse_abi(tail, acc) diff --git a/eth_client/priv/decompile.py b/eth_client/priv/decompile.py index 6e5265d..b208915 100644 --- a/eth_client/priv/decompile.py +++ b/eth_client/priv/decompile.py @@ -8,6 +8,7 @@ def get_hash(function_def): func['name'] = function_def['abi_name'] if function_def['payable']: + # This needs some work func['stateMutability'] = "payable" else: func['stateMutability'] = "nonpayable" From b0a7caa38b2782627cf6a7f113b266a1b92e0d7e Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Fri, 27 May 2022 15:39:06 -0300 Subject: [PATCH 10/14] fix style --- eth_client/lib/eth_client.ex | 16 +++++++----- eth_client/lib/eth_client/abi.ex | 35 +++++++++++++++------------ eth_client/lib/eth_client/contract.ex | 29 ++++++++++++---------- 3 files changed, 45 insertions(+), 35 deletions(-) diff --git a/eth_client/lib/eth_client.ex b/eth_client/lib/eth_client.ex index 0dec8e6..f8336ab 100644 --- a/eth_client/lib/eth_client.ex +++ b/eth_client/lib/eth_client.ex @@ -82,8 +82,9 @@ defmodule EthClient do end def call_by_selector(selector, arguments) do - encoded_arguments = ABI.TypeEncoder.encode_raw(arguments, selector.types) - |> Base.encode16(case: :lower) + encoded_arguments = + ABI.TypeEncoder.encode_raw(arguments, selector.types) + |> Base.encode16(case: :lower) data = selector.method_id <> encoded_arguments @@ -105,20 +106,23 @@ defmodule EthClient do end def invoke_by_selector(selector, arguments, amount) do - encoded_arguments = ABI.TypeEncoder.encode_raw(arguments, selector.types) - |> Base.encode16(case: :lower) + encoded_arguments = + ABI.TypeEncoder.encode_raw(arguments, selector.types) + |> Base.encode16(case: :lower) + data = selector.method_id <> encoded_arguments invoke_with_data(data, amount) end def invoke(method, arguments, amount) do - data = method + data = + method |> ABI.encode(arguments) |> Base.encode16(case: :lower) |> add_0x() - invoke_with_data(data, amount) + invoke_with_data(data, amount) end defp invoke_with_data(data, amount) do diff --git a/eth_client/lib/eth_client/abi.ex b/eth_client/lib/eth_client/abi.ex index b9da95e..cce4281 100644 --- a/eth_client/lib/eth_client/abi.ex +++ b/eth_client/lib/eth_client/abi.ex @@ -17,19 +17,20 @@ defmodule EthClient.ABI do def to_selector(function_def) do selector = ABI.FunctionSelector.decode(function_def["name"]) - selector - |> Map.put(:method_id, function_def["selector"]) - |> Map.put(:state_mutability, function_def["stateMutability"]) + selector + |> Map.put(:method_id, function_def["selector"]) + |> Map.put(:state_mutability, function_def["stateMutability"]) end defp filter_unnamed(function_def) do - "0x" <> function_hash = function_def.method_id - unknown_name = "unknown" <> function_hash - case Map.get(function_def, :function) do - ^unknown_name -> Map.delete(function_def, :function) - _ -> function_def - end + "0x" <> function_hash = function_def.method_id + unknown_name = "unknown" <> function_hash + + case Map.get(function_def, :function) do + ^unknown_name -> Map.delete(function_def, :function) + _ -> function_def + end end def get_non_etherscan(address) do @@ -38,19 +39,21 @@ defmodule EthClient.ABI do case System.cmd("python3", [decode_path, bytecode]) do {hashes, 0} -> - {:ok, funclist} = hashes - |> Jason.decode() + {:ok, funclist} = + hashes + |> Jason.decode() - funclist = funclist - |> Enum.filter(fn %{"selector" => hash} -> hash != "_fallback()" end) - |> Enum.map(&to_selector/1) - |> Enum.map(&filter_unnamed/1) + funclist = + funclist + |> Enum.filter(fn %{"selector" => hash} -> hash != "_fallback()" end) + |> Enum.map(&to_selector/1) + |> Enum.map(&filter_unnamed/1) {:ok, funclist} + {_, _} -> {:error, :abi_unavailable} end - end defp get_etherscan(address) do diff --git a/eth_client/lib/eth_client/contract.ex b/eth_client/lib/eth_client/contract.ex index 4e32241..e94e529 100644 --- a/eth_client/lib/eth_client/contract.ex +++ b/eth_client/lib/eth_client/contract.ex @@ -6,7 +6,7 @@ defmodule EthClient.Contract do defstruct [:address, :functions] - def get_functions, do: EthClient.Context.contract.functions + def get_functions, do: EthClient.Context.contract().functions def get_functions(address_or_path) do with {:ok, abi} <- ABI.get(address_or_path) do @@ -37,9 +37,10 @@ defmodule EthClient.Contract do defp parse_abi([%{function: name} = method_map | tail], acc) do function = build_function_by_hash(method_map) - selector_atom = name - |> Macro.underscore() - |> String.to_atom() + selector_atom = + name + |> Macro.underscore() + |> String.to_atom() acc = Map.put(acc, selector_atom, Code.eval_quoted(function) |> elem(0)) @@ -49,8 +50,9 @@ defmodule EthClient.Contract do defp parse_abi([%{type: function, method_id: selector} = method_map | tail], acc) do function = build_function_by_hash(method_map) - selector_atom = selector - |> String.to_atom() + selector_atom = + selector + |> String.to_atom() acc = Map.put(acc, selector_atom, Code.eval_quoted(function) |> elem(0)) @@ -62,8 +64,9 @@ defmodule EthClient.Contract do end defp build_function_by_hash(%{method_id: selector, state_mutability: mutability} = method) - when mutability in ["pure", "view"] do - args = method.types + when mutability in ["pure", "view"] do + args = + method.types |> length() |> Macro.generate_arguments(__MODULE__) @@ -77,10 +80,11 @@ defmodule EthClient.Contract do end defp build_function_by_hash(%{method_id: selector} = method) do - args = method - |> Map.get(:types) - |> length() - |> Macro.generate_arguments(__MODULE__) + args = + method + |> Map.get(:types) + |> length() + |> Macro.generate_arguments(__MODULE__) bound_method = Macro.escape(method) @@ -113,5 +117,4 @@ defmodule EthClient.Contract do end end end - end From 6c66769ed09a89deea6fd5af6a758003922ad49a Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Fri, 3 Jun 2022 16:45:58 -0300 Subject: [PATCH 11/14] address comments and fix warnings --- eth_client/lib/eth_client/abi.ex | 4 +--- eth_client/lib/eth_client/contract.ex | 12 +++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/eth_client/lib/eth_client/abi.ex b/eth_client/lib/eth_client/abi.ex index cce4281..c88b6b2 100644 --- a/eth_client/lib/eth_client/abi.ex +++ b/eth_client/lib/eth_client/abi.ex @@ -16,9 +16,7 @@ defmodule EthClient.ABI do def get(abi_path), do: get_local(abi_path) def to_selector(function_def) do - selector = ABI.FunctionSelector.decode(function_def["name"]) - - selector + ABI.FunctionSelector.decode(function_def["name"]) |> Map.put(:method_id, function_def["selector"]) |> Map.put(:state_mutability, function_def["stateMutability"]) end diff --git a/eth_client/lib/eth_client/contract.ex b/eth_client/lib/eth_client/contract.ex index e94e529..3e6f258 100644 --- a/eth_client/lib/eth_client/contract.ex +++ b/eth_client/lib/eth_client/contract.ex @@ -42,19 +42,21 @@ defmodule EthClient.Contract do |> Macro.underscore() |> String.to_atom() - acc = Map.put(acc, selector_atom, Code.eval_quoted(function) |> elem(0)) + {term, _bind} = Code.eval_quoted(function) + acc = Map.put(acc, selector_atom, term) parse_abi(tail, acc) end - defp parse_abi([%{type: function, method_id: selector} = method_map | tail], acc) do + defp parse_abi([%{type: _function, method_id: selector} = method_map | tail], acc) do function = build_function_by_hash(method_map) selector_atom = selector |> String.to_atom() - acc = Map.put(acc, selector_atom, Code.eval_quoted(function) |> elem(0)) + {term, _bind} = Code.eval_quoted(function) + acc = Map.put(acc, selector_atom, term) parse_abi(tail, acc) end @@ -63,7 +65,7 @@ defmodule EthClient.Contract do parse_abi(tail, acc) end - defp build_function_by_hash(%{method_id: selector, state_mutability: mutability} = method) + defp build_function_by_hash(%{method_id: _selector, state_mutability: mutability} = method) when mutability in ["pure", "view"] do args = method.types @@ -79,7 +81,7 @@ defmodule EthClient.Contract do end end - defp build_function_by_hash(%{method_id: selector} = method) do + defp build_function_by_hash(%{method_id: _selector} = method) do args = method |> Map.get(:types) From 217375eb88b80a4f1f450dee9b9b1662766e25ad Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:30:45 -0300 Subject: [PATCH 12/14] use 0 as default amount in invoke --- eth_client/lib/eth_client.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth_client/lib/eth_client.ex b/eth_client/lib/eth_client.ex index f8336ab..9cc4dfc 100644 --- a/eth_client/lib/eth_client.ex +++ b/eth_client/lib/eth_client.ex @@ -105,7 +105,7 @@ defmodule EthClient do wei_to_ether(balance) end - def invoke_by_selector(selector, arguments, amount) do + def invoke_by_selector(selector, arguments, amount \\ 0) do encoded_arguments = ABI.TypeEncoder.encode_raw(arguments, selector.types) |> Base.encode16(case: :lower) @@ -115,7 +115,7 @@ defmodule EthClient do invoke_with_data(data, amount) end - def invoke(method, arguments, amount) do + def invoke(method, arguments, amount \\ 0) do data = method |> ABI.encode(arguments) From 76aa3955b504eb97e5dcc9415da091d5d81eb313 Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:58:51 -0300 Subject: [PATCH 13/14] format --- eth_client/lib/eth_client/contract.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eth_client/lib/eth_client/contract.ex b/eth_client/lib/eth_client/contract.ex index 6c20643..07b0c3f 100644 --- a/eth_client/lib/eth_client/contract.ex +++ b/eth_client/lib/eth_client/contract.ex @@ -81,8 +81,8 @@ defmodule EthClient.Contract do |> Macro.underscore() |> String.to_atom() - {term, _bind} = Code.eval_quoted(function) - acc = Map.put(acc, selector_atom, term) + {term, _bind} = Code.eval_quoted(function) + acc = Map.put(acc, selector_atom, term) parse_abi(tail, acc) end @@ -94,8 +94,8 @@ defmodule EthClient.Contract do selector |> String.to_atom() - {term, _bind} = Code.eval_quoted(function) - acc = Map.put(acc, selector_atom, term) + {term, _bind} = Code.eval_quoted(function) + acc = Map.put(acc, selector_atom, term) parse_abi(tail, acc) end From ab1251bbc05121c95a37f0455e72c3d35c8d5bbb Mon Sep 17 00:00:00 2001 From: mfachal <647731+mfachal@users.noreply.github.com> Date: Mon, 6 Jun 2022 16:27:02 -0300 Subject: [PATCH 14/14] address comments --- eth_client/lib/eth_client/abi.ex | 10 ++++------ eth_client/lib/eth_client/contract.ex | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/eth_client/lib/eth_client/abi.ex b/eth_client/lib/eth_client/abi.ex index 062fb3a..9e56c55 100644 --- a/eth_client/lib/eth_client/abi.ex +++ b/eth_client/lib/eth_client/abi.ex @@ -15,7 +15,8 @@ defmodule EthClient.ABI do def get(abi_path), do: get_local(abi_path) def to_selector(function_def) do - ABI.FunctionSelector.decode(function_def["name"]) + function_def["name"] + |> ABI.FunctionSelector.decode() |> Map.put(:method_id, function_def["selector"]) |> Map.put(:state_mutability, function_def["stateMutability"]) end @@ -36,12 +37,9 @@ defmodule EthClient.ABI do case System.cmd("python3", [decode_path, bytecode]) do {hashes, 0} -> - {:ok, funclist} = - hashes - |> Jason.decode() - funclist = - funclist + hashes + |> Jason.decode!() |> Enum.filter(fn %{"selector" => hash} -> hash != "_fallback()" end) |> Enum.map(&to_selector/1) |> Enum.map(&filter_unnamed/1) diff --git a/eth_client/lib/eth_client/contract.ex b/eth_client/lib/eth_client/contract.ex index 07b0c3f..c3f96ca 100644 --- a/eth_client/lib/eth_client/contract.ex +++ b/eth_client/lib/eth_client/contract.ex @@ -104,7 +104,7 @@ defmodule EthClient.Contract do parse_abi(tail, acc) end - defp build_function_by_hash(%{method_id: _selector, state_mutability: mutability} = method) + defp build_function_by_hash(%{state_mutability: mutability} = method) when mutability in ["pure", "view"] do args = method.types