A Step By Step Guide To Testing and Deploying Ethereum Smart Contracts in Go
“A toddler standing at the bottom of a tall staircase” by Mikito Tateisi on Unsplash
In this post, we are going to take a step by step look at how to test , deploy and interact with a simple Ethereum smart contract using the Go programming language.
There are a number of tutorials on the web detailing with how to deploy and interact with Ethereum smart contracts. However, these tutorials are either exclusively done in Javascript or some combination of Javascript and Go.
As a budding Gopher and Ethereum enthusiast, I wanted to build, test, deploy and interact with smart contracts on Ethereum using Go. However, I was not able to find a simple step by step guide to help me get started. I had to collect information from disparate sources and dig through the Go Ethereum source code to understand and put together all the required pieces of the puzzle. In this post, I will attempt to use what I have learned to put together an easy to follow step by step guide in the hopes of helping others get productive with Go and Ethereum quickly.
This post will be most useful to you if:
- You are familiar with the Go programming language and have a Go workspace already setup.
- You have general understanding of the Ethereum Blockchain and the associated Smart Contracts.
- You have had some exposure to the Solidity programming language used to write Smart Contracts.
If you are a complete novice in the Blockchain and Ethereum arena, I would recommend that you get started by:
- Watching this great video for a high level overview of what a Blockchain is.
- Working through (literally pull up your favorite text editor and follow along in your favorite programing language) this excellent blog post on building a block chain.
- Reading the Ethereum white paper.
Installing Go-Ethereum and Associated Developer Tools
First off, we need to install the Go bindings for the Ethereum protocol. Assuming you have Go installed and have GOPATH environment variable set appropriately, you can get bindings via:
$ go get -d github.com/ethereum/go-ethereum
Once we have the source code checked out, then we can go ahead and build geth (Geth is the main command line tool for running a full Go based implementation of an Ethereum node) along with all the developer tools:
$ cd $GOPATH/src/github.com/ethereum
$ go install ./...
Setup Project Structure
We will write and deploy a simple Inbox contract. To do so, lets begin by setting up the following directory and file structure:
# Navigate to your Go src directory. Mine looks like:
# $GOPATH/src/github.com/sabbas
$ cd $GOPATH/src/github.com/sabbas$ mkdir -p inbox/contracts$ touch contracts/inbox_test.go fetch.go update.go deploy.go
$ tree inbox/
inbox/
├── contracts
│ └── inbox_test.go
├── deploy.go
├── fetch.go
└── update.go
We create a new project folder called inbox. Within this folder, we create a package folder called contracts. This folder will contain the Solidity code for our inbox contract along with its associated Go bindings (which we will be generating soon). Within the contracts package folder, we also have an inbox_test.go file. This file will contain all our tests. There are three additional files named deploy.go, fetch.go and update.go. We will use these additional files to write code to deploy and interact with an Ethereum contract on a public network. These files can be empty for now.
Creating a Simple Ethereum Contract
We are now ready to write some Solidity code for our Inbox contract. Navigate to the inbox/contracts
folder and create a file with the name Inbox.sol.
$ tree inbox/
inbox/
└── contracts
└── Inbox.sol
Edit the inbox.sol file and add the following lines of Solidity code for our Inbox contract:
pragma solidity ^0.4.17;
contract Inbox {
string public message;
function Inbox(string initialMessage) public {
message = initialMessage;
}
function setMessage(string newMessage) public {
message = newMessage;
}
}
The Inbox contract is pretty straight forward. It has one public data variable called message which holds the contents of the most recent message string. The contract also defines a public setMessage method which updates the contents of the message data variable.
Using the Ethereum Contract from Go
Now that we our Inbox contract defined in Solidity, we would like to able to use this contract from within our Go application. To be more specific, we would like to have the ability to deploy this contract onto an Ethereum network and interact with it conveniently from within our Go application. Go-Ethereum makes this pretty simple by providing a code generator tool which can transform a Solidity contract file into a type-safe go package which we can import and use directly from within our Go application. This tool is called abigen and it was built and installed during our go-ethereum setup above. To use abigen, navigate to the inbox/contracts
folder and execute:
$ abigen -sol inbox.sol -pkg contracts -out inbox.go
$ tree inbox
inbox
└── contracts
├── Inbox.sol
└── inbox.go
We pass the name of the Solidity contract file we want to generate the Go package for to the sol command line argument. We also specify the Go package name and the output file name to the pkg and out command line arguments. Running abigen produces the inbox.go package file containing the Go bindings for the Inbox Solidity contract. Once we have these bindings, we are ready to begin testing the Inbox contract.
Testing the Ethereum Contract before Deploying to a Public Network
Before we deploy our contract to a public Ethereum network, we want to ensure that it is working as expected. In the case of the Inbox contract, we want to test that we can deploy the contract with an initial message, retrieve this initial message and update its value later on. Go-Ethereum provides a nice utility for a blockchain simulator which is extremely helpful with automated unit testing. In the code snippets that follow, we will see how to use the blockchain simulator utility to test the Inbox contract.
The TestDeployInbox method starts off by invoking crypto.GenerateKey
to generate a private key. This key is used to create a transaction signer function used for authorizing transactions in the simulated blockchain. The transaction signer function along with an address which we can use to make transactions from is generated via a call to bind.NewKeyedTransactor.
This address is then used to create a genesis block containing a single account with some initial balance (via calls to make(core.GenesisAlloc
and core.GenesisAccount
). This genesis block is then used to seed the simulated blockchain. Finally, we “mine” the next block by explicitly committing all pending transactions and checking if the Inbox contract was deployed at a valid address.
We can navigate to inbox\contracts
and execute go test
to ensure that our deployment test passes
$ go test -v
=== RUN TestDeployInbox
--- PASS: TestDeployInbox (0.01s)
PASS
ok github.com/sabbas/inbox/contracts 0.042s
Next, we want to test that when we deploy the Inbox contract, it gets deployed with the correct initial message.
Similar to the TestDeployInbox function, the TestGetMessage function kicks off by setting up our simulated blockchain (the simulated block chain function can be refactored out into a separate re-usable function. I have avoided that here just to help with readability a bit). It then invokes the DeployInbox function created as part of the auto generated Go bindings for our Inbox Solidity contract. Note that the DeployInbox function returns a pointer to the instance of the deployed Inbox contract as the third return value. We can use this pointer to interact with our deployed Inbox contract(super cool!). And this is exactly what the test does to query and check the initial message stored in the instance of the inbox contract we just deployed.
We can navigate to inbox\contracts
and execute go test
to ensure that our tests pass
$ go test -v
=== RUN TestDeployInbox
--- PASS: TestDeployInbox (0.01s)
=== RUN TestGetMessage
--- PASS: TestGetMessage (0.00s)
PASS
ok github.com/sabbas/inbox/contracts 0.045s
Finally, we want to test that we can update the message data property in the deployed Inbox contract to a new value.
The TestSetMessage function begins with the same boiler plate code we saw earlier to setup a simulated blockchain and deploy the Inbox contract. As we saw before , a successful invocation of the DeployInbox function call returns a pointer to the instance of the deployed Inbox contract as the third return value. In the TestSetMessage function, we use this contract pointer to update the message data property of the Inbox contract by calling the SetMessage
function. Since the SetMessage
function modifies the Inbox contract, it actually generates a new transaction. As a result, we pass in a pointer to a TrasactOpts
struct populated with the transaction authorization data. Since we dont need to send any funds along with the SetMessage
invocation, we set the Value
property of the TransactOpts
struct to nil.
We can navigate to inbox\contracts
and run go test
one more time to ensure all is good.
$ go test -v
=== RUN TestDeployInbox
--- PASS: TestDeployInbox (0.01s)
=== RUN TestGetMessage
--- PASS: TestGetMessage (0.00s)
=== RUN TestSetMessage
--- PASS: TestSetMessage (0.01s)
PASS
ok github.com/sabbas/inbox/contracts 0.051s
Once our local tests are passing, our Inbox contract is ready for prime time. In the next section we will take a look at how we can take our Inbox contract and deploy it on a public Ethereum network.
We will be deploying the Inbox contract on the Rinkeby Test Ethereum public network. Deploying and interacting with Ethereum contracts on the Main Ethereum network costs actual money and not necessary when we are just learning or playing around.
To deploy and interact with an Ethereum contract on a public network such as Rinkeby, a couple of things need to be in place:
- We need to have an account with some funds (ether) on the network
- We need to be able to connect to and interact with an Ethereum Node.
In the sections that follow, we will look at each of these in turn.
Creating an Account using Metamask
We can create and manage accounts directly from the geth cli we built earlier. However, when starting out, I found it much simpler to use the in browser Metamask Chrome extension. It is quite simple to install Metamask and create an account. I will leave this as an exercise for the reader.
Once we are up and running with Metamask and have an account created, we want to make sure that we point Metamask to the Rinkeby Test network
Funding the Account
The Rinkeby network has a test faucet (https://faucet.rinkeby.io/) running which we can use to request Ether. However, to request ether from this test faucet, we first need to make a post containing just our Ethereum account address(this is account we want to transfer funds into. We can copy this address by clicking on the ellipses right next to the account name on the Metamask extension) on a social networking site and provide a link to the post to the faucet. It’s pretty straight forward.
It will take a few seconds and we should see the funds show up in our account in the Metamask extension.
Connecting to an Ethereum Node
We can use geth to spin up and manage our own Ethereum node on the Rinkeby test network. This is both resource and time intensive and not always a smooth process. A better alternative is to connect to a running Ethereum node hosted by a third party provider. One such provider is Infura. We can sign-up for a free account with Infura. Once we sign-up, Infura will send us URLS to connect to nodes running on different Ethereum networks. The URL for the Rinkeby test network should look something like https://rinkeby.infura.io/fYE8qC0WiMx4ZAX4Voff.
Generating Encrypted JSON key for Our Account
In order to deploy and interact contracts on a public Ethereum network such as Rinkeby via Go-Ethereum, we need an encrypted json key for the account we created through Metamask above. This is the account we want to be charged for deploying and interacting with the Inbox contract.
We can generate this JSON key by exporting the private key (click on the ellipses right next to the account name in metamask to get to the export private key option) for our account in Metamask to a file and then importing it via geth. Make sure you delete the private key file after you have done the import.
geth account import path/to/private/key/file
The above command will generate an encrypted JSON key file in ~/Library/Ethereum/keystore on a Mac and ~/.ethereum. Take note of this file. We will use its contents in the next section as we deploy and interact with the Inbox contract on the Rinkeby test network.
The Final Frontier
We have finally made it to the point where we are ready to deploy the Inbox contract to the Rinkeby network. Lets first look at the code that goes into the deploy.go file we created earlier and then we will walk through it it detail.
We use the ethclient.Dial
method with Infura url we generated earlier to connect to an Ethereum node on the Rinkeby test network. We then use the bind.NewTransactor
utility method to create an authorized transactor from the contents of the key file (you can use geth account list
command to retrieve the location of your JSON key file on your system) and its associated password we generated above via geth account import
. From there on, the code to deploy the contract is exactly the same as we have seen when deploying the Inbox contract using the simulated blockchain. Finally, if all goes well, we print the the address at which the Inbox contract is deployed on the network.
$ go run deploy.go
Contract pending deploy: 0x491c7fd67ac1f0afeceae79447cd98d2a0e6a9ff
It will take a few minutes for this contract to be mined and be part of the blockchain. We can check the status via etherscan by navigating to: https://rinkeby.etherscan.io/address/[contract address]. In my case this would be https://rinkeby.etherscan.io/address/0x491c7fd67ac1f0afeceae79447cd98d2a0e6a9ff
We can use the address of the deployed Inbox contract to begin interacting with it once it has been mined and included in the blockchain. For example, we can put the code below in the fetch.go we created above to retrieve the initial message.
As seen before, we use the ethclient.Dial
method with Infura url we generated earlier to connect to an Ethereum node on the Rinkeby test network. We then use the NewInbox
method generated as part of the Go bindings for the Solidity contract to attach an instance of the Inbox contract to an Inbox contract deployed at a specific address. Finally we access the Message
property on the contract which should print Hello World.
$ go run interact.go
Hello World <nil>
We can also update the Message
property on the deployed Inbox contract. To do so, place the code below in the update.go file created earlier.
Recall that the SetMessage
function modifies the Inbox contract and actually generates a new transaction. As a result, we pass in a pointer to a TrasactOpts
struct populated with the transaction authorization data. Again, It will take a few minutes for this transaction to be mined and be part of the blockchain. Once it is mined and included in the blockchain, we can follow the earlier example of retrieving the Message
property from the deployed contract to see the updated value.
There is a lot we can do with Go Ethereum but just getting started could be a bit over whelming. Hopefully this post has given you a starting point from where you could explore further.