4
0
Fork 0

Merge pull request #18 from hswick/contract-refactor2

Contract refactor
new-events
Harley Swick 6 years ago committed by GitHub
commit 26faa02a81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 52
      README.md
  2. 134
      lib/exw3.ex
  3. 95
      test/exw3_test.exs

@ -44,22 +44,13 @@ Currently, ExW3 supports a handful of json rpc commands. Mostly just the useful
Check out the [documentation](https://hexdocs.pm/exw3/ExW3.html)
```elixir
```
iex(1)> accounts = ExW3.accounts()
["0xb5c17637ccc1a5d91715429de76949fbe49d36f0",
"0xecf00f60a29acf81d7fdf696fd2ca1fa82b623b0",
"0xbf11365685e07ad86387098f27204700d7568ee2",
"0xba76d611c29fb25158e5a7409cb627cf1bd220cf",
"0xbb209f51ef097cc5ca320264b5373a48f7ee0fba",
"0x31b7a2c8b2f82a92bf4cb5fd13971849c6c956fc",
"0xeb943cee8ec3723ab3a06e45dc2a75a3caa04288",
"0x59315d9706ac567d01860d7ede03720876972162",
"0x4dbd23f361a4df1ef5e517b68e099bf2fcc77b10",
"0x150eb320428b9bc93453b850b4ea454a35308f17"]
["0x00a329c0648769a73afac7f9381e08fb43dbea72"]
iex(2)> ExW3.balance(Enum.at(accounts, 0))
99999999999962720359
1606938044258990275541962092341162602522200978938292835291376
iex(3)> ExW3.block_number()
835
1252
iex(4)> simple_storage_abi = ExW3.load_abi("test/examples/build/SimpleStorage.abi")
%{
"get" => %{
@ -81,19 +72,22 @@ iex(4)> simple_storage_abi = ExW3.load_abi("test/examples/build/SimpleStorage.ab
"type" => "function"
}
}
iex(5)> ExW3.Contract.start_link(SimpleStorage, abi: simple_storage_abi)
{:ok, #PID<0.239.0>}
iex(6)> {:ok, address, tx_hash} = ExW3.Contract.deploy(SimpleStorage, bin: ExW3.load_bin("test/examples/build/SimpleStorage.bin"), options: %{gas: 300_000, from: Enum.at(accounts, 0)})
{:ok, "0xd99306b81bd61cb0ecdd3f2c946af513b3395088"}
iex(7)> ExW3.Contract.at(SimpleStorage, address)
iex(5)> ExW3.Contract.start_link
{:ok, #PID<0.265.0>}
iex(6)> ExW3.Contract.register(:SimpleStorage, abi: simple_storage_abi)
:ok
iex(8)> ExW3.Contract.call(SimpleStorage, :get)
iex(7)> {:ok, address, tx_hash} = ExW3.Contract.deploy(:SimpleStorage, bin: ExW3.load_bin("test/examples/build/SimpleStorage.bin"), options: %{gas: 300_000, from: Enum.at(accounts, 0)})
{:ok, "0x22018c2bb98387a39e864cf784e76cb8971889a5",
"0x4ea539048c01194476004ef69f407a10628bed64e88ee8f8b17b4d030d0e7cb7"}
iex(8)> ExW3.Contract.at(:SimpleStorage, address)
:ok
iex(9)> ExW3.Contract.call(:SimpleStorage, :get)
{:ok, 0}
iex(9)> ExW3.Contract.send(SimpleStorage, :set, [1], %{from: Enum.at(accounts, 0), gas: 50_000})
{:ok, "0xb7e9cbdd2cec8ca017e675059a3af063d754496c960f156e1a41fe51ea82f3b8"}
iex(10)> ExW3.Contract.call(SimpleStorage, :get)
iex(10)> ExW3.Contract.send(:SimpleStorage, :set, [1], %{from: Enum.at(accounts, 0), gas: 50_000})
{:ok, "0x88838e84a401a1d6162290a1a765507c4a83f5e050658a83992a912f42149ca5"}
iex(11)> ExW3.Contract.call(:SimpleStorage, :get)
{:ok, 1}
```
```
## Listening for Events
@ -105,15 +99,13 @@ Whenever a change is detected it will send a message to whichever process is lis
# Start the background listener
ExW3.EventListener.start_link
# Assuming we have already setup our contract called EventTester
# We can then add a filter for the event listener to look out for
# by passing in the event name, and the process we want to receive the messages when an event is triggered.
# Assuming we have already registered our contract called :EventTester
# We can then add a filter for the event listener to look out for by passing in the event name, and the process we want to receive the messages when an event is triggered.
# For now we are going to use the main process, however, we could pass in a pid of a different process.
filter_id = ExW3.Contract.filter(EventTester, "Simple", self())
filter_id = ExW3.Contract.filter(:EventTester, "Simple", self())
# We can then wait for the event. Using the typical receive keyword we wait for the first instance
# of the event, and then continue with the rest of the code. This is useful for testing.
# We can then wait for the event. Using the typical receive keyword we wait for the first instance of the event, and then continue with the rest of the code. This is useful for testing.
receive do
{:event, {filter_id, data}} -> IO.inspect data
end
@ -121,7 +113,7 @@ end
# We can then uninstall the filter after we are done using it
ExW3.uninstall_filter(filter_id)
# ExW3 also provides a helper method to continuously listen for events.
# ExW3 also provides a helper method to continuously listen for events, with the `listen` method.
# One use is to combine all of our filters with pattern matching
ExW3.EventListener.listen(fn result ->
case result do

@ -454,60 +454,62 @@ defmodule ExW3 do
# Client
@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)
@spec start_link() :: {:ok, pid()}
@doc "Begins the Contract process to manage all interactions with smart contracts"
def start_link() do
GenServer.start_link(__MODULE__, %{}, name: ContractManager)
end
@spec deploy(pid(), []) :: {:ok, []}
@spec deploy(keyword(), []) :: {:ok, binary(), []}
@doc "Deploys contracts with given arguments"
def deploy(pid, args) do
GenServer.call(pid, {:deploy, args})
def deploy(name, args) do
GenServer.call(ContractManager, {:deploy, {name, args}})
end
@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})
@spec register(keyword(), []) :: :ok
@doc "Registers the contract with the ContractManager process. Only :abi is required field."
def register(name, contract_info) do
GenServer.cast(ContractManager, {:register, {name, contract_info}})
end
@spec address(pid()) :: {:ok, binary()}
@spec at(keyword(), binary()) :: :ok
@doc "Sets the address for the contract specified by the name argument"
def at(name, address) do
GenServer.cast(ContractManager, {:at, {name, address}})
end
@spec address(keyword()) :: {:ok, binary()}
@doc "Returns the current Contract GenServer's address"
def address(pid) do
GenServer.call(pid, :address)
def address(name) do
GenServer.call(ContractManager, {:address, name})
end
@spec call(pid(), keyword(), []) :: {:ok, any()}
@spec call(keyword(), 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}})
def call(contract_name, method_name, args \\ []) do
GenServer.call(ContractManager, {:call, {contract_name, method_name, args}})
end
@spec send(pid(), keyword(), [], %{}) :: {:ok, binary()}
@spec send(keyword(), 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}})
def send(contract_name, method_name, args, options) do
GenServer.call(ContractManager, {:send, {contract_name, method_name, args, options}})
end
@spec tx_receipt(pid(), binary()) :: %{}
@spec tx_receipt(keyword(), 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})
def tx_receipt(contract_name, tx_hash) do
GenServer.call(ContractManager, {:tx_receipt, {contract_name, tx_hash}})
end
def filter(pid, event_name, other_pid, event_data \\ %{}) do
GenServer.call(pid, {:filter, {event_name, other_pid, event_data}})
def filter(contract_name, event_name, other_pid, event_data \\ %{}) do
GenServer.call(ContractManager, {:filter, {contract_name, event_name, other_pid, event_data}})
end
# Server
def init(state) do
if state[:abi] do
{:ok, state ++ init_events(state[:abi])}
else
raise "ABI not provided upon initialization"
end
{:ok, state}
end
defp init_events(abi) do
@ -611,6 +613,13 @@ defmodule ExW3 do
)
end
defp add_helper(contract_info) do
if contract_info[:abi] do
contract_info ++ init_events(contract_info[:abi])
else
raise "ABI not provided upon initialization"
end
end
# Options' checkers
@ -622,66 +631,83 @@ defmodule ExW3 do
# Casts
def handle_cast({:at, address}, state) do
{:noreply, [{:address, address} | state]}
def handle_cast({:at, {name, address}}, state) do
contract_info = state[name]
{:noreply, Map.put(state, name, contract_info ++ [address: address])}
end
def handle_call({:filter, {event_name, other_pid, event_data}}, _from, state) do
def handle_cast({:register, {name, contract_info}}, state) do
{:noreply, Map.put(state, name, add_helper(contract_info))}
end
# Calls
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
raise "EventListener process not alive. Call ExW3.EventListener.start_link before using ExW3.Contract.subscribe"
end
payload = Map.merge(%{address: state[:address], topics: [state[:event_names][event_name]]}, event_data)
payload = Map.merge(%{address: contract_info[:address], topics: [contract_info[:event_names][event_name]]}, event_data)
filter_id = ExW3.new_filter(payload)
event_signature = state[:events][state[:event_names][event_name]][:signature]
event_fields = state[:events][state[:event_names][event_name]][:names]
event_signature = contract_info[:events][contract_info[:event_names][event_name]][:signature]
event_fields = contract_info[:events][contract_info[:event_names][event_name]][:names]
EventListener.filter(filter_id, event_signature, event_fields, other_pid)
{:reply, filter_id, state ++ [event_name, filter_id]}
{:reply, filter_id, Map.put(state, contract_name, contract_info ++ [event_name, filter_id])}
end
# Calls
def handle_call({:deploy, {name, args}}, _from, state) do
def handle_call({:deploy, 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
{contract_addr, tx_hash} = deploy_helper(bin, state[:abi], args)
do
{contract_addr, tx_hash} = deploy_helper(bin, contract_info[:abi], args)
result = {:ok, contract_addr, tx_hash}
{:reply, result , state}
else
err -> {:reply, err, state}
end
else
err -> {:reply, err, state}
end
end
def handle_call(:address, _from, state) do
{:reply, state[:address], state}
def handle_call({:address, name}, _from, state) do
{:reply, state[name][:address], state}
end
def handle_call({:call, {method_name, args}}, _from, state) do
with {:ok, address} <- check_option(state[:address], :missing_address)
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
result = eth_call_helper(address, state[:abi], Atom.to_string(method_name), args)
result = eth_call_helper(address, contract_info[:abi], Atom.to_string(method_name), args)
{:reply, result, state}
else
err -> {:reply, err, state}
end
end
def handle_call({:send, {method_name, args, options}}, _from, state) do
with {:ok, address} <- check_option(state[:address], :missing_address),
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, state[:abi], Atom.to_string(method_name), args, options)
result = eth_send_helper(address, contract_info[:abi], Atom.to_string(method_name), args, options)
{:reply, result, state}
else
err -> {:reply, err, state}
end
end
def handle_call({:tx_receipt, tx_hash}, _from, state) do
def handle_call({:tx_receipt, {contract_name, tx_hash}}, _from, state) do
contract_info = state[contract_name]
receipt = ExW3.tx_receipt(tx_hash)
events = state[:events]
events = contract_info[:events]
logs = receipt["logs"]
formatted_logs =

@ -3,6 +3,8 @@ defmodule EXW3Test do
doctest ExW3
setup_all do
ExW3.Contract.start_link
%{
simple_storage_abi: ExW3.load_abi("test/examples/build/SimpleStorage.abi"),
array_tester_abi: ExW3.load_abi("test/examples/build/ArrayTester.abi"),
@ -58,11 +60,13 @@ defmodule EXW3Test do
end
test "starts a Contract GenServer for simple storage contract", context do
ExW3.Contract.start_link(SimpleStorage, abi: context[:simple_storage_abi])
ExW3.Contract.register(:SimpleStorage, abi: context[:simple_storage_abi])
{:ok, address, _} =
ExW3.Contract.deploy(
SimpleStorage,
:SimpleStorage,
bin: ExW3.load_bin("test/examples/build/SimpleStorage.bin"),
args: [],
options: %{
@ -71,27 +75,27 @@ defmodule EXW3Test do
}
)
ExW3.Contract.at(SimpleStorage, address)
ExW3.Contract.at(:SimpleStorage, address)
assert address == ExW3.Contract.address(SimpleStorage)
assert address == ExW3.Contract.address(:SimpleStorage)
{:ok, data} = ExW3.Contract.call(SimpleStorage, :get)
{:ok, data} = ExW3.Contract.call(:SimpleStorage, :get)
assert data == 0
ExW3.Contract.send(SimpleStorage, :set, [1], %{from: Enum.at(context[:accounts], 0), gas: 50_000})
ExW3.Contract.send(:SimpleStorage, :set, [1], %{from: Enum.at(context[:accounts], 0), gas: 50_000})
{:ok, data} = ExW3.Contract.call(SimpleStorage, :get)
{: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])
ExW3.Contract.register(:ArrayTester, abi: context[:array_tester_abi])
{:ok, address, _} =
ExW3.Contract.deploy(
ArrayTester,
:ArrayTester,
bin: ExW3.load_bin("test/examples/build/ArrayTester.bin"),
options: %{
gas: 300_000,
@ -99,27 +103,27 @@ defmodule EXW3Test do
}
)
ExW3.Contract.at(ArrayTester, address)
ExW3.Contract.at(:ArrayTester, address)
assert address == ExW3.Contract.address(ArrayTester)
assert address == ExW3.Contract.address(:ArrayTester)
arr = [1, 2, 3, 4, 5]
{:ok, result} = ExW3.Contract.call(ArrayTester, :staticUint, [arr])
{:ok, result} = ExW3.Contract.call(:ArrayTester, :staticUint, [arr])
assert result == arr
{:ok, result} = ExW3.Contract.call(ArrayTester, :dynamicUint, [arr])
{:ok, result} = ExW3.Contract.call(:ArrayTester, :dynamicUint, [arr])
assert result == arr
end
test "starts a Contract GenServer for event tester contract", context do
ExW3.Contract.start_link(EventTester, abi: context[:event_tester_abi])
ExW3.Contract.register(:EventTester, abi: context[:event_tester_abi])
{:ok, address, _} =
ExW3.Contract.deploy(
EventTester,
:EventTester,
bin: ExW3.load_bin("test/examples/build/EventTester.bin"),
options: %{
gas: 300_000,
@ -127,17 +131,17 @@ defmodule EXW3Test do
}
)
ExW3.Contract.at(EventTester, address)
ExW3.Contract.at(:EventTester, address)
assert address == ExW3.Contract.address(EventTester)
assert address == ExW3.Contract.address(:EventTester)
{:ok, tx_hash} =
ExW3.Contract.send(EventTester, :simple, ["Hello, World!"], %{
ExW3.Contract.send(:EventTester, :simple, ["Hello, World!"], %{
from: Enum.at(context[:accounts], 0),
gas: 30_000
})
{:ok, {receipt, logs}} = ExW3.Contract.tx_receipt(EventTester, tx_hash)
{:ok, {receipt, logs}} = ExW3.Contract.tx_receipt(:EventTester, tx_hash)
assert receipt |> is_map
@ -151,11 +155,11 @@ defmodule EXW3Test do
end
test "starts a Contract GenServer and uses the event listener", context do
ExW3.Contract.start_link(EventTester, abi: context[:event_tester_abi])
ExW3.Contract.register(:EventTester, abi: context[:event_tester_abi])
{:ok, address, _} =
ExW3.Contract.deploy(
EventTester,
:EventTester,
bin: ExW3.load_bin("test/examples/build/EventTester.bin"),
options: %{
gas: 300_000,
@ -163,17 +167,17 @@ defmodule EXW3Test do
}
)
ExW3.Contract.at(EventTester, address)
ExW3.Contract.at(:EventTester, address)
{:ok, agent} = Agent.start_link(fn -> [] end)
ExW3.EventListener.start_link()
filter_id = ExW3.Contract.filter(EventTester, "Simple", self())
filter_id = ExW3.Contract.filter(:EventTester, "Simple", self())
{:ok, _tx_hash} =
ExW3.Contract.send(
EventTester,
:EventTester,
:simple,
["Hello, World!"],
%{from: Enum.at(context[:accounts], 0), gas: 30_000}
@ -189,6 +193,7 @@ defmodule EXW3Test do
state = Agent.get(agent, fn list -> list end)
event_log = Enum.at(state, 0)
assert event_log |> is_map
log_data = Map.get(event_log, "data")
assert log_data |> is_map
assert Map.get(log_data, "num") == 42
@ -198,11 +203,11 @@ defmodule EXW3Test do
end
test "starts a Contract GenServer for Complex contract", context do
ExW3.Contract.start_link(Complex, abi: context[:complex_abi])
ExW3.Contract.register(:Complex, abi: context[:complex_abi])
{:ok, address, _} =
ExW3.Contract.deploy(
Complex,
:Complex,
bin: ExW3.load_bin("test/examples/build/Complex.bin"),
args: [42, "Hello, world!"],
options: %{
@ -211,11 +216,11 @@ defmodule EXW3Test do
}
)
ExW3.Contract.at(Complex, address)
ExW3.Contract.at(:Complex, address)
assert address == ExW3.Contract.address(Complex)
assert address == ExW3.Contract.address(:Complex)
{:ok, foo, foobar} = ExW3.Contract.call(Complex, :getBoth)
{:ok, foo, foobar} = ExW3.Contract.call(:Complex, :getBoth)
assert foo == 42
@ -223,11 +228,11 @@ defmodule EXW3Test do
end
test "starts a Contract GenServer for AddressTester contract", context do
ExW3.Contract.start_link(AddressTester, abi: context[:address_tester_abi])
ExW3.Contract.register(:AddressTester, abi: context[:address_tester_abi])
{:ok, address, _} =
ExW3.Contract.deploy(
AddressTester,
:AddressTester,
bin: ExW3.load_bin("test/examples/build/AddressTester.bin"),
options: %{
from: Enum.at(context[:accounts], 0),
@ -235,15 +240,15 @@ defmodule EXW3Test do
}
)
ExW3.Contract.at(AddressTester, address)
ExW3.Contract.at(:AddressTester, address)
assert address == ExW3.Contract.address(AddressTester)
assert address == ExW3.Contract.address(:AddressTester)
formatted_address =
Enum.at(context[:accounts], 0)
|> ExW3.format_address
{:ok, same_address} = ExW3.Contract.call(AddressTester, :get, [formatted_address])
{:ok, same_address} = ExW3.Contract.call(:AddressTester, :get, [formatted_address])
assert ExW3.to_address(same_address) == Enum.at(context[:accounts], 0)
end
@ -283,11 +288,11 @@ defmodule EXW3Test do
test "returns proper error messages at contract deployment", context do
ExW3.Contract.start_link(SimpleStorage, abi: context[:simple_storage_abi])
ExW3.Contract.register(:SimpleStorage, abi: context[:simple_storage_abi])
assert {:error, :missing_gas} ==
ExW3.Contract.deploy(
SimpleStorage,
:SimpleStorage,
bin: ExW3.load_bin("test/examples/build/SimpleStorage.bin"),
args: [],
options: %{
@ -297,7 +302,7 @@ defmodule EXW3Test do
assert {:error, :missing_sender} ==
ExW3.Contract.deploy(
SimpleStorage,
:SimpleStorage,
bin: ExW3.load_bin("test/examples/build/SimpleStorage.bin"),
args: [],
options: %{
@ -307,7 +312,7 @@ defmodule EXW3Test do
assert {:error, :missing_binary} ==
ExW3.Contract.deploy(
SimpleStorage,
:SimpleStorage,
args: [],
options: %{
gas: 300_000,
@ -318,11 +323,11 @@ defmodule EXW3Test do
end
test "return proper error messages at send and call", context do
ExW3.Contract.start_link(SimpleStorage, abi: context[:simple_storage_abi])
ExW3.Contract.register(:SimpleStorage, abi: context[:simple_storage_abi])
{:ok, address, _} =
ExW3.Contract.deploy(
SimpleStorage,
:SimpleStorage,
bin: ExW3.load_bin("test/examples/build/SimpleStorage.bin"),
args: [],
options: %{
@ -331,13 +336,13 @@ defmodule EXW3Test do
}
)
assert {:error, :missing_address} == ExW3.Contract.call(SimpleStorage, :get)
assert {:error, :missing_address} == ExW3.Contract.send(SimpleStorage, :set, [1], %{from: Enum.at(context[:accounts], 0), gas: 50_000})
assert {:error, :missing_address} == ExW3.Contract.call(:SimpleStorage, :get)
assert {:error, :missing_address} == ExW3.Contract.send(:SimpleStorage, :set, [1], %{from: Enum.at(context[:accounts], 0), gas: 50_000})
ExW3.Contract.at(SimpleStorage, address)
ExW3.Contract.at(:SimpleStorage, address)
assert {:error, :missing_sender} == ExW3.Contract.send(SimpleStorage, :set, [1], %{gas: 50_000})
assert {:error, :missing_gas} == ExW3.Contract.send(SimpleStorage, :set, [1], %{from: Enum.at(context[:accounts], 0)})
assert {:error, :missing_sender} == ExW3.Contract.send(:SimpleStorage, :set, [1], %{gas: 50_000})
assert {:error, :missing_gas} == ExW3.Contract.send(:SimpleStorage, :set, [1], %{from: Enum.at(context[:accounts], 0)})
end

Loading…
Cancel
Save