Skip to content

seth: add function selector and calldata encoding/decoding helpers #757

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 12, 2021
5 changes: 5 additions & 0 deletions src/seth/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- `seth --to-wei` now uses eth instead of wei as the default unit for conversions
- `seth 4byte` command returns the response from querying [4byte.directory](https://www.4byte.directory/) for a given function signature
- `seth 4byte-decode` command queries 4byte.directory for matching function signatures, uses one to decode the calldata, and prints the decoded calldata
- `seth 4byte-event` command returns the response from querying 4byte.directory for a given event topic
- `seth abi-encode` command returns the ABI encoded values without the function signature
- `seth index` command returns the slot number for the specified mapping type and input data

## [0.11.0] - 2021-09-08

Expand Down
62 changes: 61 additions & 1 deletion src/seth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ hardware wallets—even if you use a remote RPC node like Infura's.
- [`seth --to-int256`]
- [`seth --to-uint256`]
- [`seth --to-wei`]
- [`seth 4byte`]
- [`seth 4byte-decode`]
- [`seth 4byte-event`]
- [`seth abi-encode`]
- [`seth age`]
- [`seth balance`]
- [`seth basefee`]
Expand All @@ -85,7 +89,7 @@ hardware wallets—even if you use a remote RPC node like Infura's.
- [`seth etherscan-source`]
- [`seth events`]
- [`seth gas-price`]
- [`seth help`]
- [`seth index`]
- [`seth keccak`]
- [`seth logs`]
- [`seth lookup-address`]
Expand Down Expand Up @@ -452,6 +456,46 @@ Convert an ETH amount into wei.

The unit may be `wei`, `gwei`, `eth`, or `ether`.

### `seth 4byte`

Prints the response from querying [4byte.directory](https://www.4byte.directory/) for a given function signature

seth 4byte <calldata> [<options>]

Any calldata appended after the function signature will be stripped before querying 4byte.directory.

By default, just the signatures will be printed, but the `-v` flag can be used to print the full JSON response.

### `seth 4byte-decode`

Queries [4byte.directory](https://www.4byte.directory/) for matching function signatures, uses one to decode the calldata, and prints the decoded calldata.

seth 4byte-decode <calldata> [<options>]

By default, the user will be prompted to select a function signature to use for decoding the calldata.

The `--id` flag can be passed to bypass interactive mode.
Use `--id earliest` or `--id latest` to use the oldest and newest functions in the 4byte.directory database, respectively.
Use `--id <number>` to select a function signature by it's ID in the 4byte.directory database.

### `seth 4byte-event`

Prints the response from querying [4byte.directory](https://www.4byte.directory/) for a given event topic

seth 4byte-event <topic> [<options>]

By default, just the signatures will be printed, but the `-v` flag can be used to print the full JSON response.

### `seth abi-encode`

Prints the ABI encoded values without the function signature

seth abi-encode <sig> [<args>]

ABI encode values based on a provided function signature, slice off the leading the function signature,
and print the result. It does not matter what the name of the function is, as only the types and values
affect the output.

### `seth age`

Show the timestamp of a block (the latest block by default).
Expand Down Expand Up @@ -631,6 +675,17 @@ See also [`seth logs`] which does not decode events.

Reads the current gas price at target chain.

### `seth index`

Prints the slot number for the specified mapping type and input data

seth index <fromtype> <totype> <fromvalue> <slot> [<lang>]

`lang` will default to Solidity when not specified.
To compute the slot for Vyper instead, specify `v`, `vy`, or `vyper`.

Result is not guaranteed to be accurate for all Vyper versions since the Vyper storage layout is not yet stable.

### `seth keccak`

Print the Keccak-256 hash of an arbitrary piece of data.
Expand Down Expand Up @@ -825,6 +880,10 @@ Show all fields unless `<field>` is given.
[`seth --calldata-decode`]: #seth---calldata-decode
[`seth block-number`]: #seth-block-number
[`seth gas-price`]: #seth-gas-price
[`seth 4byte`]: #seth-4byte
[`seth 4byte-decode`]: #seth-4byte-decode
[`seth 4byte-event`]: #seth-4byte-event
[`seth abi-encode`]: #seth-abi-encode
[`seth abi`]: #seth-abi
[`seth age`]: #seth-age
[`seth balance`]: #seth-balance
Expand All @@ -841,6 +900,7 @@ Show all fields unless `<field>` is given.
[`seth etherscan-source`]: #seth-etherscan-source
[`seth events`]: #seth-events
[`seth help`]: #seth-help
[`seth index`]: #seth-index
[`seth keccak`]: #seth-keccak
[`seth logs`]: #seth-logs
[`seth lookup-address`]: #seth-lookup-address
Expand Down
7 changes: 7 additions & 0 deletions src/seth/libexec/seth/seth
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
### --source=<file> path to combined.json source
### --state=<dir> directory to store hevm state in
###
### Options for seth-4byte-decode(1):
###
### --id 'earliest' or 'latest' or the ID number of a particular signature
###
### Other options:
###
Expand Down Expand Up @@ -106,6 +109,9 @@ no-src don't fetch contract source
Options for seth-bundle-source(1):
D,dir=directory directory to store output

Options for seth-4byte-decode(1):
id=string|number 'earliest' or 'latest' or the ID number of a particular signature

Other options:
async do not wait for transaction receipt
follow go into a loop watching for new logs
Expand Down Expand Up @@ -185,6 +191,7 @@ while [[ $1 ]]; do
--async) export SETH_ASYNC=yes;;
--follow) export SETH_FOLLOW=yes;;
-j|--json) export SETH_JSON=yes;;
--id) shift; export SETH_4BYTE_ID=$1;;

*) printf "${0##*/}: internal error: %q\\n" "$1"; exit 1
esac; shift
Expand Down
17 changes: 17 additions & 0 deletions src/seth/libexec/seth/seth-4byte
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
### seth-4byte -- prints the response from querying 4byte.directory for a given function signature
### Usage: seth 4byte <calldata> [<options>]
###
### Takes a 4 byte function signature and prints matching functions from 4byte.directory. This will also strip
### any appended calldata after the first four bytes
###
### When the --verbose option is used, the full JSON response is printed, otherwise just text signatures are printed

set -e
[[ $# -eq 1 ]] || seth --fail-usage "$0"

if [[ $SETH_VERBOSE ]]; then
curl -s "https://www.4byte.directory/api/v1/signatures/?hex_signature=${1:0:10}" | jq .
else
curl -s "https://www.4byte.directory/api/v1/signatures/?hex_signature=${1:0:10}" | jq '.results[] | .text_signature'
fi
53 changes: 53 additions & 0 deletions src/seth/libexec/seth/seth-4byte-decode
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
### seth-4byte-decode -- querys 4byte.directory to find a matching signature, then decodes the calldata with it
### Usage: seth 4byte-decode <calldata> [<options>]
###
### Querys 4byte.directory to find matching signatures and prompts the user to to select one. The selected
### signature will be used to decode the calldata.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be nice to have an --optimistic (I feel lucky) flag to this if we wanna build some larger pipeline where we can't be interactive

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking that too, just wasn't sure of how we'd want to do it. You can sometimes deduce which sig is the correct one from the calldata, but that can get hairy for complex sigs, and you can't always deduce it

For now I learn towards a simpler approach of having --optimistic use the oldest sig 4byte.directory has. Anecdotally this seems to often be the desired one (or at least it was the for the ERC20 methods I used for testing)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdyt about a --id flag that accepts either an id number or earliest/latest

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what would be the use case for specifying an ID? If you know the ID then presumably you also know the function signature, meaning you could use seth --calldata-decode instead of seth 4byte-decode

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meh mainly for the ability to specify earliest/latest

and id is shorter than using the full sig 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems reasonable enough to me, being able to toggle earliest/latest is nice

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love some help with my --id flag here. In f45ee07 I pushed an attempt that works if I set the env var manually but not with the --id flag


set -e
[[ $# -eq 1 ]] || seth --fail-usage "$0"

if [[ -z $SETH_4BYTE_ID ]]; then
# Function signature ID not specified, prompt user to choose a signature
sigs=$(curl -s "https://www.4byte.directory/api/v1/signatures/?hex_signature=${1:0:10}" | jq '.results[] | .text_signature' )
PS3="Select a function signature by number: "
select sig in $sigs; do
break
done
echo "" # add a line break for easier reading
else
# Function signature ID was specified, so parse results without user input
results=$(curl -s "https://www.4byte.directory/api/v1/signatures/?hex_signature=${1:0:10}" | jq .results[] )
sigs=$(echo $results | jq '.text_signature')

# sigs is a string so split it into an array
sigarray=()
for sig in $sigs; do sigarray+=($sig); done
length=${#sigarray[@]}

# parse the provided ID
if [[ $SETH_4BYTE_ID = earliest ]]; then
# first one added to 4byte is the last in the array
sig=${sigarray[$length-1]}
elif [[ $SETH_4BYTE_ID = latest ]]; then
# last one added to 4byte is the first in the array
sig=${sigarray[0]}
else
# specific ID number provided (if using this option, you may be better off with `seth --calldata-decode`)
query=". | select(.id==$SETH_4BYTE_ID) | .text_signature"
sig=$(echo $results | jq "$query")
fi
fi

# Exit if no sig found
if [[ -z $sig ]]; then
echo >&2 "seth 4byte-decode: no signature found"
exit 1
elif [[ $SETH_VERBOSE ]]; then
echo "signature: $sig"
fi

# Remove leading and trailing quotes from JSON, then decode
sig="${sig//\"}"
seth --calldata-decode $sig $1
14 changes: 14 additions & 0 deletions src/seth/libexec/seth/seth-4byte-event
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
### seth-4byte-event -- prints the response from querying 4byte.directory for a given function topic
### Usage: seth 4byte-event <topic> [<options>]
###
### Takes a 32 byte topic and prints the response from querying 4byte.directory for that topic

set -e
[[ $# -eq 1 ]] || seth --fail-usage "$0"

if [[ $SETH_VERBOSE ]]; then
curl -s "https://www.4byte.directory/api/v1/event-signatures/?hex_signature=$1" | jq .
else
curl -s "https://www.4byte.directory/api/v1/event-signatures/?hex_signature=$1" | jq '.results[] | .text_signature'
fi
12 changes: 12 additions & 0 deletions src/seth/libexec/seth/seth-abi-encode
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
### seth-abi-encode -- ABI encode values, and prints the encoded values without the function signature
### Usage: seth abi-encode <sig> [<args>]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to accept a tuple of types and then automatically add some dummy function signature just as an internal implementation detail?

Copy link
Contributor Author

@mds1 mds1 Sep 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could, but IMO we should keep the current sig input format since it's more familiar (i.e. used in other seth methods) and as a result more composable with other methods (i.e. outputs from the 4byte methods).

Also noticed a docs issue with this command, so fixed in d523c2e

###
### ABI encode values based on a provided function signature, slice off the leading the function signature,
### and print the result. It does not matter what the name of the function is, as only the types and values
### affect the output.

set -e

x=$(seth calldata $@); # generate full calldata based on function signature
echo "0x${x:10}" # slice off the function signature and only keep the encoded values
24 changes: 24 additions & 0 deletions src/seth/libexec/seth/seth-index
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
### seth-index -- Prints the slot number for the specified mapping type and input data
### Usage: seth index <fromtype> <totype> <fromvalue> <slot> [<lang>]
###
### Prints the slot number for the specified mapping type and input data. For example,
### the balances mapping in DAI is slot 2, so to find the slot where the balance
### is stored for <account>, use
### seth-index address uint256 <account> 2
Comment on lines +2 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we document the allowed values for <lang> here pls

Copy link
Contributor Author

@mds1 mds1 Sep 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d523c2e

###
### `lang` will default to Solidity when not specified. To use Vyper, enter `v`, `vy`,
### or `vyper`. The above example cna be repeated for a Vyper token with
### seth-index address uint256 <account> 2 vyper

set -e
[[ $# -eq 4 ]] || [[ $# -eq 5 ]] || seth --fail-usage "$0"

Copy link
Contributor

@d-xo d-xo Sep 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we print usage info we don't have the expected number of params, right now if I run seth index it shows a fairly inscrutable error from hevm:

> seth index
hevm: wrong number of arguments:0: []
CallStack (from HasCallStack):
  error, called at hevm-cli/hevm-cli.hs:984:11 in main:Main

Copy link
Contributor Author

@mds1 mds1 Sep 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d523c2e

if [[ $5 = 'vyper' || $5 = 'vy' || $5 = 'v' ]]; then
# note: not guaranteed to be accurate for all Vyper versions since storage layout is not yet stable
# more info: https://twitter.com/big_tech_sux/status/1420159854170152963
echo >&2 "${0##*/}: warning: not guaranteed to be accurate for all Vyper versions since storage layout is not yet stable"
echo $(seth keccak $(seth abi-encode "x($2,$1)" $4 $3));
else
echo $(seth keccak $(seth abi-encode "x($1,$2)" $3 $4));
fi
19 changes: 17 additions & 2 deletions src/seth/libexec/seth/seth-sig
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,20 @@
### Returns: 0x2e1a7d4d
set -e
[[ $# = 1 ]] || seth --fail-usage "$0"
method=$(seth keccak "$1")
echo "${method:0:10}"

# Get ABI from input
abi=$(seth --abi-function-json "$1")
inputs=$(echo $abi | jq '.inputs[] | .type')

# Generate dummy args
args=()
for input in $inputs; do
type="${input//\"}" # remove leading and trailing quotes from the JSON
end=${type: -2} # get the last two characters to check for array types
[ $end = "[]" ] && arg="[]" || arg=0 # use [] for array types and 0 otherwise
args+=(--arg "$arg")
done

# Use dummy args generate calldata and only keep the function selector
calldata=$(hevm abiencode --abi "$abi" "${args[@]}")
echo "${calldata:0:10}"