Use dapp.tools for Ethereum Contract Development
Introduction
When developing and testing Solidity contract, I believe most people are using Remix, an online IDE (integrated development environment) everyone can use through a browser. Remix comes with editor, terminal, compiler and runtime environment, on either a built-in Ethereum simulator or an external Ethereum network.
Because of a new project I got a chance to use a toolset from dapp.tools, and find almost a command-line equivalent to Remix running in my localhost. I find it very intuitive. Also dapp.tools come with some packages that I can easily incorporate into my contract.
Here in this article I first show some basic functions and operation of dapp.tools, I will use a simple contract code and see how to create unit test on the contract itself, ensuring the logic defined in the contract is exactly what is expected in the test case. After that I deploy the contract and interact it with an Ethereum simulation, testnet, which is also from dapp.tools. In the next article I will create another contract and see how to incorporate pre-built token package into my project.
dapp.tools Overview and Installation
The best introduction for dapp.tools is in their page.
As described by their site, dapp.tools is a set of “command-line-friendly tools for blockchain development”. As a quick overview, dapp.tools come with several handy tools for Ethereum contract development. Again you can see the best description on those tools and the description in their website.
In my demonstration I will use dapp to perform unit test of my contract code and deploy it in the testnet provided by dapp.tools. After that i will use seth to interact with my deployed contract.
The installation is quite straight-forward. As I am using Mac I simply follow their instruction.
curl https://dapp.tools/install | sh
Initialize a Workspace
The first thing of our demonstration is to initialize a workspace for dapp, using dapp init
.
mkdir simplestorage
cd simplestoragedapp init
From the output we learn that, after dapp init
, we have
- a directory structure created, with directory src keeping our contract code and test code, and directory lib for the packages.
- inside src, there are two files created: Simplestorage.sol is for our contract code, and Simplestorage.t.sol is for the test code. They are both prepopulated with basic contract structure.
- inside lib, a package
ds-test
is installed. This is the package for testing our contract. - A sanity test is performed.
- A git repository is created for this workspace.
Here is the directory structure after dapp init
.
Contract Code and Test Code
Now let’s prepare the contract code and the test code.
Here I am using the famous Simple Storage contract code. This contract keeps a stored value when it is first deployed. And we can use the two functions to interact with the contract: get() for the stored value, and set(x) to update the value.
The workspace initialization creates an empty contract using the workspace directory name: src/Simplestorage.sol. We simply copy this contract code in this file.
pragma solidity ^0.5.6;contract Simplestorage {
uint storedData;function set(uint x) public {
storedData = x;
}function get() view public returns (uint retVal) {
return storedData;
}}
The workspace initialization also creates a test contract of the name, with extension t.sol: src/Simplestorage.t.sol. We simply copy the test code in this file. While it comes with two sample tests.
pragma solidity ^0.5.6;import "ds-test/test.sol";import "./Simplestorage.sol";contract SimplestorageTest is DSTest {
Simplestorage simplestorage;function setUp() public {
simplestorage = new Simplestorage();
}function testGetInitialValue() public {
assertTrue(simplestorage.get() == 0);
}function testSetValue() public {
uint x = 300;
simplestorage.set(x);
assertTrue(simplestorage.get() == 300);
}}
Let’s take a look on this test contract. Simplestorage.t.sol is also a Solidity contract. It first imports the test.sol inds-test
package (installed during workspace initialization) and the Simplestorage.sol contract.
We are defining our test cases as functions. Before that we need a setUp() function to deploy this contract.
Functions for test cases are named with prefix test. Each of these functions will be performed as test item. Test is passed only when condition in assertTrue(condition) is true. Function testGetInitialValue() is to get the stored value with get() function after contract deployment. Function testSetValue() is to check whether set(x) can be used for a new value and check the value with get() function.
You may ask where the test is running. Dapp test uses a Hevm, their own EVM implementation good for testing and debugging. Therefore we do not need to run any testnet for the contract testing.
Test Contract Code
As said, we do not need a testnet for contract testing. Here we use command dapp test
for unit test.
dapp test
Dapp will search those contract with t.sol extension, and execute setUp() and all functions with prefix test: in our test code there are two functions: testGetInitialValue() and testSetValue().
Here are the result.
From the result we see dapp test first compile the two contracts. After that, the two test cases inside Simplestorage.t.sol are run and successfully passed.
Meanwhile the compiled result is stored in directory out. We can find all the artifacts after compilation in this directory.
Run Testnet
Now our contract is ready for deployment. We will first run the testnet, an Ethereum network provided by dapp.
Use a different terminal to run the testnet
dapp testnet
Some observation on running dapp testnet
- All the testnet setup is in my home directory, under ~/.dapp/testnet/8545.
- If we take a look on this directory, we see the geth component and other setup.
- Inside keystore directory we will have the coinbase account setup. The address is also shown in the testnet screen (here it is 0x62a8…c362, and this address is different every time when we run
dapp testnet
.) - The testnet is in fact a running geth with genesis block and coinbase key kept in this directory.
Here is the directory structure of the testnet.
Deploy Contract
With a terminal with dapp testnet running, we are ready to deploy the contract.
We first specify the coinbase address we are using for contract deployment and tell where to get the private key. It is done through setting environment variables ETH_FROM and ETH_KEYSTORE.
export ETH_FROM=0x62a872619c4072f73724d0fb5905354fc6ecc362
export ETH_KEYSTORE=~/.dapp/testnet/8545/keystore
We use dapp create
to deploy the contract. When prompted for passphrase, it is empty for testnet.
dapp create Simplestorage
As we see a transaction is executed. This is the contract deployment.
Note we will get back an address (0x9fa2…7fde). It is the contract ID (or contract address) after deployment. For easy access we will define another environment variable SIMSTO to keep this value.
export SIMSTO=0x9fa245ee3daa5314fdfe33124b9215029c157fde
Now the contract is deployed, and we are ready to interact with it.
Interacting with Contract
To interact with contract we are using seth
. Seth is the Ethereum client made in command line. We can perform many tasks with seth
. Here we just use it for executing functions in the deployed contract.
First we will use seth call
for function get() for those functions without updating the blockchain. What we need is the deployed contract ID (the environment variables we set in previous step).
seth call $SIMSTO “get()”
As expected, the value is initially zero.
We will set a new value. For functions requiring arguments, we place the argument list after the function. Also seth
requires hex input and we will use seth --to-uint256
to convert the number into hex and fitting to 256-bit.
We will use seth send
for functions updating blockchain.
seth send $SIMSTO “set(uint)” $(seth --to-uint256 100)
We see a new transaction is created, and the transaction is included in a new block.
Finally we check the stored value again with get().
seth call $SIMSTO “get()”
The result is 0x64 (100), as expected.
Clean-Up
And after we complete our demonstration or testing, we can stop the dapp testnet terminal by control-c. The directory for testnet, i.e. ~/.dapp/testnet/8545 will be emptied. All information in testnet, including the account and the block created during the test, are gone. This helps a lot as we do not need to delete files in various directories.
Side Node: Deploy Contract in Public Ethereum
The default setup for seth
is going to local RPC, which is the local testnet.
If we wish to deploy our contract in public Ethereum networks, what we need is to let seth
knows the target network through environment variables.
The variables we need are
export SETH_CHAIN=<the chain you are deploying, e.g. mainnet, rinkeby, etc>export ETH_KEYSTORE=<your keystore in the Ethereum network>export ETH_FROM=<your coinbase address in this Ethereum network>
Then all commands from seth
will go to the Ethereum network you specify. Make sure you have ethers on the network in your account for gas.
Closing
We just show how to perform unit test for an Ethereum contract using dapp tools. We also deploy a testnet and use seth to execute functions for a deployed contract.
This only shows the very basic functionality of dapp tools. In the next article I will create a bigger contract, and see how to incorporate ds-token
package to make token useable in my contract.