GithubHelp home page GithubHelp logo

hswick / exw3 Goto Github PK

View Code? Open in Web Editor NEW
149.0 11.0 45.0 549 KB

High level Ethereum RPC Client for Elixir

License: Apache License 2.0

Elixir 96.97% Shell 0.40% Solidity 2.63%
ethereumex ethereum web3

exw3's Introduction

ExW3

Build Status Module Version Hex Docs Total Download License Last Updated

Installation

The package can be installed by adding :exw3 to your list of dependencies in mix.exs:

def deps do
  [
    {:exw3, "~> 0.6"}
  ]
end

Overview

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

Ensure you have an ethereum node to connect to at the specified url in your config. An easy local testnet to use is ganache-cli:

$ ganache-cli

Or you can use parity: Install Parity, then run it with

$ echo > passfile
parity --chain dev --unlock=0x00a329c0648769a73afac7f9381e08fb43dbea72 --reseal-min-period 0 --password passfile

If Parity complains about password or missing account, try

$ parity --chain dev --unlock=0x00a329c0648769a73afac7f9381e08fb43dbea72

HTTP

To use Ethereumex's HttpClient simply set your config like this:

config :ethereumex,
  client_type: :http,
  url: "http://localhost:8545"

IPC

If you want to use IpcClient set your config to something like this:

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.

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 commands.

Check out the documentation for more details of the API.

Example

iex(1)> accounts = ExW3.accounts()
["0x00a329c0648769a73afac7f9381e08fb43dbea72"]
iex(2)> ExW3.balance(Enum.at(accounts, 0))
1606938044258990275541962092341162602522200978938292835291376
iex(3)> ExW3.block_number()
1252
iex(4)> simple_storage_abi = ExW3.Abi.load_abi("test/examples/build/SimpleStorage.abi")
%{
  "get" => %{
    "constant" => true,
    "inputs" => [],
    "name" => "get",
    "outputs" => [%{"name" => "", "type" => "uint256"}],
    "payable" => false,
    "stateMutability" => "view",
    "type" => "function"
  },
  "set" => %{
    "constant" => false,
    "inputs" => [%{"name" => "_data", "type" => "uint256"}],
    "name" => "set",
    "outputs" => [],
    "payable" => false,
    "stateMutability" => "nonpayable",
    "type" => "function"
  }
}
iex(5)> ExW3.Contract.start_link
{:ok, #PID<0.265.0>}
iex(6)> ExW3.Contract.register(:SimpleStorage, abi: simple_storage_abi)
:ok
iex(7)> {:ok, address, tx_hash} = ExW3.Contract.deploy(:SimpleStorage, bin: ExW3.Abi.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(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}

Address Type

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.Utils.hex_to_integer("0x88838e84a401a1d6162290a1a765507c4a83f5e050658a83992a912f42149ca5")

Events

ExW3 allows the retrieval of event logs using filters or transaction receipts. In this example we will demonstrate a filter. Assume we have already deployed and registered a contract called EventTester.

# We can optionally specify extra parameters like `:fromBlock`, and `:toBlock`
{:ok, filter_id} = ExW3.Contract.filter(:EventTester, "Simple", %{fromBlock: 42, toBlock: "latest"})

# After some point that we think there are some new changes
{:ok, changes} = ExW3.Contract.get_filter_changes(filter_id)

# We can then uninstall the filter after we are done using it
ExW3.Contract.uninstall_filter(filter_id)

Indexed Events

Ethereum allows a user to add topics to filters. This means the filter will only return events with the specific index parameters. For all of the extra options see here

If you have written your event in Solidity like this:

event SimpleIndex(uint256 indexed num, bytes32 indexed data, uint256 otherNum);

You can add a filter on which logs will be returned back to the RPC client based on the indexed fields.

ExW3 allows for 2 ways of specifying these parameters (:topics) in two ways. The first, and probably more preferred way, is with a map:

indexed_filter_id = ExW3.Contract.filter(
  :EventTester,
  "SimpleIndex",
  %{
    topics: %{num: 46, data: "Hello, World!"},
  }
)

The other option is a list (mapped version is an abstraction over this). The downside here is this is order dependent. Any values you don't want to specify must be represented with a nil. This approach has been included because it is the implementation of the JSON RPC spec.

indexed_filter_id = ExW3.Contract.filter(
  :EventTester,
  "SimpleIndex",
  %{
    topics: [nil, "Hello, World!"]
  }
)

Here we are skipping the num topic, and only filtering on the data parameter.

NOTE!!! These two approaches are mutually exclusive, and for almost all cases you should prefer the map.

Continuous Event Handling

In many cases, you will want some process to continuously listen for events. We can implement this functionality using a recursive function. Since Elixir uses tail call optimization, we won't have to worry about blowing up the stack.

def listen_for_event do
  {:ok, changes} = ExW3.Contract.get_filter_changes(filter_id) # Get our changes from the blockchain
  handle_changes(changes) # Some function to deal with the data. Good place to use pattern matching.
  :timer.sleep(1000) # Some delay in milliseconds. Recommended to save bandwidth, and not spam.
  listen_for_event() # Recurse
end

Compiling Solidity

To compile the test solidity contracts after making a change run this command:

$ solc --abi --bin --overwrite -o test/examples/build test/examples/contracts/*.sol

Contributing

Test

The full test suite requires a running blockchain. You can run your own or start openethereum with docker-compose.

$ docker-compose up
$ mix test

Copyright and License

Copyright (c) 2018 Harley Swick

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

exw3's People

Contributors

aguxez avatar ayrat555 avatar dependabot[bot] avatar dsummers91 avatar hswick avatar kianmeng avatar mjia-catalx avatar mosic avatar pik694 avatar rupurt avatar timjp87 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

exw3's Issues

Problem with ABI.TypeDecoder

Hi!

I have a problem with ExW3.Contract.call method and ABI.TypeDecoder.decode_type.

Solidity function:
function get_DFA(uint dfaID) public returns (DFA memory) { DFA storage dfa = dfa_pool[dfaID]; return dfa; }

Elixir function:

def sc_call(sc_abi_path, sc_address, sc_name, sc_method, sc_method_args \\ []) do
    ExW3.Contract.start_link()
    sc_abi = ExW3.Abi.load_abi(sc_abi_path)
    ExW3.Contract.register(String.to_atom(sc_name), abi: sc_abi)
    ExW3.Contract.at(String.to_atom(sc_name), sc_address)
    ExW3.Contract.call(String.to_atom(sc_name), String.to_atom(sc_method), sc_method_args)
end

When I call this method from ElixirApp, I get a error

ElixirApp.Utils.Ethereum.sc_call("data/DFAPool.abi", "0x39Df1178CCBeC6d48A81969b6f67f13F3B4CC6E3", "DFAPool", "get_DFA", [1])

00:35:02.056 [error] GenServer ContractManager terminating
** (FunctionClauseError) no function clause matching in ABI.TypeDecoder.decode_type/2
(ex_abi 0.5.3) lib/abi/type_decoder.ex:244: ABI.TypeDecoder.decode_type(:tuple, <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...>>)
(ex_abi 0.5.3) lib/abi/type_decoder.ex:270: anonymous fn/3 in ABI.TypeDecoder.decode_type/2
(elixir 1.11.4) lib/enum.ex:2193: Enum."-reduce/3-lists^foldl/2-0-"/3
(ex_abi 0.5.3) lib/abi/type_decoder.ex:265: ABI.TypeDecoder.decode_type/2
(ex_abi 0.5.3) lib/abi/type_decoder.ex:189: anonymous fn/3 in ABI.TypeDecoder.do_decode_raw/2
(elixir 1.11.4) lib/enum.ex:2193: Enum."-reduce/3-lists^foldl/2-0-"/3
(ex_abi 0.5.3) lib/abi/type_decoder.ex:184: ABI.TypeDecoder.do_decode_raw/2
(ex_abi 0.5.3) lib/abi/type_decoder.ex:178: ABI.TypeDecoder.decode_raw/2
Last message (from #PID<0.255.0>): {:call, {:DFAPool, :get_DFA, [1]}}

I don't understand what's happened.

Experimental ABI features crash.

For contracts that have been compiled with pragma experimental ABIEncoderV2;, and return an array for example:

  function actors() external view returns (address[] memory) {
    return actorList.values;
  }

ExW3 doesn't return, hogs the CPU and the memory.

get_filter_changes_batch

Would be useful (possibly more performant) to grab a batch of changes for a list of filters.

This would mean less trips over the wire to the Ethereum node.

Provide a nonblocking send using cast instead of call

For the most part any time someone interacts with the blockchain they want some feedback. However, there are cases of fire and forget and the easiest way to implement this would be to provide a send method that uses GenServer cast instead of call.

Leaving this issue open for others to comment on.

Encode bytes32 error

I am trying to call a function with the following function signature:

get(bytes32)

I tried the following:

ExW3.Contract.call(:my_contract, :get, ["63cb2f697029ee491b58939796e9013087104b816ce0f51e731e9b03cc50c3b9"])

I get the following error:

[error] GenServer ContractManager terminating
** (RuntimeError) size mismatch for bytes32: "63cb2f697029ee491b58939796e9013087104b816ce0f51e731e9b03cc50c3b9"
    lib/abi/type_encoder.ex:178: ABI.TypeEncoder.encode_type/2
    lib/abi/type_encoder.ex:199: anonymous fn/2 in ABI.TypeEncoder.encode_type/2
    (elixir) lib/enum.ex:1940: Enum."-reduce/3-lists^foldl/2-0-"/3
    lib/abi/type_encoder.ex:191: ABI.TypeEncoder.encode_type/2
    lib/abi/type_encoder.ex:141: ABI.TypeEncoder.do_encode/3
    (exw3) lib/exw3.ex:442: ExW3.encode_method_call/3
    (exw3) lib/exw3.ex:697: ExW3.Contract.eth_call_helper/4
    (exw3) lib/exw3.ex:959: ExW3.Contract.handle_call/3

Is the hex string supposed to be encoded in a different format before providing in the arguments?

Returning error instead of raising when a transaction is not mined.

At the moment when you try to check the receipt of a transaction tx_receipt/1 with a hash of a transaction which has not been mined yet, the function will raise a BadMapError I think it should be better to return an error ({:error, :not_mined} in this case) so we don't cut operations of related functions outside of the library,

Singing with PK

Does this library support signing transactions with a given PK? I can't seem to find any details in the documentation.
I don't want to use accounts since I'll be using Alchemy RPC.

Can't compile ex_keccak

This is the the error I am getting when trying to compile. I'm using a Mac Air with an M1 processor. Any ideas how to fix this?

$ mix compile
==> ex_keccak
Compiling 1 file (.ex)

== Compilation error in file lib/ex_keccak.ex ==
** (ErlangError) Erlang error: :enoent
    (elixir 1.13.2) lib/system.ex:1044: System.cmd("cargo", ["metadata", "--format-version=1"], [cd: "native/exkeccak"])
    (rustler 0.22.2) lib/rustler/compiler/config.ex:81: Rustler.Compiler.Config.external_resources/2
    (rustler 0.22.2) lib/rustler/compiler/config.ex:69: Rustler.Compiler.Config.build/1
    (rustler 0.22.2) lib/rustler/compiler.ex:9: Rustler.Compiler.compile_crate/2
    lib/ex_keccak.ex:2: (module)
could not compile dependency :ex_keccak, "mix compile" failed. Errors may have been logged above. You can recompile this dependency with "mix deps.compile ex_keccak", update it with "mix deps.update ex_keccak" or clean it with "mix deps.clean ex_keccak"

Signing Transactions

ethereumex explains a way to do this, but it requires the Blockchain hex package. Considering, that the entire Blockchain project probably isnt necessary for this feature, it may make sense to extract those parts out.

The thinking would be something like this (taken from etheremex README):

eth_send_raw_transaction example - Payable smart contract call

Calling a smart contract method that requires computation will cost you gas or ether (if that method requires payment also). This means you will have to sign your transactions using the private key that owns some ethereum. In order to send signed transactions you will need both ABI and Blockchain hex packages.

abi_encoded_data = ABI.encode("transferFrom(address,address,uint)", [from_address, to_address, token_id])
contract_address = "0x123" |> String.slice(2..-1) |> Base.decode16(case: :mixed)

transaction_data = %Blockchain.Transaction{
    data: abi_encoded_data,
    gas_limit: 100_000,
    gas_price: 16_000_000_000,
    init: <<>>,
    nonce: 5,
    to: contract_address,
    value: 0
}
|> Blockchain.Transaction.Signature.sign_transaction(private_key)
|> Blockchain.Transaction.serialize()
|> ExRLP.encode()
|> Base.encode16(case: :lower)

Ethereumex.HttpClient.eth_send_raw_transaction("0x" <> transaction_data)

Permissioned DLTs - Quorum

Hi,

I am using permissioned DLT -> Quorum from Consensys (previously JPMorgan). This is basically "enterprise functionaliy" rich version of Ethereum. Most important one are private contracts and private transactions.

For instance:
https://docs.goquorum.consensys.net/en/stable/HowTo/Use/DevelopingSmartContracts/#creating-private-transactionscontracts

Can I handle this with exw3? Deployment of such contract, calling it later?
Can't really see from the documentation.

Thanks

Add watcher to auto-compile Solidity Files

Hello guys!

First off I wanna thank you for the awesome, although concise library!

But this section/feature on Readme, got me thinking, maybe could we automate this?

To compile the test solidity contracts after making a change run this command:

$ solc --abi --bin --overwrite -o test/examples/build test/examples/contracts/*.sol

And such, I did as a MVP, a simple implementation, that you can check out here. I did some changes, implemented better automations, and error treatments. But unfortunately I couldn't create a branch here ๐Ÿ˜ข .

So Here's my request, can we got my PR here, and such we can get this working? I already integrated with the library, and tested in a sample Phoenix Framework Project:

image

Test arg count error

There should be a throw triggered if the args count given is not the same as the input types of the abi. But I need to test this more.

As well, need to ensure the error message is good.

Calling function that requires bytes argument fails

I have been running into a case where if I use ExW3 with a solidity smart contract that needs a bytes function argument it will fail. I think this might be related to how the encoding works within ExW3, however when I bypass ExW3 and use ABI directly it works:

ABI.encode("setTest(bytes)", [encoded_data])

From what I can tell, there seems to be an extra 64 bytes of data being included in the ExW3 version that I am not sure where it is coming from.

Example:

# Fails when submitted as part of the transaction

# the variable `abi` was parsed from the contract abi file using ExW3
ExW3.Abi.encode_method_call(abi, "setTest", [<<1,2,3,4>>])
"0af724830000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000"

# Works when submitted as part of the transaction

ABI.encode("setTest(bytes)", [<<1,2,3,4>>]) |> Base.encode16(case: :lower)
"0af72483000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000040102030400000000000000000000000000000000000000000000000000000000"

Looking at the output it seems to be include a second "0x2" block of 64 bytes so I think something is getting wrapped twice.

Allow a way to parse get_logs the same way the Contract.get_filter_changes works

Right now get_filter_changes can return a feed of logs parsed to the contract's events. Would be great to open up the capability to do this for get_logs so you can use the same parsing for both current and historical blocks. Easiest way may be to open up two methods. 1 that would take an ABI, convert it and return the event_attributes and then either open up format_log_data or refactor the format logs code in the Genserver call for :get_filter_changes and make that public. get_logs probably should not be a genserver call as someone may make tons of calls going through 10k's of blocks historically.

Happy to submit this or open to another alternatives!

How to send ERC20 tokens using ExW3?

Could you please provide an example of sending ERC20 tokens using ExW3? I have the following ABI:

// transfer
  {
    "constant": false,
    "inputs": [
      {
        "name": "_to",
        "type": "address"
      },
      {
        "name": "_value",
        "type": "uint256"
      }
    ],
    "name": "transfer",
    "outputs": [
      {
        "name": "",
        "type": "bool"
      }
    ],
    "type": "function"
  }

and would like to send USDT from one ethereum address to another.

ABI function calls with output type "string" Decode (MatchError)

I'm trying to call an abi's :symbol method in an erc721 smartcontract but get an error whenever I call a method that returns and abi function output with "string" type. Methods that return address or uint256 work fine... Is this an issue with exw3 or ex_abi? I noticed that this repo doesn't test for smart-contracts calls that have return outputs of "string"

GenServer ContractManager terminating
** (MatchError) no match of right hand side value: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>
    (ex_abi 0.5.5) lib/abi/type_decoder.ex:238: ABI.TypeDecoder.decode_bytes/4
    (ex_abi 0.5.5) lib/abi/type_decoder.ex:333: anonymous fn/3 in ABI.TypeDecoder.decode_type/3
    (elixir 1.11.4) lib/enum.ex:2193: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ex_abi 0.5.5) lib/abi/type_decoder.ex:331: ABI.TypeDecoder.decode_type/3
    (ex_abi 0.5.5) lib/abi/type_decoder.ex:188: anonymous fn/3 in ABI.TypeDecoder.do_decode_raw/2
    (elixir 1.11.4) lib/enum.ex:2193: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ex_abi 0.5.5) lib/abi/type_decoder.ex:185: ABI.TypeDecoder.do_decode_raw/2
    (ex_abi 0.5.5) lib/abi/type_decoder.ex:179: ABI.TypeDecoder.decode_raw/2
    (exw3 0.6.0) lib/exw3/abi.ex:52: ExW3.Abi.decode_output/3
    (exw3 0.6.0) lib/exw3/contract.ex:214: ExW3.Contract.eth_call_helper/4
    (exw3 0.6.0) lib/exw3/contract.ex:480: ExW3.Contract.handle_call/3
    (stdlib 3.14.2.1) gen_server.erl:715: :gen_server.try_handle_call/4
    (stdlib 3.14.2.1) gen_server.erl:744: :gen_server.handle_msg/6
    (stdlib 3.14.2.1) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.639.0>): {:call, {:"0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D", :symbol, []}}

This issues is occurring on this line when the output decoded type is "string" https://github.com/hswick/exw3/blob/master/lib/exw3/abi.ex#L49

Contract Manager can't access ExthCrypto library

Hi,

I'm using exw3 to deploy a ETH 2 Validator Deposit Contract via a Mix Task:
https://github.com/timjp87/panacea/blob/master/apps/beaconchain/lib/mix/tasks/deploy.ex

However, when I run "mix deploy" it can't access ExthCrypto.Hash.Keccak.kec/1 which should be available through the ethereumex dependency, right?

16:56:05.451 [info]  Deploying validator deposit contract.
16:56:05.493 [info]  Started Ethereumex Application.
16:56:05.595 [info]  Got list of accounts from RPC.
16:56:05.596 [info]  Started contract manager.
16:56:05.597 [info]  Loaded contract ABI.
16:56:05.597 [info]  Contract registered.

16:56:05.614 [error] GenServer ContractManager terminating
** (UndefinedFunctionError) function ExthCrypto.Hash.Keccak.kec/1 is undefined (module ExthCrypto.Hash.Keccak is not available)
    ExthCrypto.Hash.Keccak.kec("Deposit(bytes,bytes)")
    lib/exw3.ex:293: ExW3.encode_event/1
    lib/exw3.ex:587: anonymous fn/1 in ExW3.Contract.init_events/1
    (elixir) lib/enum.ex:1327: Enum."-map/2-lists^map/1-0-"/2
    lib/exw3.ex:583: ExW3.Contract.init_events/1
    lib/exw3.ex:720: ExW3.Contract.register_helper/1
    lib/exw3.ex:742: ExW3.Contract.handle_cast/2
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4```

Provide timeout as a config value

In the following function

@spec call(atom(), atom(), list()) :: {:ok, any()}
@doc "Use a Contract's method with an eth_call"
def call(contract_name, method_name, args \\ []) do
  GenServer.call(ContractManager, {:call, {contract_name, method_name, args}})
end

the GenServer request times out quite a few times while accessing data on the mainnet (because the default timeout is 5 seconds).

The timeout of Ethereumex.HttpClient can be set to :infinity, but the request times out at exw3.

Will it be helpful in allowing setting the GenServer timeout as a config value?

Eth unit converters

Would be nice to have unit converters. This is a handy web3 util, that should be included here as well

Assertion error sending to contract...

    accounts = ExW3.accounts()
    simple_storage_abi = ExW3.Abi.load_abi("abi/MyContract.abi")

    ExW3.Contract.start_link
    ExW3.Contract.register(:MyContract, abi: simple_storage_abi)
    ExW3.Contract.at(:MyContract, Application.get_env(:ethereumex, :contract_address) |> Base.encode16(case: :lower) )

    ExW3.Contract.send(:MyContract, :MyFunction, [1, "Foo"], %{from: Enum.at(accounts, 0), gas: 50_000})

"AssertionError [ERR_ASSERTION]: The field to must have byte length of 20\n at Transaction.setter [as to] (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/node_modules/ethereumjs-util/src/object.ts:57:9)\n at /tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/lib/utils/transaction.js:116:23\n at Array.forEach ()\n at initData (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/lib/utils/transaction.js:110:18)\n at new Transaction (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/lib/utils/transaction.js:168:5)\n at Function.fromJSON (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/lib/utils/transaction.js:228:16)\n at StateManager.queueTransaction (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/lib/statemanager.js:371:22)\n at GethApiDouble.eth_sendTransaction (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/lib/subproviders/geth_api_double.js:325:14)\n at GethApiDouble.handleRequest (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/lib/subproviders/geth_api_double.js:109:10)\n at next (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/node_modules/web3-provider-engine/index.js:136:18)\n at GethDefaults.handleRequest (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/lib/subproviders/gethdefaults.js:15:12)\n at next (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/node_modules/web3-provider-engine/index.js:136:18)\n at SubscriptionSubprovider.FilterSubprovider.handleRequest (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/node_modules/web3-provider-engine/subproviders/filters.js:89:7)\n at SubscriptionSubprovider.handleRequest (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/node_modules/web3-provider-engine/subproviders/subscriptions.js:137:49)\n at next (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/node_modules/web3-provider-engine/index.js:136:18)\n at DelayedBlockFilter.handleRequest (/tmp/.mount_ganachnT4wTL/resources/static/node/node_modules/ganache-core/lib/subproviders/delayedblockfilter.js:31:3)"

Single Contract process

One suggestion I've received is to have a single process that handles all the requests for every contract. This seems to be a more idiomatic style for Elixir apps, where this process is in charge of any Contract related service. One benefit would be that this single process would be easier to pass around different modules, and you would only have to start it in the Application supervisor.

I was going to implement this and realized it is a very involved change because it completely changes the way state is handled starting from init to every single call. This would also be a very breaking change, so I think it is best to sit on it, and see what others think.

Issues With Memory String Consumption In Smart Contracts

When attempting to consume a String in a contract function, ExW3 seems to handle encoding improperly, causing variables to mix and or set improperly in the contracts state.

Smart Contract Example:

pragma solidity >=0.4.22 <0.9.0;

contract Tester {
  address public currentAddr;
  uint256 public currentInt;
  string public sym;

  function setOnlyStringBefore(string memory _sym, address new_addr, uint256 new_int) public {
    currentAddr = new_addr;
    currentInt = new_int;
    sym = _sym;
  }

  function setOnlyStringAfter(address new_addr, uint256 new_int, string memory _sym) public {
    currentAddr = new_addr;
    currentInt = new_int;
    sym = _sym;
  }

  function setOnlyAddrAndInt(address new_addr, uint256 new_int) public {
    currentAddr = new_addr;
    currentInt = new_int;
  }
}

Scenario 1

(Function setOnlyStringBefore) - string is set before address and uint256 in smart contract function parameters

Using ExW3 ->
ExW3.Contract.send(:Tester, :setOnlyStringBefore, ["name", "0x0255bff90b8787f06cf13ab325997cbf3b139c1d", 10], %{from: @contracts_owner, gas: 6000000})

{:ok, "0x1c6ac0864e8e6418cfc743200ee74ad06bb2442bd5842fd73c2d12bb835db0dc"}

In Truffle ->

truffle(k8s)> let test = await Tester.deployed()

truffle(k8s)> test.sym()

Thrown:
{ Error: invalid codepoint at offset 14; unexpected continuation byte (argument="bytes", value={"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":2,"13":85,"14":191,"15":249,"16":11,"17":135,"18":135,"19":240,"20":108,"21":241,"22":58,"23":179,"24":37,"25":153,"26":124,"27":191,"28":59,"29":19,"30":156,"31":29,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":10,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":4}, code=INVALID_ARGUMENT, version=strings/5.0.5)
    at evalmachine.<anonymous>:0:6
    at sigintHandlersWrap (vm.js:288:15)
    at Script.runInContext (vm.js:130:14)
    at runScript (/usr/local/lib/node_modules/truffle/build/webpack:/packages/core/lib/console.js:251:1)
    at Console.interpret (/usr/local/lib/node_modules/truffle/build/webpack:/packages/core/lib/console.js:266:1)
    at bound (domain.js:402:14)
    at REPLServer.runBound [as eval] (domain.js:415:12)
    at REPLServer.onLine (repl.js:642:10)
    at REPLServer.emit (events.js:198:13)
    at REPLServer.EventEmitter.emit (domain.js:448:20)
    at REPLServer.Interface._onLine (readline.js:308:10)
    at REPLServer.Interface._line (readline.js:656:8)
    at REPLServer.Interface._ttyWrite (readline.js:937:14)
    at REPLServer.self._ttyWrite (repl.js:715:7)
    at ReadStream.onkeypress (readline.js:184:10)
    at ReadStream.emit (events.js:198:13)
    at ReadStream.EventEmitter.emit (domain.js:448:20)
    at emitKeys (internal/readline.js:424:14)
    at emitKeys.next (<anonymous>)
    at ReadStream.onData (readline.js:1073:36)
    at ReadStream.emit (events.js:198:13)
    at ReadStream.EventEmitter.emit (domain.js:448:20)
    at addChunk (_stream_readable.js:288:12)
    at readableAddChunk (_stream_readable.js:269:11)
    at ReadStream.Readable.push (_stream_readable.js:224:10)
    at TTY.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
  reason:
   'invalid codepoint at offset 14; unexpected continuation byte',
  code: 'INVALID_ARGUMENT',
  argument: 'bytes',
  value:
   Uint8Array [
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     2,
     85,
     191,
     249,
     11,
     135,
     135,
     240,
     108,
     241,
     58,
     179,
     37,
     153,
     124,
     191,
     59,
     19,
     156,
     29,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     10,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     0,
     4 ],
  baseType: 'string',
  name: null,
  type: 'string',
  hijackedStack:
   'Error: invalid codepoint at offset 14; unexpected continuation byte (argument="bytes", value={"0":0,"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":0,"11":0,"12":2,"13":

truffle(k8s)> test.currentAddr()
'0x0000000000000000000000000000000000000060'

truffle(k8s)> test.currentInt()
<BN: 255bff90b8787f06cf13ab325997cbf3b139c1d>

Note: The transaction is successful but the sym is set as a byte array and the address and uint256 values are swapped. They are passed in as

[name, new_addr, new_int]

and the function consumes them as

(string memory _sym, address new_addr, uint256 new_int)

however you can see in the above .currentAddr() and .currentInt() that the addr is set as a hex encoded value of 10 and the int is set as a BigNumber conversion of the address.

Note: This function is successful when called directly from truffle

truffle(k8s)> test.setOnlyStringBefore("Name", "0xfe5e669eD4C62A7D4621A604BcC1029171814046", 10)
{ tx:
   '0x1e7f54778810d2ea33b604dc2e1791b05b69540cd77a2357e8572f3f3f580588',
  receipt:
   { blockHash:
      '0x28a3a821daf8b6d8844b330f355ac74e46ee46c900f1be5b748c166357b044c2',
     blockNumber: 878,
     contractAddress: null,
     cumulativeGasUsed: 85007,
     from: '0xfe5e669ed4c62a7d4621a604bcc1029171814046',
     gasUsed: 85007,
     logs: [],
     logsBloom:
      '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
     status: true,
     to: '0x3c7d058ccb934c732674e28b4c45bcc722fb0004',
     transactionHash:
      '0x1e7f54778810d2ea33b604dc2e1791b05b69540cd77a2357e8572f3f3f580588',
     transactionIndex: 0,
     rawLogs: [] },
  logs: [] }

truffle(k8s)> test.sym()

'Name'

truffle(k8s)> test.currentAddr()

'0xfe5e669eD4C62A7D4621A604BcC1029171814046'

truffle(k8s)> test.currentInt()

<BN: a>


Scenerio 2

### (Function setOnlyStringAfter) - string is set after address and uint256 in smart contract function parameters

ExW3.Contract.send(:Tester, :setOnlyStringAfter, [10, "0x0255bff90b8787f06cf13ab325997cbf3b139c1d", "name"], %{from: @contracts_owner, gas: 6000000})

{:ok, "0xcb1e0f29d7a0fa5bc7822c2d9302b12cc5a22b02a52323d3ac3bad1a01b64634"}

In Truffle ->

truffle(k8s)> let test = await Tester.deployed()

truffle(k8s)> test.sym()

'' //// <--- Value never got set

truffle(k8s)> web3.eth.getTransactionReceipt("0xcb1e0f29d7a0fa5bc7822c2d9302b12cc5a22b02a52323d3ac3bad1a01b64634")
{ blockHash:
   '0x146459c31989cc9cfe8ae9025c31701566b5a3da14b74f8e69b97bfe4290d983',
  blockNumber: 877,
  contractAddress: null,
  cumulativeGasUsed: 24203,
  from: '0xfe5e669ed4c62a7d4621a604bcc1029171814046',
  gasUsed: '0x5e8b',
  logs: [],
  logsBloom:
   '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
  status: false,
  to: '0x3c7d058ccb934c732674e28b4c45bcc722fb0004',
  transactionHash:
   '0xcb1e0f29d7a0fa5bc7822c2d9302b12cc5a22b02a52323d3ac3bad1a01b64634',
  transactionIndex: 0 }

Note: The transaction status is 'false', meaning that the transaction has failed for some reason.

Note: The function call is successful when called from Truffle

Issue with calling smart contract

ExW3.Contract.start_link()
ExW3.Contract.register(:Contract, abi: ExW3.Abi.load_abi("path/to/abis/erc721.json"))
ExW3.Contract.at(:Contract, "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d")
url = Spool.Chain.Helpers.get_endpoint_for_network(1)
Application.put_env(:ethereumex, :url, url)
ExW3.Contract.call(:Contract, :symbol)
[error] GenServer ContractManager terminating
** (MatchError) no match of right hand side value: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>
    (ex_abi 0.5.16) lib/abi/type_decoder.ex:238: ABI.TypeDecoder.decode_bytes/4
    (ex_abi 0.5.16) lib/abi/type_decoder.ex:327: anonymous fn/3 in ABI.TypeDecoder.decode_type/3
    (elixir 1.14.1) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ex_abi 0.5.16) lib/abi/type_decoder.ex:325: ABI.TypeDecoder.decode_type/3
    (ex_abi 0.5.16) lib/abi/type_decoder.ex:188: anonymous fn/3 in ABI.TypeDecoder.do_decode_raw/2
    (elixir 1.14.1) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ex_abi 0.5.16) lib/abi/type_decoder.ex:185: ABI.TypeDecoder.do_decode_raw/2
    (ex_abi 0.5.16) lib/abi/type_decoder.ex:179: ABI.TypeDecoder.decode_raw/2
    (exw3 0.6.1) lib/exw3/abi.ex:52: ExW3.Abi.decode_output/3
    (exw3 0.6.1) lib/exw3/contract.ex:214: ExW3.Contract.eth_call_helper/4
    (exw3 0.6.1) lib/exw3/contract.ex:480: ExW3.Contract.handle_call/3
    (stdlib 4.1.1) gen_server.erl:1149: :gen_server.try_handle_call/4
    (stdlib 4.1.1) gen_server.erl:1178: :gen_server.handle_msg/6
    (stdlib 4.1.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.840.0>): {:call, {:Contract, :symbol, []}}

#153

Here is a quick fix

Error when decoding array of uint256

I'm getting a MatchError raised by ex_abi when I use this library to call a contract's method that returns an array of uint256. Initially I thought it was a bug in ex_abi but based on the evidence provided in poanetwork/ex_abi#98 it seems that there might something wrong on how this library is handling the response from the RPC call and passing that to ex_abi for decoding.

The issue I reference in ex_abi has all the required information to reproduce the bug

Fix CI server

Travis is now using old Elixir and OTP, which causes mix test command to fail, but Travis still thinks that it has not failed, because it checks status of the last command and the command is the whole script, which last line simply kills parity so it is always successful.

The command "bash travis_test.sh" exited with 0.

Implement send_async and call_async using Task

A nice enhancement would be to make it easy to interact with the client asynchronously. What I'm thinking is that the Contract process acts as a Supervision tree for Tasks to be created for interacting with the blockchain.

The idea is that send_async and call_async return tasks that have to be awaited on using Task.await. Also these tasks would be managed with the Supervision tree.

Reasoning behind this is that it will probably be a common pattern when people need this functionality to asynchronously so this library can make it very easy.

Passing string as parameter throws error

I create 2 diff contracts, one with set function receiving integer as parameter, the other receiving string.

First works fine and the second one fails with this message:

{:error,
 %{
   "code" => -32000,
   "data" => %{
     "0xe20bb21666d462cfec33dad694cae424fadd38cdc4820970c8769f2e7690f149" => %{
       "error" => "revert",
       "program_counter" => 5281,
       "return" => "0x"
     },
     "name" => "RuntimeError",
     "stack" => "RuntimeError: VM Exception while processing transaction: revert\n    at Function.RuntimeError.fromResults (/tmp/.mount_ganachjrwblU/resources/static/node/node_modules/ganache-core/lib/utils/runtimeerror.js:94:13)\n    at BlockchainDouble.processBlock (/tmp/.mount_ganachjrwblU/resources/static/node/node_modules/ganache-core/lib/blockchain_double.js:627:24)\n    at runMicrotasks (<anonymous>)\n    at processTicksAndRejections (internal/process/task_queues.js:93:5)"
   },
   "message" => "VM Exception while processing transaction: revert"
 }}

I've tried converting string to hex and didn't work either.

Please add an example on readme on how to format string before passing it to contract function.

EventListener should use the GenServer abstraction

Right now EventListener module uses the loop receive pattern. However, I'm learning that this is an antipattern, in that using basic processes is like using assembly language.

I also have a suspicion that EventPoller's behavior could be rolled into the EventListener GenServer which might decrease the amount of code.

Better error messages for missing options

Thanks @pik694 exw3 will throw an error message if you don't include gas, from, or contract address in a transaction. Which helps with debugging.

Right now the error messages look like this:

** (MatchError) no match of right hand side value: {:error, :missing_gas}

Which is useful, and does tell what is going wrong. But I think it would be better if we could have a more readable and clear error message like

(MissingOptionsError) transaction request is missing the :gas field

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.