DEV Community

Liam Zebedee
Liam Zebedee

Posted on • Updated on

A Primer on Seth, Solidity's Swiss Army Knife

Tired of writing new Web3 in a JS script just to check a balance? Annoyed about the friction of converting things to BigNumber, loading Contract.json, dealing with promises just to do something simple? Well let me introduce you to seth. Using seth, you can easily interact with smart contracts with the speed and dexterity of the command line. It's part of a suite of tools developed by the MakerDAO team, of DAI acclaim. In this post, you're going to go from 0 to hero.

Introduction to seth

The Seth reference guide on Github is great, but it misses some lessons that won't be obvious if you're not a power user of the CLI. I'm going to explain some really simple techniques from the perspective of these use cases:

  • deploying a Truffle contract
  • saving a contract's deployment address
  • calling methods and passing arguments
  • transacting from different accounts
  • converting units (numbers, eth)

Installing seth

seth is part of the dapptools suite, which is a super-minimal package of software (like 5kB). Taken from their Github instructions:

curl https://dapp.tools/install | sh
Enter fullscreen mode Exit fullscreen mode

Deploying a Truffle contract

Seth is based on the unix philosophy, which puts files at its core. You've probably gotten used to some monolithic framework like Truffle, which puts you in the backseat while it takes care of business. Running truffle compile, it will generate contract artifacts in the form of build/contracts/ContractName.json. And running truffle migrate, it will automatically take the bytecode of these contracts and deploy them.

It might seem scary, but the details aren't actually that complex to do ourselves!


But wait, what's the difference between bytecode and deployedBytecode? Well, when you deploy a contract in Ethereum, you're doing two things:

1) you're creating a contract, which stores its runtime code on the chain
2) you're calling the constructor

Simply put - the deployedBytecode is just the runtime, whereas the bytecode includes the code of the constructor.

Let's see how easy it is to deploy a contract using seth. To begin with, we're going to install jq, a CLI tool which can parse JSON. You can find installation instructions here.

# Get the bytecode from the Truffle artifact
export CODE=$(cat build/contracts/ERC20.json | jq -r .bytecode)
Enter fullscreen mode Exit fullscreen mode

Setting up seth's environment

Run the below to setup the environment variables seth needs to know:

# Connect to the local node
export ETH_RPC_URL=http://127.0.0.1:8545

# Disable below if not on Truffle
export ETH_GAS=4712388

# Use the unlocked RPC accounts
export ETH_RPC_ACCOUNTS=yes

# Get the 1st account and use it
export ETH_FROM=$(seth accounts | head -n1 | awk '{ print $1 }')
Enter fullscreen mode Exit fullscreen mode

Deploying the contract

# We can write it simply
seth send --create $CODE

# Passing variables to the constructor
# In this example, the constructor is ERC20Detailed
# constructor(string name, string symbol, uint decimals)
seth send --create $CODE TokenName TOK 18
Enter fullscreen mode Exit fullscreen mode

Running the above, you will get an output like so:

seth-send: Published transaction with 3227 bytes of calldata.
seth-send: 0xb330d69093fd6b19d5afecffebf6734f77ef8d7cf47dabb87ad840a7d4b8e57f
seth-send: Waiting for transaction receipt....
seth-send: Transaction included in block 36.
0x243e72b69141f6af525a9a5fd939668ee9f2b354
Enter fullscreen mode Exit fullscreen mode

The last line is the contract address.

Saving a contract's deployment address

I'm not that skilled in Bash - but I do know one thing: I'm not reinventing the wheel when I want to save the deployment address. Using Sourcegraph (which is grep+git on steroids), I just searched for seth send across all of dapphub's repos and was able to grab the line below.

ERC20_ADDR=$(seth send --create $CODE --status 2>/dev/null)

# or even terser
ERC20_ADDR=$(seth send --create $(cat build/contracts/ERC20.json | jq -r .bytecode) --status 2>/dev/null)
Enter fullscreen mode Exit fullscreen mode

2>/dev/null basically redirects STDERR (see Standard streams) to null. All of the misc logging by seth is ignored and we are left with the contract address.

If the deployment fails, there's no .catch or burdensome Promises to deal with. The $ERC20_ADDR variable will be empty. If we are automating this in a script, it's wise to make sure it exits early in case of failure. Check this out:

#!/bin/bash
set -ex

# Connect to the local node
export ETH_RPC_URL=http://127.0.0.1:8545

# Disable below if not on Truffle
export ETH_GAS=4712388

# Use the unlocked RPC accounts
export ETH_RPC_ACCOUNTS=yes

# Get the 1st account and use it
export ETH_FROM=$(seth accounts | head -n1 | awk '{ print $1 }')

ERC20_ADDR=$(seth send --create $(cat build/contracts/ERC20.json | jq -r .bytecode) --status 2>/dev/null)

echo -n $ERC20_ADDR > ERC20.deployment
Enter fullscreen mode Exit fullscreen mode

Boom! We've just saved the address to a file that we can load later!

Calling methods and passing arguments

This is probably the best part of Seth. Calling methods on contracts couldn't be easier with this tool, because you don't have to load the JSON ABI" to interact with contracts.

For example, say we're testing this token contract and we want to mint ourselves some tokens. This is how simple it is:

# Get the contracts address
export ERC20_ADDR=$(cat ERC20.deployment)

seth send $ERC20_ADDR "mint(uint256)" 1
Enter fullscreen mode Exit fullscreen mode

Likewise, if a method has a return value, you just have to show seth how to decode it. What we're passing is the method signature, but in a much simpler form than the full method - method(<types>)(<return-types>).

# Get the balance of our address
seth send $ERC20_ADDR "balanceOf(address)(uint)" $ETH_FROM
Enter fullscreen mode Exit fullscreen mode

Transacting from different accounts

Oftentimes, we're using msg.sender in our contract logic. When it comes time to testing, then we probably want to use different accounts for interacting. Seth makes this super easy using the ETH_FROM variable.

We've already been using a global ETH_FROM to begin with, but it's pretty simple to use per-call too.

export BUYER=$(seth accounts | sed -n 2p | awk '{ print $1 }')

seth send --from $BUYER --value 402343423423423 $EXCHANGE "ethToTokenSwapInput(uint256,uint256)" 3 1000000000000
Enter fullscreen mode Exit fullscreen mode

The above example calls ethToTokenSwapInput on a Uniswap exchange, from a specific account I've allocated called the $BUYER. It also makes use of --value to send some ether with the transaction.

Converting units (numbers, eth)

This brings me to my last important teaching on Seth - converting units. In Ethereum, we deal with uint256 as our default, and ether and other tokens are usually designed to be shown with 18 decimal places. Seth makes it easy to convert between these different formats.

seth --to-dec 0000000000000cb803 eth
# 833539

seth --from-wei <decimal-amount>
# 0.123

seth --to-wei <decimal-value>
# 123

seth --to-ascii 67646179206d617465
# ... ;)
Enter fullscreen mode Exit fullscreen mode

Conclusion

I think seth is a fantastic investment as a developer in Ethereum's ecocsystem. Given the inherent complexity in a whole new platform of tooling, seth is a pragmatic approach to inspecting, experimenting, and iterating on blockchain software.

β˜•οΈ If you liked this guide, consider following me on Twitter.

😎 I enjoy learning and teaching, and do it regularly. Here's some links you might find interesting too:

Top comments (0)