4
0
Fork 0

Reformatted code

pull/5/head
hswick 7 years ago
parent e119bd8c0f
commit 6672c4635a
  1. 232
      lib/exw3.ex
  2. 2
      mix.exs
  3. 105
      test/exw3_test.exs

@ -1,16 +1,15 @@
defmodule ExW3 do
@spec bytes_to_string(binary()) :: binary()
@doc "converts Ethereum style bytes to string"
def bytes_to_string bytes do
@spec bytes_to_string(binary()) :: binary()
@doc "converts Ethereum style bytes to string"
def bytes_to_string(bytes) do
bytes
|> Base.encode16(case: :lower)
|> String.replace_trailing("0", "")
|> Base.decode16!(case: :lower)
end
@spec accounts() :: list()
@doc "returns all available accounts"
@spec accounts() :: list()
@doc "returns all available accounts"
def accounts do
case Ethereumex.HttpClient.eth_accounts() do
{:ok, accounts} -> accounts
@ -18,16 +17,16 @@ defmodule ExW3 do
end
end
@spec to_decimal(binary()) :: number()
@doc "Converts ethereum hex string to decimal number"
@spec to_decimal(binary()) :: number()
@doc "Converts ethereum hex string to decimal number"
def to_decimal(hex_string) do
hex_string
|> String.slice(2..-1)
|> String.to_integer(16)
end
@spec block_number() :: integer()
@doc "Returns the current block number"
@spec block_number() :: integer()
@doc "Returns the current block number"
def block_number do
case Ethereumex.HttpClient.eth_block_number() do
{:ok, block_number} ->
@ -38,8 +37,8 @@ defmodule ExW3 do
end
end
@spec balance(binary()) :: integer()
@doc "Returns current balance of account"
@spec balance(binary()) :: integer()
@doc "Returns current balance of account"
def balance(account) do
case Ethereumex.HttpClient.eth_get_balance(account) do
{:ok, balance} ->
@ -50,7 +49,7 @@ defmodule ExW3 do
end
end
@spec keys_to_decimal(%{}, []) :: %{}
@spec keys_to_decimal(%{}, []) :: %{}
def keys_to_decimal(map, keys) do
Map.new(
Enum.map(keys, fn k ->
@ -59,20 +58,23 @@ defmodule ExW3 do
)
end
@spec tx_receipt(binary()) :: %{}
@doc "Returns transaction receipt for specified transaction hash(id)"
@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
{:ok, receipt} ->
Map.merge receipt, keys_to_decimal(receipt, ["blockNumber", "cumulativeGasUsed", "gasUsed"])
Map.merge(
receipt,
keys_to_decimal(receipt, ["blockNumber", "cumulativeGasUsed", "gasUsed"])
)
err ->
err
end
end
@spec block(integer()) :: any()
@doc "Returns block data for specified block number"
@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
{:ok, block} -> block
@ -80,22 +82,22 @@ defmodule ExW3 do
end
end
@spec mine(integer()) :: any()
@doc "Mines number of blocks specified. Default is 1"
@spec mine(integer()) :: any()
@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", [], [])
end
end
@spec encode_event(binary()) :: binary()
@doc "Encodes event based on signature"
@spec encode_event(binary()) :: binary()
@doc "Encodes event based on signature"
def encode_event(signature) do
ExthCrypto.Hash.Keccak.kec(signature) |> Base.encode16(case: :lower)
end
@spec decode_event(binary(), binary()) :: any()
@doc "Decodes event based on given data and provided signature"
@spec decode_event(binary(), binary()) :: any()
@doc "Decodes event based on given data and provided signature"
def decode_event(data, signature) do
formatted_data =
data
@ -107,14 +109,14 @@ defmodule ExW3 do
ABI.TypeDecoder.decode(formatted_data, fs)
end
@spec reformat_abi([]) :: %{}
@doc "Reformats abi from list to map with event and function names as keys"
@spec reformat_abi([]) :: %{}
@doc "Reformats abi from list to map with event and function names as keys"
def reformat_abi(abi) do
Map.new(Enum.map(abi, fn x -> {x["name"], x} end))
end
@spec load_abi(binary()) :: []
@doc "Loads the abi at the file path and reformats it to a map"
@spec load_abi(binary()) :: []
@doc "Loads the abi at the file path and reformats it to a map"
def load_abi(file_path) do
file = File.read(Path.join(System.cwd(), file_path))
@ -124,19 +126,19 @@ defmodule ExW3 do
end
end
@spec load_bin(binary()) :: binary()
@doc "Loads the bin ar the file path"
@spec load_bin(binary()) :: binary()
@doc "Loads the bin ar the file path"
def load_bin(file_path) do
file = File.read(Path.join(System.cwd(), file_path))
case file do
{:ok, bin} -> bin
err -> err
end
end
end
@spec decode_output(%{}, binary(), binary()) :: []
@doc "Decodes output based on specified functions return signature"
@spec decode_output(%{}, binary(), binary()) :: []
@doc "Decodes output based on specified functions return signature"
def decode_output(abi, name, output) do
{:ok, trim_output} =
String.slice(output, 2..String.length(output)) |> Base.decode16(case: :lower)
@ -144,54 +146,57 @@ defmodule ExW3 do
output_types = Enum.map(abi[name]["outputs"], fn x -> x["type"] end)
types_signature = Enum.join(["(", Enum.join(output_types, ","), ")"])
output_signature = "#{name}(#{types_signature})"
outputs =
outputs =
ABI.decode(output_signature, trim_output)
|> List.first
|> Tuple.to_list
|> List.first()
|> Tuple.to_list()
outputs
end
@spec types_signature(%{}, binary()) :: binary()
@doc "Returns the type signature of a given function"
@spec types_signature(%{}, binary()) :: binary()
@doc "Returns the type signature of a given function"
def types_signature(abi, name) do
input_types = Enum.map(abi[name]["inputs"], fn x -> x["type"] end)
types_signature = Enum.join(["(", Enum.join(input_types, ","), ")"])
types_signature
end
@spec method_signature(%{}, binary()) :: binary()
@doc "Returns the 4 character method id based on the hash of the method signature"
@spec method_signature(%{}, binary()) :: binary()
@doc "Returns the 4 character method id based on the hash of the method signature"
def method_signature(abi, name) do
if abi[name] do
input_signature = "#{name}#{types_signature(abi, name)}" |> ExthCrypto.Hash.Keccak.kec()
# Take first four bytes
<<init::binary-size(4), _rest::binary>> = input_signature
init
init
else
raise "#{name} method not found in the given abi"
end
end
@spec encode_data(binary(), []) :: binary()
@doc "Encodes data into Ethereum hex string based on types signature"
@spec encode_data(binary(), []) :: binary()
@doc "Encodes data into Ethereum hex string based on types signature"
def encode_data(types_signature, data) do
ABI.TypeEncoder.encode_raw(
[List.to_tuple(data)],
[List.to_tuple(data)],
ABI.FunctionSelector.decode_raw(types_signature)
)
end
@spec encode_method_call(%{}, binary(), []) :: binary()
@doc "Encodes data and appends it to the encoded method id"
@spec encode_method_call(%{}, binary(), []) :: binary()
@doc "Encodes data and appends it to the encoded method id"
def encode_method_call(abi, name, input) do
encoded_method_call = method_signature(abi, name) <> encode_data(types_signature(abi, name), input)
encoded_method_call =
method_signature(abi, name) <> encode_data(types_signature(abi, name), input)
encoded_method_call |> Base.encode16(case: :lower)
end
@spec encode_input(%{}, binary(), []) :: binary()
@doc "Encodes input from a method call based on function signature"
@spec encode_input(%{}, binary(), []) :: binary()
@doc "Encodes input from a method call based on function signature"
def encode_input(abi, name, input) do
if abi[name]["inputs"] do
input_types = Enum.map(abi[name]["inputs"], fn x -> x["type"] end)
@ -202,11 +207,11 @@ defmodule ExW3 do
<<init::binary-size(4), _rest::binary>> = input_signature
encoded_input =
init <>
ABI.TypeEncoder.encode_raw(
[List.to_tuple(input)],
ABI.FunctionSelector.decode_raw(types_signature)
)
init <>
ABI.TypeEncoder.encode_raw(
[List.to_tuple(input)],
ABI.FunctionSelector.decode_raw(types_signature)
)
encoded_input |> Base.encode16(case: :lower)
else
@ -219,50 +224,50 @@ defmodule ExW3 do
# Client
@spec start_link(atom(), list()) :: {:ok, pid()}
@doc "Begins a Contract GenServer with specified name and state"
@spec start_link(atom(), list()) :: {:ok, pid()}
@doc "Begins a Contract GenServer with specified name and state"
def start_link(name, state) do
GenServer.start_link(__MODULE__, state, name: name)
end
@spec deploy(pid(), []) :: {:ok, []}
@doc "Deploys contracts with given arguments"
@spec deploy(pid(), []) :: {:ok, []}
@doc "Deploys contracts with given arguments"
def deploy(pid, args) do
GenServer.call(pid, {:deploy, args})
end
@spec at(pid(), binary()) :: :ok
@doc "Sets the current Contract GenServer's address to given address"
@spec at(pid(), binary()) :: :ok
@doc "Sets the current Contract GenServer's address to given address"
def at(pid, address) do
GenServer.cast(pid, {:at, address})
end
@spec address(pid()) :: {:ok, binary()}
@doc "Returns the current Contract GenServer's address"
@spec address(pid()) :: {:ok, binary()}
@doc "Returns the current Contract GenServer's address"
def address(pid) do
GenServer.call(pid, :address)
end
@spec call(pid(), keyword(), []) :: {:ok, any()}
@doc "Use a Contract's method with an eth_call"
@spec call(pid(), keyword(), []) :: {:ok, any()}
@doc "Use a Contract's method with an eth_call"
def call(pid, method_name, args \\ []) do
GenServer.call(pid, {:call, {method_name, args}})
end
@spec send(pid(), keyword(), [], %{}) :: {:ok, binary()}
@doc "Use a Contract's method with an eth_sendTransaction"
@spec send(pid(), keyword(), [], %{}) :: {:ok, binary()}
@doc "Use a Contract's method with an eth_sendTransaction"
def send(pid, method_name, args, options) do
GenServer.call(pid, {:send, {method_name, args, options}})
end
@spec tx_receipt(pid(), binary()) :: %{}
@doc "Returns a formatted transaction receipt for the given transaction hash(id)"
@spec tx_receipt(pid(), binary()) :: %{}
@doc "Returns a formatted transaction receipt for the given transaction hash(id)"
def tx_receipt(pid, tx_hash) do
GenServer.call(pid, {:tx_receipt, tx_hash})
end
# Server
def init(state) do
if state[:abi] do
{:ok, [{:events, init_events(state[:abi])} | state]}
@ -272,17 +277,19 @@ defmodule ExW3 do
end
defp init_events(abi) do
events = Enum.filter abi, fn {_, v} ->
v["type"] == "event"
end
events =
Enum.filter(abi, fn {_, v} ->
v["type"] == "event"
end)
signature_types_map = Enum.map events, fn {name, v} ->
types = Enum.map v["inputs"], &Map.get(&1, "type")
names = Enum.map v["inputs"], &Map.get(&1, "name")
signature = Enum.join([name, "(", Enum.join(types, ","), ")"])
signature_types_map =
Enum.map(events, fn {name, v} ->
types = Enum.map(v["inputs"], &Map.get(&1, "type"))
names = Enum.map(v["inputs"], &Map.get(&1, "name"))
signature = Enum.join([name, "(", Enum.join(types, ","), ")"])
{"0x#{ExW3.encode_event(signature)}", %{signature: signature, names: names}}
end
{"0x#{ExW3.encode_event(signature)}", %{signature: signature, names: names}}
end)
Enum.into(signature_types_map, %{})
end
@ -290,20 +297,21 @@ defmodule ExW3 do
# Helpers
def deploy_helper(bin, abi, args) do
constructor_arg_data =
if args[:args] do
constructor_abi = Enum.find abi, fn {_, v} ->
v["type"] == "constructor"
end
if constructor_abi do
{_, constructor} = constructor_abi
input_types = Enum.map(constructor["inputs"], fn x -> x["type"] end)
types_signature = Enum.join(["(", Enum.join(input_types, ","), ")"])
bin <> (ExW3.encode_data(types_signature, args[:args]) |> Base.encode16(case: :lower))
else
bin
end
constructor_abi =
Enum.find(abi, fn {_, v} ->
v["type"] == "constructor"
end)
if constructor_abi do
{_, constructor} = constructor_abi
input_types = Enum.map(constructor["inputs"], fn x -> x["type"] end)
types_signature = Enum.join(["(", Enum.join(input_types, ","), ")"])
bin <> (ExW3.encode_data(types_signature, args[:args]) |> Base.encode16(case: :lower))
else
bin
end
else
bin
end
@ -315,17 +323,18 @@ defmodule ExW3 do
}
{: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 eth_call_helper(address, abi, method_name, args) do
result = Ethereumex.HttpClient.eth_call(%{
to: address,
data: ExW3.encode_method_call(abi, method_name, args)
})
def eth_call_helper(address, abi, method_name, args) do
result =
Ethereumex.HttpClient.eth_call(%{
to: address,
data: ExW3.encode_method_call(abi, method_name, args)
})
case result do
{:ok, data} -> ([:ok] ++ ExW3.decode_output(abi, method_name, data)) |> List.to_tuple()
@ -367,6 +376,7 @@ defmodule ExW3 do
def handle_call({:call, {method_name, args}}, _from, state) do
address = state[:address]
if address do
result = eth_call_helper(address, state[:abi], Atom.to_string(method_name), args)
{:reply, result, state}
@ -377,6 +387,7 @@ defmodule ExW3 do
def handle_call({:send, {method_name, args, options}}, _from, state) do
address = state[:address]
if address do
result = eth_send_helper(address, state[:abi], Atom.to_string(method_name), args, options)
{:reply, result, state}
@ -386,21 +397,24 @@ defmodule ExW3 do
end
def handle_call({:tx_receipt, tx_hash}, _from, state) do
receipt = ExW3.tx_receipt tx_hash
receipt = ExW3.tx_receipt(tx_hash)
events = state[:events]
logs = receipt["logs"]
formatted_logs = Enum.map logs, fn log ->
topic = Enum.at log["topics"], 0
event = Map.get events, topic
if event do
Enum.zip(event[:names], ExW3.decode_event(log["data"], event[:signature])) |> Enum.into(%{})
else
nil
end
end
formatted_logs =
Enum.map(logs, fn log ->
topic = Enum.at(log["topics"], 0)
event = Map.get(events, topic)
if event do
Enum.zip(event[:names], ExW3.decode_event(log["data"], event[:signature]))
|> Enum.into(%{})
else
nil
end
end)
{:reply, {:ok, {receipt, formatted_logs}}, state}
end
end
end
end

@ -3,7 +3,7 @@ defmodule ExW3.MixProject do
def project do
[app: :exw3,
version: "0.1.0",
version: "0.1.1",
elixir: "~> 1.6.4",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,

@ -8,20 +8,20 @@ defmodule EXW3Test do
array_tester_abi: ExW3.load_abi("test/examples/build/ArrayTester.abi"),
event_tester_abi: ExW3.load_abi("test/examples/build/EventTester.abi"),
complex_abi: ExW3.load_abi("test/examples/build/Complex.abi"),
accounts: ExW3.accounts
accounts: ExW3.accounts()
}
end
test "gets accounts" do
assert ExW3.accounts |> is_list
assert ExW3.accounts() |> is_list
end
test "gets balance", context do
assert ExW3.balance(Enum.at context[:accounts], 0 ) |> is_integer
assert ExW3.balance(Enum.at(context[:accounts], 0)) |> is_integer
end
test "gets block number" do
assert ExW3.block_number |> is_integer
assert ExW3.block_number() |> is_integer
end
test "loads abi", context do
@ -29,29 +29,29 @@ defmodule EXW3Test do
end
test "mines a block" do
block_number = ExW3.block_number
ExW3.mine
assert ExW3.block_number == block_number + 1
block_number = ExW3.block_number()
ExW3.mine()
assert ExW3.block_number() == block_number + 1
end
test "mines multiple blocks" do
block_number = ExW3.block_number
ExW3.mine 5
assert ExW3.block_number == block_number + 5
block_number = ExW3.block_number()
ExW3.mine(5)
assert ExW3.block_number() == block_number + 5
end
test "starts a Contract GenServer for simple storage contract", context do
ExW3.Contract.start_link(SimpleStorage, abi: context[:simple_storage_abi])
{:ok, address} = ExW3.Contract.deploy(
SimpleStorage,
bin: ExW3.load_bin("test/examples/build/SimpleStorage.bin"),
options: %{
gas: 300000,
from: Enum.at(context[:accounts], 0)
}
)
{:ok, address} =
ExW3.Contract.deploy(
SimpleStorage,
bin: ExW3.load_bin("test/examples/build/SimpleStorage.bin"),
options: %{
gas: 300_000,
from: Enum.at(context[:accounts], 0)
}
)
ExW3.Contract.at(SimpleStorage, address)
@ -66,20 +66,20 @@ defmodule EXW3Test do
{:ok, data} = ExW3.Contract.call(SimpleStorage, :get)
assert data == 1
end
test "starts a Contract GenServer for array tester contract", context do
ExW3.Contract.start_link(ArrayTester, abi: context[:array_tester_abi])
{:ok, address} = ExW3.Contract.deploy(
ArrayTester,
bin: ExW3.load_bin("test/examples/build/ArrayTester.bin"),
options: %{
gas: 300000,
from: Enum.at(context[:accounts], 0)
}
)
{:ok, address} =
ExW3.Contract.deploy(
ArrayTester,
bin: ExW3.load_bin("test/examples/build/ArrayTester.bin"),
options: %{
gas: 300_000,
from: Enum.at(context[:accounts], 0)
}
)
ExW3.Contract.at(ArrayTester, address)
@ -99,20 +99,24 @@ defmodule EXW3Test do
test "starts a Contract GenServer for event tester contract", context do
ExW3.Contract.start_link(EventTester, abi: context[:event_tester_abi])
{:ok, address} = ExW3.Contract.deploy(
EventTester,
bin: ExW3.load_bin("test/examples/build/EventTester.bin"),
options: %{
gas: 300000,
from: Enum.at(context[:accounts], 0)
}
)
{:ok, address} =
ExW3.Contract.deploy(
EventTester,
bin: ExW3.load_bin("test/examples/build/EventTester.bin"),
options: %{
gas: 300_000,
from: Enum.at(context[:accounts], 0)
}
)
ExW3.Contract.at(EventTester, address)
assert address == ExW3.Contract.address(EventTester)
{:ok, tx_hash} = ExW3.Contract.send(EventTester, :simple, ["Hello, World!"], %{from: Enum.at(context[:accounts], 0)})
{:ok, tx_hash} =
ExW3.Contract.send(EventTester, :simple, ["Hello, World!"], %{
from: Enum.at(context[:accounts], 0)
})
{:ok, {receipt, logs}} = ExW3.Contract.tx_receipt(EventTester, tx_hash)
@ -124,23 +128,22 @@ defmodule EXW3Test do
|> Map.get("data")
|> ExW3.bytes_to_string()
assert data == "Hello, World!"
assert data == "Hello, World!"
end
test "starts a Contract GenServer for Complex contract", context do
ExW3.Contract.start_link(Complex, abi: context[:complex_abi])
{:ok, address} = ExW3.Contract.deploy(
Complex,
bin: ExW3.load_bin("test/examples/build/Complex.bin"),
args: [42, "Hello, world!"],
options: %{
from: Enum.at(context[:accounts], 0),
gas: 300000
}
)
{:ok, address} =
ExW3.Contract.deploy(
Complex,
bin: ExW3.load_bin("test/examples/build/Complex.bin"),
args: [42, "Hello, world!"],
options: %{
from: Enum.at(context[:accounts], 0),
gas: 300_000
}
)
ExW3.Contract.at(Complex, address)
@ -151,7 +154,5 @@ defmodule EXW3Test do
assert foo == 42
assert ExW3.bytes_to_string(foobar) == "Hello, world!"
end
end
end

Loading…
Cancel
Save