Awesome
ExABI
The Application Binary Interface (ABI) of Solidity describes how to transform binary data to types which the Solidity programming language understands. For instance, if we want to call a function bark(uint32,bool)
on a Solidity-created contract contract Dog
, what data
parameter do we pass into our Ethereum transaction? This project allows us to encode such function calls.
Installation
If available in Hex, the package can be installed
by adding ex_abi
and ex_keccak
to your list of dependencies in mix.exs
:
def deps do
[
{:ex_abi, "~> 0.8.1"},
{:ex_keccak, "~> 0.7.5"}
]
end
Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/ex_abi.
Confiiguration
The default keccak library is set to ex_keccak
but that can be overridden with a different module. The module should implement one function hash_256/1
.
config :ex_abi, keccak_module: MyCustomKeccak
If you're going to use a custom module, you should remove ex_keccak
from deps in mix.exs
.
Usage
Encoding
To encode a function call, pass the ABI spec and the data to pass in to ABI.encode/1
.
iex> ABI.encode("baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned])
<<162, 145, 173, 214, 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, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, ...>>
That transaction can then be sent via JSON-RPC Client ethereumex.
Decoding
Decode is generally the opposite of encoding, though we generally leave off the function signature from the start of the data. E.g. from above:
iex> ABI.decode("baz(uint,address)", "00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
[50, <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>]
Function selectors
Both ABI.encode/2
and ABI.decode/2
can accept a function selector as the first parameter. For example:
selector = %ABI.FunctionSelector{
function: "startInFlightExit",
input_names: [
"inFlightTx",
"inputTxs",
"inputUtxosPos",
"inputTxsInclusionProofs",
"inFlightTxWitnesses"
],
inputs_indexed: nil,
method_id: <<90, 82, 133, 20>>,
returns: [],
type: :function,
types: [
tuple: [
:bytes,
{:array, :bytes},
{:array, {:uint, 256}},
{:array, :bytes},
{:array, :bytes}
]
]
}
ABI.encode(selector, params)
To parse function selector from the abi json, use ABI.parse_specification/2
:
iex> [%{
...> "inputs" => [
...> %{"name" => "_numProposals", "type" => "uint8"}
...> ],
...> "payable" => false,
...> "stateMutability" => "nonpayable",
...> "type" => "constructor"
...> }]
...> |> ABI.parse_specification
[%ABI.FunctionSelector{function: nil, input_names: ["_numProposals"], inputs_indexed: nil, method_id: <<99, 53, 230, 34>>, returns: [], type: :constructor, types: [uint: 8]}]
Decoding output
By default, decode and encode functions try to decode/encode input (params that passed to the function). To decode/encode output pass :output
as the third parameter:
selector = %FunctionSelector{
function: "getVersion",
input_names: [],
inputs_indexed: nil,
method_id: <<13, 142, 110, 44>>,
returns: [:string],
type: :function,
types: []
}
data =
"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d312e302e342b6136396337363300000000000000000000000000000000000000"
|> Base.decode16!(case: :lower)
expected_result = ["1.0.4+a69c763"]
assert expected_result == ABI.decode(selector, data, :output)
assert data == ABI.encode(selector, expected_result, :output)
Support
Currently supports:
-
uint<M>
-
int<M>
-
address
-
uint
-
int
-
bool
-
fixed<M>x<N>
-
ufixed<M>x<N>
-
fixed
-
bytes<M>
-
<type>[M]
-
bytes
-
string
-
<type>[]
-
(T1,T2,...,Tn)