Smart Contracts Just Got Smarter With AI Auditing

25.04.2023 | 3 min read

Smart contracts are the new cool kids on the blockchain block. But, let's face it, they're not always as smart as they claim to be. But entering AI-powered auditing is like giving your smart contract a brain transplant. Suddenly, it's not just smart, it's Einstein-level genius. In this article, we dive into how AI can make your smart contract audits quicker and more accurate.

I'll share my experience, the conversation I had with an AI language model, and the process we went through to arrive at the final version of the smart contract.

At the very beginning, I tried to ask ChatGPT 4 to build a simple token with deflationary tokenomics so that we have material for auditing.

To begin, ChatGPT wrote a basic deflationary token contract with a 1% burn rate on every transaction. Note that this code is for educational purposes only and has not been audited. Before deploying any smart contract on the Ethereum network, make sure to have it audited and tested.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract DeflationaryToken is ERC20 {
    uint256 public constant BURN_RATE = 100; // 1% burn rate

    constructor(uint256 initialSupply) ERC20("DeflationaryToken", "DFT") {
        _mint(msg.sender, initialSupply);
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal override {
        uint256 burnAmount = amount / BURN_RATE;
        uint256 netAmount = amount - burnAmount;

        super._transfer(sender, recipient, netAmount);
        super._transfer(sender, address(0), burnAmount);
    }
}

Moreover, I was supplied with satisfying comment:

This contract imports the OpenZeppelin ERC20 contract and creates a new contract called DeflationaryToken that inherits the ERC20 contract. The burn rate is set to 1% (represented by BURN_RATE constant).



The _transfer function is overridden to implement the deflationary mechanism. During each transfer, the contract calculates the burn amount (1% of the transferred amount) and deducts it from the original transfer amount. The net amount is then transferred to the recipient, while the burn amount is sent to the zero address, effectively burning the tokens.



Remember to install the OpenZeppelin Contracts library before deploying this contract

The contract looks like it was written by a beginner, blockchain programmer.

Audit

After writing the initial version of the contract, I reached out to an AI language model for advice on how to improve the code. The AI model suggested several enhancements, such as adding comments and documentation, specifying the initial supply and token details, using SafeMath for arithmetic operations, making the burn rate a variable, and emitting events when significant actions occur.

Based on the AI's recommendations, I made the necessary changes to the contract:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

/**
 * @title DeflationaryToken
 * @dev A simple deflationary ERC20 token with a burn rate on every transaction.
 */
contract DeflationaryToken is ERC20 {
    using SafeMath for uint256;

    uint256 public burnRate; // Burn rate as a percentage (e.g., 100 represents 1%)

    event BurnRateChanged(uint256 newBurnRate);

    constructor(
        uint256 initialSupply,
        string memory name,
        string memory symbol,
        uint256 _burnRate
    ) ERC20(name, symbol) {
        require(_burnRate <= 10000, "Invalid burn rate");
        _mint(msg.sender, initialSupply);
        burnRate = _burnRate;
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal override {
        uint256 burnAmount = amount.mul(burnRate).div(10000);
        uint256 netAmount = amount.sub(burnAmount);

        super._transfer(sender, recipient, netAmount);
        super._transfer(sender, address(0), burnAmount);
    }

    function setBurnRate(uint256 newBurnRate) external {
        require(newBurnRate <= 10000, "Invalid burn rate");
        burnRate = newBurnRate;
        emit BurnRateChanged(newBurnRate);
    }
}

In The new version of smartcontract GPT added not really necessary (in this case) SafeMath library and added comments - which is good, but it’s only a cosmetics - this contract is very easy to understand without comments, which are actually missing in some places like _transfer and setBurnRate function.

Below is the list of all suggestions AI told me:

  • Add comments and documentation: Including clear and concise comments helps make the code more readable and understandable. Consider using the NatSpec format to describe the contract and its functions.
  • Specify the initial supply and token details: The initial supply, token name, and symbol are hardcoded in this example. To improve flexibility, consider passing these values as constructor arguments.
  • Use SafeMath: Although Solidity 0.8.0 and later have built-in overflow and underflow checks, it's still a good idea to use OpenZeppelin's SafeMath library for additional safety when performing arithmetic operations.
  • Expose burn rate as a variable: The burn rate is set as a constant in the contract. You may want to allow the burn rate to be adjustable through a function callable by the contract owner or a predefined governance mechanism.
  • Events and logging: Emit events when significant actions occur, such as changing the burn rate or other token parameters. This makes it easier to track and analyze the contract's operation.

I think those are mostly good practices, not security risks to be eliminated.

Implementing Access Control

While discussing the contract with the AI, we realized that certain functions, like setBurnRate and mint, should be access-controlled to prevent unauthorized changes. We decided to use OpenZeppelin's Ownable contract to provide a simple mechanism for access control. This allowed us to restrict access to those functions to the contract owner only.

In my opinion, this should be added at the very beginning, but fortunately, pointing out the problem made the GPT do the improvements.

Adding a Hard Cap on Token Supply

As the conversation with the AI continued, we discussed how to make the token truly deflationary by introducing a hard cap on the total supply. This would ensure that no more tokens could be minted once the cap was reached, helping maintain scarcity and ensuring a decreasing token supply as tokens were burned in each transaction.

Following this idea, chat updated the contract to include a hard cap on the token supply:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

/**
 * @title DeflationaryToken
 * @dev A simple deflationary ERC20 token with a burn rate on every transaction and a hard cap on total supply.
 */
contract DeflationaryToken is ERC20, Ownable {
    using SafeMath for uint256;

    uint256 public burnRate; // Burn rate as a percentage (e.g., 100 represents 1%)
    uint256 public hardCap; // Maximum number of tokens that can be minted

    event BurnRateChanged(uint256 newBurnRate);

    constructor(
        string memory name,
        string memory symbol,
        uint256 _burnRate,
        uint256 _hardCap
    ) ERC20(name, symbol) {
        require(_burnRate <= 10000, "Invalid burn rate");
        require(_hardCap > 0, "Hard cap must be greater than 0");
        burnRate = _burnRate;
        hardCap = _hardCap;
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal override {
        uint256 burnAmount = amount.mul(burnRate).div(10000);
        uint256 netAmount = amount.sub(burnAmount);

        super._transfer(sender, recipient, netAmount);
        super._transfer(sender, address(0), burnAmount);
    }

    function setBurnRate(uint256 newBurnRate) external onlyOwner {
        require(newBurnRate <= 10000, "Invalid burn rate");
        burnRate = newBurnRate;
        emit BurnRateChanged(newBurnRate);
    }

    function mint(address to, uint256 amount) external onlyOwner {
        uint256 newTotalSupply = totalSupply().add(amount);
        require(newTotalSupply <= hardCap, "Minting would exceed hard cap");

        _mint(to, amount);
    }
}

VERY NICE: Chat each time reminds me that it’s only a training model and I should not trust it: “Remember to consult with professional smart contract auditors for a comprehensive and in-depth analysis before deploying any smart contract on the Ethereum mainnet or other networks.”

Conclusions

IMPORTANT: It's important to remember that AI models are not a substitute for professional smart contract auditors. Before deploying any smart contract on the Ethereum mainnet or other networks, make sure to consult with professional auditors for a comprehensive and in-depth analysis.

  • Smartcontract is written in Solidity version 0.8.0, while the newest version now is 0.8.17 (I can accept that since it’s still relatively new version and GPT has limited knowledge after 2021.
  • Chat remembers of good practices that it suggests, but only for the beginning of the code, later on, it somehow “forgets” them
  • As a blockchain professional, it was actually me auditing the AI’s work, not AI guiding me in this.
  • AI may be a super powerful tool as your personal assistant, but unfortunately, you need to keep your eyes wide open, because it may make mistakes
  • If you are a blockchain professional, you may try to utilise AI for checking your code against some optimisations or good practices, however, I strongly discourage you to utilise AI as your smartcontracts auditor.

What AI was better with, is that it helped me write this article, basing on our whole conversation.

You may also like these posts

Start a project with 10Clouds

Hire us