Even with the best tools, auditing a smart contract on Hardhat can feel like navigating a maze in the dark, especially when dealing with nested calls and complex logic. Are you struggling to track variable values and control flow during your audits or development?
Imagine pinpointing issues with precision, sharing testnet progress seamlessly with your team, and eliminating the guesswork. In this article, we'll guide you through integrating Tenderly with your Hardhat project, focusing on practical steps and crucial insights beyond the official documentation.
From a security point of view, while auditing protocols, you will encounter multiple scenarios where keeping track of various variable values in the control flow is not feasible. For example, there can be multiple nested calls with values getting updated in each call frame. In many large projects, it’s impractical to track everything mentally, and direct debugging is also not possible if you’re using a Hardhat project.
While we have explored some local options, this would be more user friendly option. with possibility of working with multiple teams by sharing the TestNet progress.
The project consists of three main simplified smart contracts:
Really short explanation of the contract functionality / entry points for mentioned contracts:
Initializes the staking contract with the MyToken and Rewards contract addresses.
Allows users to stake a specified amount of MyToken tokens, updating their staked amount and the total staked amount.
Allows users to unstake a specified amount of MyToken tokens, updating their staked amount, the total staked amount, and claiming rewards.
Returns the staked amount of a specified user.
Returns the total amount of tokens staked in the contract.
Rewards.sol
)Initializes the rewards contract with the address of the staking contract.
Calculates the rewards for a specified user based on their staked amount.
Claims the rewards for a specified user, ensuring only the staking contract can call this function.
From now onwards, you can follow the official documentation, but there’s something additional I’d like to share that you might not find in the docs.
1. Install the Tenderly CLI
brew tap tenderly/tenderly
brew install tenderly
tenderly login
Once logged in, you can check tenderly whoami to confirm.
2. Install tenderly-hardhat plugin
npm install --save-dev @tenderly/hardhat-tenderly
3. Import the plugin to your hardhat.config.js/ts file like
const tenderly = require("@tenderly/hardhat-tenderly"); the way of importing will vary based if its .ts or .js file.
4. Generally if you are using @tenderly/hardhat-tenderly version that is upto 2.4.0 you need to call the tdly.setup() and set the automaticVerifications option to true like this: tenderly.setup({ automaticVerifications: true });
But if your are using the version that is greater than this then you can set the TENDERLY_AUTOMATIC_VERIFICATION environment variable to true for example with .env file. Otherwise you might get warning like this.
5. Adding tenderly configuration in hardhat configuration:
Where you need to create a virtual TestNet using Tenderly dashboard and add a network with a Tenderly virtual TestNet URL and Tenderly configuration object:
Writing the deployment script. How script works is not explained as it is assumed that reader knows it already.
As you can see the script contains code to deploy contracts and to call the functions. and based on the smart contract logic it expects the output.
But additionally you can see when calling any functions on the smart contract, the function is actually called on native Contract and not on contract name variable/instance. That's because once you configure hardhat to use Tenderly the types of these contracts are changed from BaseContract type to TdlyContract . you can check this by printing the contract instance that BaseContract is in the nativeContract . Generally it is directly available when the tenderly is not configured. But here, directly calling the function won't work with tenderly setup.
Additionally it is helpful to use waitForDeployment() on contracts after deploying (as can be seen in the script above) as it will verify the contracts in tenderly TestNets.
Since the automatic contract verification is set to true, you can just run the deployment script with the command like: where the virtualMainnet is the network name in the hardhat config file:
npx hardhat run scripts/deploy.ts --network virtualMainnet
Open tenderly TestNet dashboard and debug the transaction by clicking on the transaction hash.
and here we go debugging:
While Tenderly offers a wealth of features, we focused on addressing the specific challenges and 'quirks' encountered during script development, particularly the crucial aspect of accessing the base contract object for function calls. These insights, often less emphasized in official documentation, are essential for a smooth debugging experience.
By mastering these nuances, you'll be well-equipped to navigate complex smart contract logic and enhance your development workflow, ultimately leading to more effective smart contract auditing. Remember, effective debugging is not just about finding errors; it's about gaining a deeper understanding of your code's behavior and building more robust, secure smart contracts.