DappTools is a suite of Ethereum focused CLI tools following the Unix design philosophy, favoring composability, configurability and extensibility.
But so is hardhat, right?
Yeah, but hardhat is not perfect and the JavaScript based testing workflow is a nightmare and has following issues:
- So much boilerplate
- Context switching: The mental gymnastics of switching between two different languages to test the same functionality is like having a penguin as your co worker, you might think it’s exotic and adds value. But it doesn’t.
- It gets complicated real fast.
- Its painfully Slow for complex tests, you are still making RPC calls behind the scenes And no, adding typescript to the equation doesn’t help.
Simply put, working with JavaScript to test solidity is like trying to put out a fire by blowing at it, it works for a small fire, it gets increasingly difficult to put out as it gets bigger. And before you know it, you’re out of breath and in tears wishing there was a fire extinguisher all along. DappTools is the fire extinguisher. It can help you fix bugs in your smart contracts that can cost you millions of dollars.
If you want devs to write more secure code, give them better tools
Still not sold?
Eth2 Deposit Contract and Wrapped Ether are the top 2 accounts by ETH Balance. Together they hold around $60 billion in value or about 13% of all ETH in existence.

Do you want to guess what they have in common?

They were both built / tested using DappTools. It’s also used by: – The ETH2 Deposit Contract – MakerDAO – Fractional – Reflexer – Maple – Pickle – And countless others
If the top smart contracts use DappTools for their development workflow, you bet there are great reasons why DappTools is simply awesome.
So what’s this DappTools thing?
I’m glad you asked, DappTools is a suite of tools for writing, testing, fuzzing, and deploying Solidity smart contracts. Let’s explore with an example:
Get started by installing DappTools:
Install Nix if you haven’t already: curl -L https://nixos.org/nix/install | sh
Run this or login again to use Nix
. "$HOME/.nix-profile/etc/profile.d/nix.sh"
Then install dapptools: curl https://dapp.tools/install | sh
Create a directory and run dapp init
You should have a batteries included project in under 10s. That brings me to another feature of DappTools. It’s fast. Very very fast.
Project Structure
The project structure is fairly straight forward. The src folder contains your source file and a corresponding “.t.sol” file. That’s right – you can now test solidity files in solidity.
Building Files
You can build your solidity files using the “dapp build” command.
Let’s look at the following code:
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.6;
contract Dapptools {
function add(uint256 a, uint256 b) external pure returns (uint256){
return undfinedVariable;
}
}
DappTools will give you clear build errors during compilation.
Let’s fix our function and actually add the parameters.
contract Dapptools {
function add(uint256 a, uint256 b) external pure returns (uint256){
return a+b;
}
}
Let’s rebuild our contract.
This time our contract compiles without any error. The contract ABI is stored in the out directory. So far so good.
Testing Files
DappTools ship with a sample test file “Dapptools.t.sol”. Let’s understand how this file is constructed.
The setup function is cached under the hood, which significantly speeds up unit tests.
Let’s write our first test
contract DapptoolsTest is DSTest {
Dapptools dapptools;
function setUp() public {
dapptools = new Dapptools();
}
function testAddFunction() public {
assertTrue(dapptools.add(1, 2)==3);
}
}
You can run your test using the “dapp test” command:
Our test passed. You might also see the gas it took for our function to run. DappTools gives you the sweet sweet gas computation out of the box. You see what I mean by batteries included framework?
Let try failing our test
function testAddFunction() public {
assertTrue(dapptools.add(1, 2)==4);
}
And re-run our test
It’s not clear what exactly is going on. To get more info we can run dapp test --verbosity 3
Okay now we see where exactly the test is failing but the error message could be improved. Lets update our assertion statement.
function testAddFunction() public {
assertEq(dapptools.add(1, 2),4);
}
The assertEq function explicitly tests equality and provides appropriate error messages when the test fails. Let’s run the test.
Beautiful, isn’t it?
Fuzzing
Now you cannot be 100% sure if your function really works because 1 unit test passed. You could technically add more assertions:
function testAddFunction() public {
assertEq(dapptools.add(1, 2),3);
assertEq(dapptools.add(1, 3),4);
assertEq(dapptools.add(1, 4),5);
assertEq(dapptools.add(1, 5),6);
}
But there will always be inputs that you may not be able to cover manually. Behold – here comes fuzzing to the rescue.
Fuzzing might be new to you if you come from a traditional unit testing framework. Fuzzing is the idea of supplying type hinted parameters to test function and letting the framework supply the value during runtime.
Lets update our test include fuzzing:
function testAddFunction(uint256 x, uint256 y) public {
assertEq(dapptools.add(x, y),x+y);
}
Our test function can take arguments – DappTools will supply random values during runtime. Lets try it:
Our test failed: we were unable to add two very large numbers because these numbers are out of bounds for unit256 data type. This brings me to another important point – unit tests should always expose limitations of our code which is exactly what happened here. Lets change our test function to add 2 uint128 since adding two unit128 numbers will not cause this overflow.
function testAddFunction(uint128 x, uint128 y) public {
assertEq(dapptools.add(x, y),uint256(x)+unit256(y));
}
Re run the test:
Our test passes for 100 runs. Sweet. We can customize the number of runs with “dapp test –fuzz-runs 1000”
Symbolic Execution
But we are still not trying all possible inputs. How can we really be sure if our function works in all cases?
Symbolic execution is a unique way of testing code for correctness. Under the hood, instead of spamming your function with random inputs, it turns your code into a mathematical expression and uses a solver to find all possible paths the function could take and which are reachable.
If there are any reachable paths which violate your assertions, DappTools will tell you!
Alright, how do we turn our boring old fuzz test into a futuristic symbolically executed test that can tell us for sure that our function is correct?
We just change the function name so it starts with the word prove. That’s it! DappTools will behind the scenes convert the fuzz test to a symbolic test. Can it get any simpler than this?
function prooveAddFunction(uint128 x, uint128 y) public {
assertEq(dapptools.add(x, y),uint256(x)+uint256(y));
}
Lets run the test
Now, assuming there aren’t any big bugs in DappTools or the mathematical solver they use, we should have 100% certainty that our assertion holds over all possible inputs.
So why ever use a fuzz test?
If symbolic execution tries every input and is still fast, can we just throw away “test” and prefix every test with “prove”?
The amount of possible paths explodes as the code’s complexity increases which makes symbolic execution infeasible beyond a moderate level of complexity.
Use symbolic tests wherever you can for simpler functions and other isolated parts of your codebase.
Use fuzz tests to test larger parts of your codebase that are too complex to symbolically execute.
Here is a table that can guide you while making this decision.
Closing Thoughts
We only just scratched the surface of DappTools and what it has to offer. Learn more about DappTools on the project homepage: https://dapp.tools/ and let it supercharge your workflow.
Special Thanks
I’m thankful to @Transmissions11 for his work on DappTools and allowing me to use his work in this post. Go follow him on twitter where he is building a vibrant community around this exceptional tool.
Leave a Reply