Skip to main content
Create your own

Debugging Solidity with Hardhat Console.log

Welcome to the final lesson in our module on smart contract testing. Over the past few lessons, you have built a robust toolkit for verifying your contracts, covering everything from basic state assertions and event emissions to complex multi-user interaction tests. Now, we'll add one more practical skill to your arsenal: debugging.

While writing tests helps you verify that your code works, debugging helps you figure out why it doesn't. In this lesson, you will learn how to use console.log directly within your Solidity code. Much like using print() statements in Python or inspecting logs in an ETL process to check intermediate values, this feature provides a direct window into your contract's execution flow within the Hardhat environment.

By the end of this lesson, you will be able to effectively use console.log to inspect variables and trace the execution path of your smart contracts, making your development process faster and more efficient.

1. A Familiar Tool in a New Context

If you've written code in JavaScript, Python, or many other languages, console.log is a familiar friend. Hardhat brings this simple yet powerful debugging utility to Solidity. It allows you to print messages and variable values from your contract functions to the terminal when you run your tests.

This is a feature provided specifically by the Hardhat Network. It is not a native part of the Solidity language itself. To use it, you must first import the console.sol library provided by Hardhat.

An example of `console.log` being used within Solidity functions to output data during a test run. The left side shows the contract code, and the right side displays the logged values in the terminal.

The Hardhat documentation provides a great introductory example. It shows how to add the import statement and use console.log within a transfer function to see who is transferring tokens and how much.

6. Debugging with Hardhat Network | Ethereum development ...

This tutorial page will walk you through the basic setup and usage of console.log in a Solidity contract.

Read the section Solidity console.log. Pay attention to these three key steps: Importing hardhat/console.sol. Calling console.log() inside a function with a formatted string and variables (msg.sender, to, amount). Observing the output in the terminal when npx hardhat test is run.

2. Supported Data Types and Formatting

The console.log function in Hardhat is quite versatile. It supports common value types and allows for formatting similar to Node.js's console.log.

For a complete reference on what's possible, the official Hardhat documentation is the best source. It details the supported data types and the different logging function overloads available.

Solidity console.log() reference | Hardhat 3

This reference page provides a comprehensive overview of the console.log functionality.

Please review the following sections: The <span data-type="resource_reading_textrange" data-resource-subitem-id="0e4eafbc" data-range-start="Hardhat allows you" data-range-end="block.number );">initial example demonstrates string formatting with %s for strings/addresses and %d for numbers. 2. The "Supported functions" section clarifies where you can place these log statements. 3. The "Supported console.log overloads" section lists the data types you can log (uint256, string, bool, address) and mentions the specialized functions like console.logInt(), console.logString(), etc.

As you read, note that you can log up to four parameters of mixed types, which is extremely useful for inspecting multiple variables in a single line.

The following video also provides a clear visual demonstration of using console.log and highlights its flexibility compared to using events for debugging purposes.

Debug Remix with Hardhat

This video from Smart Contract Programmer shows console.log in action within the Remix IDE connected to Hardhat.

Watch these key segments: Introduction: The instructor explains why console.log can be a more convenient debugging tool than events. Usage Demo: Here, you'll see how to log multiple variables of different types (address, uint, bool, string) in a single function call. Viewing Output: The video shows where the logged output appears in the transaction details.

3. Your Turn: Debugging the Box Contract

Now, let's apply this to the Box.sol contract we've been working with. We'll add some log statements to trace the execution of its key functions.

  1. Add the import statement at the top of your contracts/Box.sol file:

    import "hardhat/console.sol";
    
  2. Add console.log calls to the store and transferOwnership functions. Your modified Box.sol should look like this:

    // contracts/Box.sol
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.24;
    
    import "hardhat/console.sol"; // <-- ADD THIS LINE
    
    contract Box {
        uint256 private value;
        address public owner;
    
        event ValueChanged(uint256 newValue);
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
        constructor() {
            owner = msg.sender;
        }
    
        function store(uint256 newValue) public {
            console.log("Attempting to store value. Caller: %s, New Value: %d", msg.sender, newValue);
            require(msg.sender == owner, "Caller is not the owner");
            require(newValue != 0, "Value cannot be zero");
            value = newValue;
            emit ValueChanged(newValue);
        }
    
        function retrieve() public view returns (uint256) {
            return value;
        }
    
        function transferOwnership(address newOwner) public {
            console.log("Transferring ownership from %s to %s", owner, newOwner);
            require(msg.sender == owner, "Caller is not the owner");
            require(newOwner != address(0), "New owner is the zero address");
            address oldOwner = owner;
            owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    }
    
  3. Run your tests using the command npx hardhat test.

You will now see your log messages interspersed with the test results in your terminal. For example, when the test for transferring ownership runs, you should see a line like: Transferring ownership from 0xf39... to 0x709.... This gives you immediate feedback on the state of your contract during the test execution.

4. Important Considerations

While console.log is a fantastic development tool, it's crucial to understand its limitations and implications.

  • Gas Cost: The console.log functions are not free. They are implemented as calls to a special, pre-defined contract address. While Hardhat intercepts these calls and prints to your terminal, they still consume gas during execution.
  • Production Code: Because of the added gas cost and the fact that they serve no purpose on a live network, you must remove all console.log statements from your code before deploying to a mainnet or public testnet. Forgetting to do so will make your contract functions more expensive for users, with no benefit.
  • How it Works: As mentioned in the Hardhat documentation, console.log works by making a staticcall to a specific address. The Hardhat Network is designed to detect calls to this address, decode the input data, and print it. On other networks like Ethereum Mainnet, this address has no code, so the call does nothing but still consumes gas.

Conclusion

You have now learned how to use console.log to peer inside your smart contracts during execution, a fundamental skill for efficient debugging. This concludes our module on smart contract testing.

Let's recap what you've accomplished:

  • You can write structured tests using Mocha, Chai, and ethers.js.
  • You can test for correct state changes, expected failures (revertedWith), and event emissions.
  • You can simulate a multi-user environment by sending transactions from different accounts.
  • And now, you can use console.log to quickly diagnose issues during development.

These skills form the foundation for building secure and reliable smart contracts. Without rigorous testing, deploying any system, especially one that handles valuable assets, would be unacceptably risky.

In the next module, "Advanced Solidity and Security Patterns," we will shift our focus from testing to writing more sophisticated and secure code. We will explore concepts like contract inheritance, custom error handling, data location management for gas optimization, and crucial security patterns to protect against common vulnerabilities.

Can't find a good explanation? Sign up and we'll make it for you

Sign up