4
0
Fork 0

Merge pull request #26 from hswick/optimize

0.4.0 release
travis-fix
Harley Swick 6 years ago committed by GitHub
commit 376e4ef910
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 36
      README.md
  2. 4
      config/config.exs
  3. 162
      lib/exw3.ex
  4. 6
      mix.exs
  5. 8
      mix.lock
  6. 49
      test/exw3_test.exs

@ -11,7 +11,7 @@ end
```
## Overview
ExW3 is a wrapper around ethereumex to provide a high level, user friendly json rpc api. It currently ONLY supports http. The primary feature of this library is a handy abstraction for working with smart contracts.
ExW3 is a wrapper around ethereumex to provide a high level, user friendly json rpc api. This library is focused on providing a handy abstraction for working with smart contracts, and any other relevant utilities.
## Usage
@ -34,15 +34,33 @@ If Parity complains about password or missing account, try
parity --chain dev --unlock=0x00a329c0648769a73afac7f9381e08fb43dbea72
```
Make sure your config includes:
### Http
To use Ethereumex's HttpClient simply set your config like this:
```elixir
config :ethereumex,
client_type: :http,
url: "http://localhost:8545"
```
Currently, ExW3 supports a handful of json rpc commands. Mostly just the useful ones. If it doesn't support a specific commands you can always use the [Ethereumex](https://github.com/exthereum/ethereumex) commands.
### Ipc
If you want to use IpcClient set your config to something like this:
```elixir
config :ethereumex,
client_type: :ipc,
ipc_path: "/.local/share/io.parity.ethereum/jsonrpc.ipc"
```
Provide an absolute path to the ipc socket provided by whatever Ethereum client you are running. You don't need to include the home directory, as that will be prepended to the path provided.
Check out the [documentation](https://hexdocs.pm/exw3/ExW3.html)
* NOTE * Use of Ipc is recommended, as it is more secure and significantly faster.
Currently, ExW3 supports a handful of json rpc commands. Primarily the ones that get used the most. If ExW3 doesn't provide a specific command, you can always use the [Ethereumex](https://github.com/exthereum/ethereumex) commands.
Check out the [documentation](https://hexdocs.pm/exw3/ExW3.html) for more details of the API.
### Example
```elixir
iex(1)> accounts = ExW3.accounts()
@ -89,13 +107,11 @@ iex(11)> ExW3.Contract.call(:SimpleStorage, :get)
{:ok, 1}
```
## Asynchronous
## Address Type
ExW3 provides async versions of `call` and `send`. They both return a `Task` that can be awaited on.
```elixir
t = ExW3.Contract.call_async(:SimpleStorage, :get)
{:ok, data} = Task.await(t)
If you are familiar with web3.js you may find the way ExW3 handles addresses unintuitive. ExW3's abi encoder interprets the address type as an uint160. If you are using an address as an option to a transaction like `:from` or `:to` this will work as expected. However, if one of your smart contracts is expecting an address type for an input parameter then you will need to do this:
```
a = ExW3.to_decimal("0x88838e84a401a1d6162290a1a765507c4a83f5e050658a83992a912f42149ca5")
```
## Events

@ -25,4 +25,6 @@ use Mix.Config
#
# import_config "#{Mix.env}.exs"
config :ethereumex,
url: "http://localhost:8545"
url: "http://localhost:8545",
client_type: :ipc,
ipc_path: "/.local/share/io.parity.ethereum/jsonrpc.ipc"

@ -31,6 +31,8 @@ defmodule ExW3 do
:tether => 1_000_000_000_000_000_000_000_000_000_000
}
@client_type Application.get_env(:ethereumex, :client_type, :http)
@spec get_unit_map() :: %{}
@doc "Returns the map used for ether unit conversion"
def get_unit_map do
@ -117,10 +119,18 @@ defmodule ExW3 do
to_checksum_address(address) == address
end
defp call_client(method_name, arguments \\ []) do
case @client_type do
:http -> apply(Ethereumex.HttpClient, method_name, arguments)
:ipc -> apply(Ethereumex.IpcClient, method_name, arguments)
_ -> {:error, :invalid_client_type}
end
end
@spec accounts() :: list()
@doc "returns all available accounts"
def accounts do
case Ethereumex.HttpClient.eth_accounts() do
case call_client(:eth_accounts) do
{:ok, accounts} -> accounts
err -> err
end
@ -137,7 +147,7 @@ defmodule ExW3 do
@spec block_number() :: integer()
@doc "Returns the current block number"
def block_number do
case Ethereumex.HttpClient.eth_block_number() do
case call_client(:eth_block_number) do
{:ok, block_number} ->
block_number |> to_decimal
@ -149,7 +159,7 @@ defmodule ExW3 do
@spec balance(binary()) :: integer()
@doc "Returns current balance of account"
def balance(account) do
case Ethereumex.HttpClient.eth_get_balance(account) do
case call_client(:eth_get_balance, [account]) do
{:ok, balance} ->
balance |> to_decimal
@ -170,22 +180,23 @@ defmodule ExW3 do
@spec tx_receipt(binary()) :: %{}
@doc "Returns transaction receipt for specified transaction hash(id)"
def tx_receipt(tx_hash) do
case Ethereumex.HttpClient.eth_get_transaction_receipt(tx_hash) do
case call_client(:eth_get_transaction_receipt, [tx_hash]) do
{:ok, receipt} ->
Map.merge(
receipt,
keys_to_decimal(receipt, ["blockNumber", "cumulativeGasUsed", "gasUsed"])
)
{:ok,
Map.merge(
receipt,
keys_to_decimal(receipt, ["blockNumber", "cumulativeGasUsed", "gasUsed"])
)}
err ->
err
{:error, err}
end
end
@spec block(integer()) :: any()
@doc "Returns block data for specified block number"
def block(block_number) do
case Ethereumex.HttpClient.eth_get_block_by_number(block_number, true) do
case call_client(:eth_get_block_by_number, [block_number, true]) do
{:ok, block} -> block
err -> err
end
@ -194,7 +205,7 @@ defmodule ExW3 do
@spec new_filter(%{}) :: binary()
@doc "Creates a new filter, returns filter id. For more sophisticated use, prefer ExW3.Contract.filter."
def new_filter(map) do
case Ethereumex.HttpClient.eth_new_filter(map) do
case call_client(:eth_new_filter, [map]) do
{:ok, filter_id} -> filter_id
err -> err
end
@ -203,7 +214,7 @@ defmodule ExW3 do
@spec get_filter_changes(binary()) :: any()
@doc "Gets event changes (logs) by filter. Unlike ExW3.Contract.get_filter_changes it does not return the data in a formatted way"
def get_filter_changes(filter_id) do
case Ethereumex.HttpClient.eth_get_filter_changes(filter_id) do
case call_client(:eth_get_filter_changes, [filter_id]) do
{:ok, changes} -> changes
err -> err
end
@ -212,7 +223,7 @@ defmodule ExW3 do
@spec uninstall_filter(binary()) :: boolean()
@doc "Uninstalls filter from the ethereum node"
def uninstall_filter(filter_id) do
case Ethereumex.HttpClient.eth_uninstall_filter(filter_id) do
case call_client(:eth_uninstall_filter, [filter_id]) do
{:ok, result} -> result
err -> err
end
@ -222,7 +233,7 @@ defmodule ExW3 do
@doc "Mines number of blocks specified. Default is 1"
def mine(num_blocks \\ 1) do
for _ <- 0..(num_blocks - 1) do
Ethereumex.HttpClient.request("evm_mine", [], [])
call_client(:request, ["evm_mine", [], []])
end
end
@ -232,6 +243,18 @@ defmodule ExW3 do
ExthCrypto.Hash.Keccak.kec(signature) |> Base.encode16(case: :lower)
end
@spec eth_call([]) :: any()
@doc "Simple eth_call to client. Recommended to use ExW3.Contract.call instead."
def eth_call(arguments) do
call_client(:eth_call, arguments)
end
@spec eth_send([]) :: any()
@doc "Simple eth_send_transaction. Recommended to use ExW3.Contract.send instead."
def eth_send(arguments) do
call_client(:eth_send_transaction, arguments)
end
@spec decode_event(binary(), binary()) :: any()
@doc "Decodes event based on given data and provided signature"
def decode_event(data, signature) do
@ -259,7 +282,7 @@ defmodule ExW3 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
@ -414,7 +437,7 @@ defmodule ExW3 do
@doc "Uninstalls the filter, and deletes the data associated with the filter id"
def uninstall_filter(filter_id) do
GenServer.cast(ContractManager, {:uninstall_filter, filter_id})
end
end
@spec at(keyword(), binary()) :: :ok
@doc "Sets the address for the contract specified by the name argument"
@ -440,22 +463,6 @@ defmodule ExW3 do
GenServer.call(ContractManager, {:send, {contract_name, method_name, args, options}})
end
@spec call_async(keyword(), keyword(), []) :: {:ok, any()}
@doc "Use a Contract's method with an eth_call. Returns a Task to be awaited."
def call_async(contract_name, method_name, args \\ []) do
Task.async(fn ->
GenServer.call(ContractManager, {:call, {contract_name, method_name, args}})
end)
end
@spec send_async(keyword(), keyword(), [], %{}) :: {:ok, binary()}
@doc "Use a Contract's method with an eth_sendTransaction. Returns a Task to be awaited."
def send_async(contract_name, method_name, args, options) do
Task.async(fn ->
GenServer.call(ContractManager, {:send, {contract_name, method_name, args, options}})
end)
end
@spec tx_receipt(keyword(), binary()) :: %{}
@doc "Returns a formatted transaction receipt for the given transaction hash(id)"
def tx_receipt(contract_name, tx_hash) do
@ -475,11 +482,11 @@ defmodule ExW3 do
@doc "Using saved information related to the filter id, event logs are formatted properly"
def get_filter_changes(filter_id) do
GenServer.call(
ContractManager,
{:get_filter_changes, filter_id}
ContractManager,
{:get_filter_changes, filter_id}
)
end
# Server
def init(state) do
@ -557,8 +564,8 @@ defmodule ExW3 do
end)
[
events: Enum.into(signature_types_map, %{}),
event_names: Enum.into(names_map, %{})
events: Enum.into(signature_types_map, %{}),
event_names: Enum.into(names_map, %{})
]
end
@ -603,19 +610,21 @@ defmodule ExW3 do
gas: gas
}
{:ok, tx_hash} = Ethereumex.HttpClient.eth_send_transaction(tx)
{:ok, tx_hash} = ExW3.eth_send([tx])
{:ok, tx_receipt} = Ethereumex.HttpClient.eth_get_transaction_receipt(tx_hash)
{:ok, tx_receipt} = ExW3.tx_receipt(tx_hash)
{tx_receipt["contractAddress"], tx_hash}
end
def eth_call_helper(address, abi, method_name, args) do
result =
Ethereumex.HttpClient.eth_call(%{
to: address,
data: "0x#{ExW3.encode_method_call(abi, method_name, args)}"
})
ExW3.eth_call([
%{
to: address,
data: "0x#{ExW3.encode_method_call(abi, method_name, args)}"
}
])
case result do
{:ok, data} -> ([:ok] ++ ExW3.decode_output(abi, method_name, data)) |> List.to_tuple()
@ -626,7 +635,7 @@ defmodule ExW3 do
def eth_send_helper(address, abi, method_name, args, options) do
gas = ExW3.encode_option(options[:gas])
Ethereumex.HttpClient.eth_send_transaction(
ExW3.eth_send([
Map.merge(
%{
to: address,
@ -634,12 +643,12 @@ defmodule ExW3 do
},
Map.put(options, :gas, gas)
)
)
])
end
defp register_helper(contract_info) do
if contract_info[:abi] do
contract_info ++ init_events(contract_info[:abi])
contract_info ++ init_events(contract_info[:abi])
else
raise "ABI not provided upon initialization"
end
@ -785,7 +794,7 @@ defmodule ExW3 do
new_data = Map.merge(indexed_fields, non_indexed_fields)
Map.put(log, "data", new_data)
end
end
def handle_call({:filter, {contract_name, event_name, event_data}}, _from, state) do
contract_info = state[contract_name]
@ -804,38 +813,47 @@ defmodule ExW3 do
filter_id = ExW3.new_filter(payload)
{:reply, {:ok, filter_id}, Map.put(state, :filters, Map.put(state[:filters], filter_id, %{contract_name: contract_name, event_name: event_name}))}
{:reply, {:ok, filter_id},
Map.put(
state,
:filters,
Map.put(state[:filters], filter_id, %{
contract_name: contract_name,
event_name: event_name
})
)}
end
def handle_call({:get_filter_changes, filter_id}, _from, state) do
filter_info = Map.get(state[:filters], filter_id)
event_attributes = get_event_attributes(state, filter_info[:contract_name], filter_info[:event_name])
event_attributes =
get_event_attributes(state, filter_info[:contract_name], filter_info[:event_name])
logs = ExW3.get_filter_changes(filter_id)
formatted_logs =
if logs != [] do
Enum.map(logs, fn log ->
formatted_log =
Enum.reduce(
[
ExW3.keys_to_decimal(log, [
"blockNumber",
"logIndex",
"transactionIndex",
"transactionLogIndex"
]),
format_log_data(log, event_attributes)
],
&Map.merge/2
)
formatted_log
end)
else
logs
end
if logs != [] do
Enum.map(logs, fn log ->
formatted_log =
Enum.reduce(
[
ExW3.keys_to_decimal(log, [
"blockNumber",
"logIndex",
"transactionIndex",
"transactionLogIndex"
]),
format_log_data(log, event_attributes)
],
&Map.merge/2
)
formatted_log
end)
else
logs
end
{:reply, {:ok, formatted_logs}, state}
end
@ -893,7 +911,7 @@ defmodule ExW3 do
def handle_call({:tx_receipt, {contract_name, tx_hash}}, _from, state) do
contract_info = state[contract_name]
receipt = ExW3.tx_receipt(tx_hash)
{:ok, receipt} = ExW3.tx_receipt(tx_hash)
events = contract_info[:events]
logs = receipt["logs"]

@ -4,7 +4,7 @@ defmodule ExW3.MixProject do
def project do
[
app: :exw3,
version: "0.3.0",
version: "0.4.0",
elixir: "~> 1.7.2",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
@ -35,9 +35,9 @@ defmodule ExW3.MixProject do
defp deps do
[
{:ex_doc, ">= 0.0.0", only: :dev},
{:ethereumex, "~> 0.3.4"},
{:ethereumex, "~> 0.4.0"},
{:abi, "~> 0.1.8"},
{:poison, "~> 3.1"}
{:poison, "~> 4.0.1"}
]
end

@ -3,11 +3,11 @@
"binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], [], "hexpm"},
"certifi": {:hex, :certifi, "2.4.2", "75424ff0f3baaccfd34b1214184b6ef616d89e420b258bb0a5ea7d7bc628f7f0", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"},
"ethereumex": {:hex, :ethereumex, "0.3.4", "5222472508bf7de46e06590e85da5d7fe49d33a386ecabc71c364c439a99945e", [:mix], [{:httpoison, "~> 1.1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"ethereumex": {:hex, :ethereumex, "0.4.0", "6ed98639c717dc7940a56f657fb81f44afe3e0287f9825b7b0f9016642e605ac", [:mix], [{:httpoison, "~> 1.3.1", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 4.0.1", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"exth_crypto": {:hex, :exth_crypto, "0.1.6", "8e636a9bcb75d8e32451be96e547a495121ed2178d078db294edb0f81f7cf2e8", [:mix], [{:binary, "~> 0.0.4", [hex: :binary, repo: "hexpm", optional: false]}, {:keccakf1600, "~> 2.0.0", [hex: :keccakf1600_orig, repo: "hexpm", optional: false]}, {:libsecp256k1, "~> 0.1.9", [hex: :libsecp256k1, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.14.0", "66e29e78feba52176c3a4213d42b29bdc4baff93a18cfe480f73b04677139dee", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "1.1.1", "96ed7ab79f78a31081bb523eefec205fd2900a02cda6dbc2300e7a1226219566", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.14.3", "b5f6f5dcc4f1fba340762738759209e21914516df6be440d85772542d4a5e412", [:rebar3], [{:certifi, "2.4.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "1.3.1", "7ac607311f5f706b44e8b3fab736d0737f2f62a31910ccd9afe7227b43edb7f0", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"keccakf1600": {:hex, :keccakf1600_orig, "2.0.0", "0a7217ddb3ee8220d449bbf7575ec39d4e967099f220a91e3dfca4dbaef91963", [:rebar3], [], "hexpm"},
"libsecp256k1": {:hex, :libsecp256k1, "0.1.9", "e725f31364cda7b554d56ce2bb976241303dde5ffd1ad59598513297bf1f2af6", [:make, :mix], [{:mix_erlang_tasks, "0.1.0", [hex: :mix_erlang_tasks, repo: "hexpm", optional: false]}], "hexpm"},
@ -18,7 +18,7 @@
"mix_erlang_tasks": {:hex, :mix_erlang_tasks, "0.1.0", "36819fec60b80689eb1380938675af215565a89320a9e29c72c70d97512e4649", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.2.2", "d526b23bdceb04c7ad15b33c57c4526bf5f50aaa70c7c141b4b4624555c68259", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
}

@ -186,9 +186,8 @@ defmodule EXW3Test do
end
test "Testing formatted get filter changes", context do
ExW3.Contract.register(:EventTester, abi: context[:event_tester_abi])
{:ok, address, _} =
ExW3.Contract.deploy(
:EventTester,
@ -240,7 +239,7 @@ defmodule EXW3Test do
{:ok, change_logs} = ExW3.Contract.get_filter_changes(indexed_filter_id)
event_log = Enum.at(change_logs, 0)
assert event_log |> is_map
log_data = Map.get(event_log, "data")
assert log_data |> is_map
@ -279,7 +278,7 @@ defmodule EXW3Test do
assert Map.get(log_data, "num") == 46
assert ExW3.bytes_to_string(Map.get(log_data, "data")) == "Hello, World!"
assert Map.get(log_data, "otherNum") == 42
ExW3.Contract.uninstall_filter(indexed_filter_id)
# Tests filter with map params
@ -311,9 +310,8 @@ defmodule EXW3Test do
assert Map.get(log_data, "num") == 46
assert ExW3.bytes_to_string(Map.get(log_data, "data")) == "Hello, World!"
assert Map.get(log_data, "otherNum") == 42
ExW3.Contract.uninstall_filter(indexed_filter_id)
end
test "starts a Contract GenServer for Complex contract", context do
@ -528,43 +526,4 @@ defmodule EXW3Test do
assert ExW3.from_wei(1_000_000_000_000_000_000, :gether) == 0.000000001
assert ExW3.from_wei(1_000_000_000_000_000_000, :tether) == 0.000000000001
end
test "send and call sync with SimpleStorage", context do
ExW3.Contract.register(:SimpleStorage, abi: context[:simple_storage_abi])
{:ok, address, _} =
ExW3.Contract.deploy(
:SimpleStorage,
bin: ExW3.load_bin("test/examples/build/SimpleStorage.bin"),
args: [],
options: %{
gas: 300_000,
from: Enum.at(context[:accounts], 0)
}
)
ExW3.Contract.at(:SimpleStorage, address)
assert address == ExW3.Contract.address(:SimpleStorage)
t = ExW3.Contract.call_async(:SimpleStorage, :get)
{:ok, data} = Task.await(t)
assert data == 0
t =
ExW3.Contract.send_async(:SimpleStorage, :set, [1], %{
from: Enum.at(context[:accounts], 0),
gas: 50_000
})
Task.await(t)
t = ExW3.Contract.call_async(:SimpleStorage, :get)
{:ok, data} = Task.await(t)
assert data == 1
end
end

Loading…
Cancel
Save