|
|
|
@ -1,6 +1,5 @@ |
|
|
|
|
defmodule ExW3 do |
|
|
|
|
|
|
|
|
|
Module.register_attribute __MODULE__, :unit_map, persist: true, accumulate: false |
|
|
|
|
Module.register_attribute(__MODULE__, :unit_map, persist: true, accumulate: false) |
|
|
|
|
|
|
|
|
|
@unit_map %{ |
|
|
|
|
:noether => 0, |
|
|
|
@ -42,7 +41,7 @@ defmodule ExW3 do |
|
|
|
|
if @unit_map[key] do |
|
|
|
|
num * @unit_map[key] |
|
|
|
|
else |
|
|
|
|
throw "#{key} not valid unit" |
|
|
|
|
throw("#{key} not valid unit") |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
@ -50,7 +49,7 @@ defmodule ExW3 do |
|
|
|
|
if @unit_map[key] do |
|
|
|
|
num / @unit_map[key] |
|
|
|
|
else |
|
|
|
|
throw "#{key} not valid unit" |
|
|
|
|
throw("#{key} not valid unit") |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
@ -75,7 +74,7 @@ defmodule ExW3 do |
|
|
|
|
address |
|
|
|
|
|> String.slice(2..-1) |
|
|
|
|
|> Base.decode16!(case: :lower) |
|
|
|
|
|> :binary.decode_unsigned |
|
|
|
|
|> :binary.decode_unsigned() |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
@spec to_address(binary()) :: binary() |
|
|
|
@ -89,16 +88,20 @@ defmodule ExW3 do |
|
|
|
|
def to_checksum_address(address) do |
|
|
|
|
address = String.replace(address, ~r/^0x/, "") |
|
|
|
|
|
|
|
|
|
hash = ExthCrypto.Hash.Keccak.kec(String.downcase(address)) |
|
|
|
|
hash = |
|
|
|
|
ExthCrypto.Hash.Keccak.kec(String.downcase(address)) |
|
|
|
|
|> Base.encode16(case: :lower) |
|
|
|
|
|> String.replace(~r/^0x/, "") |
|
|
|
|
|
|
|
|
|
keccak_hash_list = hash |
|
|
|
|
keccak_hash_list = |
|
|
|
|
hash |
|
|
|
|
|> String.split("", trim: true) |
|
|
|
|
|> Enum.map(fn (x) -> elem(Integer.parse(x, 16),0) end) |
|
|
|
|
|> Enum.map(fn x -> elem(Integer.parse(x, 16), 0) end) |
|
|
|
|
|
|
|
|
|
list_arr = for n <- 0..String.length(address)-1 do |
|
|
|
|
list_arr = |
|
|
|
|
for n <- 0..(String.length(address) - 1) do |
|
|
|
|
number = Enum.at(keccak_hash_list, n) |
|
|
|
|
|
|
|
|
|
cond do |
|
|
|
|
number >= 8 -> String.upcase(String.at(address, n)) |
|
|
|
|
true -> String.downcase(String.at(address, n)) |
|
|
|
@ -242,11 +245,9 @@ defmodule ExW3 do |
|
|
|
|
@spec reformat_abi([]) :: %{} |
|
|
|
|
@doc "Reformats abi from list to map with event and function names as keys" |
|
|
|
|
def reformat_abi(abi) do |
|
|
|
|
|
|
|
|
|
abi |
|
|
|
|
|> Enum.map(&map_abi/1) |
|
|
|
|
|> Map.new |
|
|
|
|
|
|
|
|
|
|> Map.new() |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
@spec load_abi(binary()) :: [] |
|
|
|
@ -274,8 +275,7 @@ defmodule ExW3 do |
|
|
|
|
@spec decode_data(binary(), binary()) :: any() |
|
|
|
|
@doc "Decodes data based on given type signature" |
|
|
|
|
def decode_data(types_signature, data) do |
|
|
|
|
{:ok, trim_data} = |
|
|
|
|
String.slice(data, 2..String.length(data)) |> Base.decode16(case: :lower) |
|
|
|
|
{:ok, trim_data} = String.slice(data, 2..String.length(data)) |> Base.decode16(case: :lower) |
|
|
|
|
|
|
|
|
|
ABI.decode(types_signature, trim_data) |> List.first() |
|
|
|
|
end |
|
|
|
@ -334,7 +334,11 @@ defmodule ExW3 do |
|
|
|
|
def encode_option(0), do: "0x0" |
|
|
|
|
|
|
|
|
|
def encode_option(value) do |
|
|
|
|
"0x" <> (value |> :binary.encode_unsigned() |> Base.encode16(case: :lower) |> String.trim_leading("0")) |
|
|
|
|
"0x" <> |
|
|
|
|
(value |
|
|
|
|
|> :binary.encode_unsigned() |
|
|
|
|
|> Base.encode16(case: :lower) |
|
|
|
|
|> String.trim_leading("0")) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
@spec encode_method_call(%{}, binary(), []) :: binary() |
|
|
|
@ -393,7 +397,8 @@ defmodule ExW3 do |
|
|
|
|
|
|
|
|
|
@impl true |
|
|
|
|
def init(state) do |
|
|
|
|
schedule_work() # Schedule work to be performed on start |
|
|
|
|
# Schedule work to be performed on start |
|
|
|
|
schedule_work() |
|
|
|
|
{:ok, state} |
|
|
|
|
end |
|
|
|
|
|
|
|
|
@ -405,16 +410,18 @@ defmodule ExW3 do |
|
|
|
|
@impl true |
|
|
|
|
def handle_info(:work, state) do |
|
|
|
|
# Do the desired work here |
|
|
|
|
Enum.each state, fn filter_id -> |
|
|
|
|
send Listener, {:event, filter_id, ExW3.get_filter_changes(filter_id)} |
|
|
|
|
end |
|
|
|
|
Enum.each(state, fn filter_id -> |
|
|
|
|
send(Listener, {:event, filter_id, ExW3.get_filter_changes(filter_id)}) |
|
|
|
|
end) |
|
|
|
|
|
|
|
|
|
schedule_work() # Reschedule once more |
|
|
|
|
# Reschedule once more |
|
|
|
|
schedule_work() |
|
|
|
|
{:noreply, state} |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
defp schedule_work() do |
|
|
|
|
Process.send_after(self(), :work, 500) # In 1/2 sec |
|
|
|
|
# In 1/2 sec |
|
|
|
|
Process.send_after(self(), :work, 500) |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
@ -428,13 +435,14 @@ defmodule ExW3 do |
|
|
|
|
|
|
|
|
|
def filter(filter_id, event_fields, pid) do |
|
|
|
|
Poller.filter(filter_id) |
|
|
|
|
send Listener, {:filter, filter_id, event_fields, pid} |
|
|
|
|
send(Listener, {:filter, filter_id, event_fields, pid}) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def listen(callback) do |
|
|
|
|
receive do |
|
|
|
|
{:event, result} -> apply callback, [result] |
|
|
|
|
{:event, result} -> apply(callback, [result]) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
listen(callback) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
@ -453,7 +461,9 @@ defmodule ExW3 do |
|
|
|
|
indexed_fields = |
|
|
|
|
if length(log["topics"]) > 1 do |
|
|
|
|
[_head | tail] = log["topics"] |
|
|
|
|
decoded_topics = Enum.map(0..length(tail) - 1, fn i -> |
|
|
|
|
|
|
|
|
|
decoded_topics = |
|
|
|
|
Enum.map(0..(length(tail) - 1), fn i -> |
|
|
|
|
topic_type = Enum.at(event_attributes[:topic_types], i) |
|
|
|
|
topic_data = Enum.at(tail, i) |
|
|
|
|
|
|
|
|
@ -476,6 +486,7 @@ defmodule ExW3 do |
|
|
|
|
receive do |
|
|
|
|
{:filter, filter_id, event_attributes, pid} -> |
|
|
|
|
loop(Map.put(state, filter_id, %{pid: pid, event_attributes: event_attributes})) |
|
|
|
|
|
|
|
|
|
{:event, filter_id, logs} -> |
|
|
|
|
filter_attributes = Map.get(state, filter_id) |
|
|
|
|
event_attributes = filter_attributes[:event_attributes] |
|
|
|
@ -483,19 +494,26 @@ defmodule ExW3 do |
|
|
|
|
unless logs == [] do |
|
|
|
|
Enum.each(logs, fn log -> |
|
|
|
|
formatted_log = |
|
|
|
|
Enum.reduce([ |
|
|
|
|
ExW3.keys_to_decimal(log, ["blockNumber", "logIndex", "transactionIndex", "transactionLogIndex"]), |
|
|
|
|
Enum.reduce( |
|
|
|
|
[ |
|
|
|
|
ExW3.keys_to_decimal(log, [ |
|
|
|
|
"blockNumber", |
|
|
|
|
"logIndex", |
|
|
|
|
"transactionIndex", |
|
|
|
|
"transactionLogIndex" |
|
|
|
|
]), |
|
|
|
|
format_log_data(log, event_attributes) |
|
|
|
|
], |
|
|
|
|
&Map.merge/2) |
|
|
|
|
&Map.merge/2 |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
send filter_attributes[:pid], {:event, {filter_id, formatted_log}} |
|
|
|
|
send(filter_attributes[:pid], {:event, {filter_id, formatted_log}}) |
|
|
|
|
end) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
loop(state) |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
defmodule Contract do |
|
|
|
@ -568,7 +586,10 @@ defmodule ExW3 do |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def filter(contract_name, event_name, other_pid, event_data \\ %{}) do |
|
|
|
|
GenServer.call(ContractManager, {:filter, {contract_name, event_name, other_pid, event_data}}) |
|
|
|
|
GenServer.call( |
|
|
|
|
ContractManager, |
|
|
|
|
{:filter, {contract_name, event_name, other_pid, event_data}} |
|
|
|
|
) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
# Server |
|
|
|
@ -605,19 +626,23 @@ defmodule ExW3 do |
|
|
|
|
|
|
|
|
|
encoded_event_signature = "0x#{ExW3.encode_event(signature)}" |
|
|
|
|
|
|
|
|
|
indexed_fields = Enum.filter(v["inputs"], fn input -> |
|
|
|
|
indexed_fields = |
|
|
|
|
Enum.filter(v["inputs"], fn input -> |
|
|
|
|
input["indexed"] |
|
|
|
|
end) |
|
|
|
|
|
|
|
|
|
indexed_names = Enum.map(indexed_fields, fn field -> |
|
|
|
|
indexed_names = |
|
|
|
|
Enum.map(indexed_fields, fn field -> |
|
|
|
|
field["name"] |
|
|
|
|
end) |
|
|
|
|
|
|
|
|
|
non_indexed_fields = Enum.filter(v["inputs"], fn input -> |
|
|
|
|
non_indexed_fields = |
|
|
|
|
Enum.filter(v["inputs"], fn input -> |
|
|
|
|
!input["indexed"] |
|
|
|
|
end) |
|
|
|
|
|
|
|
|
|
non_indexed_names = Enum.map(non_indexed_fields, fn field -> |
|
|
|
|
non_indexed_names = |
|
|
|
|
Enum.map(non_indexed_fields, fn field -> |
|
|
|
|
field["name"] |
|
|
|
|
end) |
|
|
|
|
|
|
|
|
@ -663,8 +688,11 @@ defmodule ExW3 do |
|
|
|
|
|
|
|
|
|
arg_count = Enum.count(arguments) |
|
|
|
|
input_types_count = Enum.count(input_types) |
|
|
|
|
|
|
|
|
|
if input_types_count != arg_count do |
|
|
|
|
raise "Number of provided arguments to constructor is incorrect. Was given #{arg_count} args, looking for #{input_types_count}." |
|
|
|
|
raise "Number of provided arguments to constructor is incorrect. Was given #{ |
|
|
|
|
arg_count |
|
|
|
|
} args, looking for #{input_types_count}." |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
bin <> (ExW3.encode_data(types_signature, arguments) |> Base.encode16(case: :lower)) |
|
|
|
@ -706,6 +734,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( |
|
|
|
|
Map.merge( |
|
|
|
|
%{ |
|
|
|
@ -747,7 +776,6 @@ defmodule ExW3 do |
|
|
|
|
# Calls |
|
|
|
|
|
|
|
|
|
defp filter_topics_helper(event_signature, event_data, topic_types, topic_names) do |
|
|
|
|
|
|
|
|
|
topics = |
|
|
|
|
if is_map(event_data[:topics]) do |
|
|
|
|
Enum.map(topic_names, fn name -> |
|
|
|
@ -759,11 +787,13 @@ defmodule ExW3 do |
|
|
|
|
|
|
|
|
|
if topics do |
|
|
|
|
formatted_topics = |
|
|
|
|
Enum.map(0..length(topics) - 1, fn i -> |
|
|
|
|
Enum.map(0..(length(topics) - 1), fn i -> |
|
|
|
|
topic = Enum.at(topics, i) |
|
|
|
|
|
|
|
|
|
if topic do |
|
|
|
|
if is_list(topic) do |
|
|
|
|
topic_type = Enum.at(topic_types, i) |
|
|
|
|
|
|
|
|
|
Enum.map(topic, fn t -> |
|
|
|
|
"0x" <> (ExW3.encode_data(topic_type, [t]) |> Base.encode16(case: :lower)) |
|
|
|
|
end) |
|
|
|
@ -775,6 +805,7 @@ defmodule ExW3 do |
|
|
|
|
topic |
|
|
|
|
end |
|
|
|
|
end) |
|
|
|
|
|
|
|
|
|
[event_signature] ++ formatted_topics |
|
|
|
|
else |
|
|
|
|
[event_signature] |
|
|
|
@ -789,6 +820,7 @@ defmodule ExW3 do |
|
|
|
|
else |
|
|
|
|
ExW3.encode_data("(uint256)", [event_data[:fromBlock]]) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
Map.put(event_data, :fromBlock, new_from_block) |
|
|
|
|
else |
|
|
|
|
event_data |
|
|
|
@ -801,8 +833,10 @@ defmodule ExW3 do |
|
|
|
|
if Enum.member?(["latest", "earliest", "pending"], event_data[key]) do |
|
|
|
|
event_data[key] |
|
|
|
|
else |
|
|
|
|
"0x" <> (ExW3.encode_data("(uint256)", [event_data[key]]) |> Base.encode16(case: :lower)) |
|
|
|
|
"0x" <> |
|
|
|
|
(ExW3.encode_data("(uint256)", [event_data[key]]) |> Base.encode16(case: :lower)) |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
Map.put(event_data, key, new_param) |
|
|
|
|
else |
|
|
|
|
event_data |
|
|
|
@ -817,7 +851,6 @@ defmodule ExW3 do |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def handle_call({:filter, {contract_name, event_name, other_pid, event_data}}, _from, state) do |
|
|
|
|
|
|
|
|
|
contract_info = state[contract_name] |
|
|
|
|
|
|
|
|
|
unless Process.whereis(Listener) do |
|
|
|
@ -830,7 +863,8 @@ defmodule ExW3 do |
|
|
|
|
|
|
|
|
|
topics = filter_topics_helper(event_signature, event_data, topic_types, topic_names) |
|
|
|
|
|
|
|
|
|
payload = Map.merge( |
|
|
|
|
payload = |
|
|
|
|
Map.merge( |
|
|
|
|
%{address: contract_info[:address], topics: topics}, |
|
|
|
|
event_data_format_helper(event_data) |
|
|
|
|
) |
|
|
|
@ -844,13 +878,11 @@ defmodule ExW3 do |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
def handle_call({:deploy, {name, args}}, _from, state) do |
|
|
|
|
|
|
|
|
|
contract_info = state[name] |
|
|
|
|
|
|
|
|
|
with {:ok, _} <- check_option(args[:options][:from], :missing_sender), |
|
|
|
|
{:ok, _} <- check_option(args[:options][:gas], :missing_gas), |
|
|
|
|
{:ok, bin} <- check_option([state[:bin], args[:bin]], :missing_binary) |
|
|
|
|
do |
|
|
|
|
{:ok, bin} <- check_option([state[:bin], args[:bin]], :missing_binary) do |
|
|
|
|
{contract_addr, tx_hash} = deploy_helper(bin, contract_info[:abi], args) |
|
|
|
|
result = {:ok, contract_addr, tx_hash} |
|
|
|
|
{:reply, result, state} |
|
|
|
@ -866,8 +898,7 @@ defmodule ExW3 do |
|
|
|
|
def handle_call({:call, {contract_name, method_name, args}}, _from, state) do |
|
|
|
|
contract_info = state[contract_name] |
|
|
|
|
|
|
|
|
|
with {:ok, address} <- check_option(contract_info[:address], :missing_address) |
|
|
|
|
do |
|
|
|
|
with {:ok, address} <- check_option(contract_info[:address], :missing_address) do |
|
|
|
|
result = eth_call_helper(address, contract_info[:abi], Atom.to_string(method_name), args) |
|
|
|
|
{:reply, result, state} |
|
|
|
|
else |
|
|
|
@ -877,11 +908,19 @@ defmodule ExW3 do |
|
|
|
|
|
|
|
|
|
def handle_call({:send, {contract_name, method_name, args, options}}, _from, state) do |
|
|
|
|
contract_info = state[contract_name] |
|
|
|
|
|
|
|
|
|
with {:ok, address} <- check_option(contract_info[:address], :missing_address), |
|
|
|
|
{:ok, _} <- check_option(options[:from], :missing_sender), |
|
|
|
|
{:ok, _} <- check_option(options[:gas], :missing_gas) |
|
|
|
|
do |
|
|
|
|
result = eth_send_helper(address, contract_info[:abi], Atom.to_string(method_name), args, options) |
|
|
|
|
{:ok, _} <- check_option(options[:gas], :missing_gas) do |
|
|
|
|
result = |
|
|
|
|
eth_send_helper( |
|
|
|
|
address, |
|
|
|
|
contract_info[:abi], |
|
|
|
|
Atom.to_string(method_name), |
|
|
|
|
args, |
|
|
|
|
options |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
{:reply, result, state} |
|
|
|
|
else |
|
|
|
|
err -> {:reply, err, state} |
|
|
|
@ -902,13 +941,18 @@ defmodule ExW3 do |
|
|
|
|
event_attributes = Map.get(events, topic) |
|
|
|
|
|
|
|
|
|
if event_attributes do |
|
|
|
|
non_indexed_fields = Enum.zip(event_attributes[:non_indexed_names], ExW3.decode_event(log["data"], event_attributes[:signature])) |> Enum.into(%{}) |
|
|
|
|
|
|
|
|
|
non_indexed_fields = |
|
|
|
|
Enum.zip( |
|
|
|
|
event_attributes[:non_indexed_names], |
|
|
|
|
ExW3.decode_event(log["data"], event_attributes[:signature]) |
|
|
|
|
) |
|
|
|
|
|> Enum.into(%{}) |
|
|
|
|
|
|
|
|
|
if length(log["topics"]) > 1 do |
|
|
|
|
[_head | tail] = log["topics"] |
|
|
|
|
decoded_topics = Enum.map(0..length(tail) - 1, fn i -> |
|
|
|
|
|
|
|
|
|
decoded_topics = |
|
|
|
|
Enum.map(0..(length(tail) - 1), fn i -> |
|
|
|
|
topic_type = Enum.at(event_attributes[:topic_types], i) |
|
|
|
|
topic_data = Enum.at(tail, i) |
|
|
|
|
|
|
|
|
@ -917,12 +961,13 @@ defmodule ExW3 do |
|
|
|
|
decoded |
|
|
|
|
end) |
|
|
|
|
|
|
|
|
|
indexed_fields = Enum.zip(event_attributes[:topic_names], decoded_topics) |> Enum.into(%{}) |
|
|
|
|
indexed_fields = |
|
|
|
|
Enum.zip(event_attributes[:topic_names], decoded_topics) |> Enum.into(%{}) |
|
|
|
|
|
|
|
|
|
Map.merge(indexed_fields, non_indexed_fields) |
|
|
|
|
else |
|
|
|
|
non_indexed_fields |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
else |
|
|
|
|
nil |
|
|
|
|
end |
|
|
|
@ -931,5 +976,4 @@ defmodule ExW3 do |
|
|
|
|
{:reply, {:ok, {receipt, formatted_logs}}, state} |
|
|
|
|
end |
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
end |
|
|
|
|