Build, Test StableCoin Smart Contract Using Foundry, and Deploy on RSK Network

Creating a stablecoin is a significant endeavor in the world of decentralized finance (DeFi). In this comprehensive tutorial, we will walk you through the entire process of building, testing, and deploying a stablecoin smart contract using Foundry, a powerful development toolkit, on the RSK test network. I will cover everything from setting up your environment to writing the smart contract, testing it, and finally deploying it on the Rootstock Testnet.

Understanding Stablecoins

What is a Stablecoin?

A stablecoin is a type of cryptocurrency designed to maintain a stable value relative to a specific asset, typically a fiat currency like the US dollar. The primary purpose of stablecoins is to provide the benefits of cryptocurrencies, such as fast transactions and low fees, while minimizing the volatility that often accompanies them.

Why Are Stablecoins Important?

Stablecoins serve several critical functions in the DeFi ecosystem:

  • Medium of Exchange: They facilitate transactions without the volatility associated with traditional cryptocurrencies.

  • Unit of Account: Stablecoins provide a reliable measure of value, making it easier to price goods and services.

  • Storage of Value: They allow users to store value in a digital format without the risk of significant price fluctuations.

Types of Stablecoins

Stablecoins can be categorized into three main types:

  1. Fiat-Collateralized Stablecoins: These are backed by reserves of fiat currency. For example, Tether (USDT) and USD Coin (USDC) are pegged to the US dollar.

  2. Crypto-Collateralized Stablecoins: These are backed by other cryptocurrencies. An example is DAI, which is over-collateralized with Ethereum and other crypto assets.

  3. Algorithmic Stablecoins: These use algorithms to control the supply of the stablecoin, adjusting it based on demand. Examples include Terra (LUNA) and Ampleforth (AMPL).

Setting Up Your Development Environment

Before you can start building your stablecoin, you need to set up your development environment. This involves installing Foundry and configuring it to work with the RSK network.

Install Foundry

Foundry is a fast, portable, and modular toolkit for Ethereum application development. To install Foundry, follow these steps:

  1. Open your terminal.

  2. Run the following command to install Foundry:

     curl -L https://foundry.paradigm.xyz | bash
    
  3. After installation, run the following command and follow the instructions
    foundryup

Now you installed Foundry, let’s create a fresh new directory and start building our smart contract.

Create a new directory for your project

mkdir stables
  1.   cd stables
    
  2. Initialise a new Foundry project

     forge init
    

Writing the Stablecoin Smart Contract

Now that your environment is set up, it's time to write the smart contract for your stablecoin. This contract will define the core functionalities, such as minting, and burning tokens. Go inside your foundry project’s src directory and delete that counter.sol file and create a new file named StableCoin.sol and let’s start writing our smart contract. Also, go inside test and script directory and delete counter.t.sol and counter.s.sol files. But before going ahead we need to install OpenZeppelin contract library in our foundry stablecoin project because we are building ERC20 StableCoin and we will use Openzeppelin for that.

Open your terminal, make sure you're in the stables directory, and install OpenZeppelin by running the following command:

forge install Openzeppelin/openzeppelin-contracts

Great, now we need to set our remappings so we can avoid some Red lines in our code and compile our contracts successfully.

To set remappings run the following command

forge remappings >> remappings.txt

And you can see in your project’s root there is a new file named remappings.txt automatically generated with setting remappings which will look something like this, (see the image below)

Now go back to your StableCoin contract file and start writing the smart contract. Feel free to copy from here, I have explained the smart contract and its functionalities in the contract comments.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;


import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";


/**

 * ⚠️ Dont use this contract for production

 * @title StableCoin

 * @dev A simple stablecoin implementation built on Rootstock network

 * This contract implements an ERC20 token with minting and burning capabilities

 * controlled by an owner. It inherits from OpenZeppelin's ERC20 and Ownable contracts.

 *

 * Security:

 * - Only the owner can mint and burn tokens

 * - Uses OpenZeppelin's battle-tested contracts for ERC20 functionality and access control

 */

contract StableCoin is ERC20, Ownable {

    /**

     * @dev Constructor initializes the stablecoin with a name and symbol

     * @param name The name of the stablecoin

     * @param symbol The symbol of the stablecoin

     */

    constructor(

        string memory name,

        string memory symbol

    ) ERC20(name, symbol) Ownable(msg.sender) {}


    /**

     * @dev Mints new tokens to a specified address

     * @param to The address that will receive the minted tokens

     * @param amount The amount of tokens to mint

     * Requirements:

     * - Only callable by the contract owner

     */

    function mint(address to, uint256 amount) public onlyOwner {

        _mint(to, amount);

    }


    /**

     * @dev Burns tokens from a specified address

     * @param from The address from which tokens will be burned

     * @param amount The amount of tokens to burn

     * Requirements:

     * - Only callable by the contract owner

     */

    function burn(address from, uint256 amount) public onlyOwner {

        _burn(from, amount);

    }


    /**

     * @dev Returns the number of decimals used for token amounts

     * @return The number of decimals (18 for this implementation)

     */

    function decimals() public pure override returns (uint8) {

        return 18;

    }

}

Amazing, our contract is ready. Let's try compiling it by running the following command

forge build

And you will see output like Compiler run successfully!

Congrats, you successfully written and compiled your contract and now it’s time to test our contract.

To test our contract go inside the test directory and create a new file named Stablecoin.t.sol and paste the following test suite code inside this file.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;


import {Test, console2} from "forge-std/Test.sol";

import {StableCoin} from "../src/StableCoin.sol";

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";


contract StableCoinTest is Test {

    StableCoin public token;

    address public owner;

    address public user1;

    address public user2;


    function setUp() public {

        owner = address(this);

        user1 = makeAddr("user1");

        user2 = makeAddr("user2");

        token = new StableCoin("Test Stablecoin", "TST");

    }


    function testInitialState() public view {

        assertEq(token.name(), "Test Stablecoin");

        assertEq(token.symbol(), "TST");

        assertEq(token.decimals(), 18);

        assertEq(token.totalSupply(), 0);

    }


    function testMint() public {

        uint256 amount = 1000  10 * 18; // 1000 tokens with 18 decimals

        token.mint(user1, amount);

        assertEq(token.balanceOf(user1), amount);

        assertEq(token.totalSupply(), amount);

    }


    function testBurn() public {

        uint256 amount = 1000  10 * 18;

        token.mint(user1, amount);

        token.burn(user1, amount);

        assertEq(token.balanceOf(user1), 0);

        assertEq(token.totalSupply(), 0);

    }


    function testTransfer() public {

        uint256 amount = 1000  10 * 18;

        token.mint(user1, amount);

        vm.prank(user1);

        token.transfer(user2, amount);

        assertEq(token.balanceOf(user2), amount);

        assertEq(token.balanceOf(user1), 0);

    }


    function test_Revert_When_NonOwnerMints() public {

        vm.prank(user1);

        vm.expectRevert(

            abi.encodeWithSelector(

                Ownable.OwnableUnauthorizedAccount.selector,

                user1

            )

        );

        token.mint(user2, 1000  10 * 18);

    }


    function test_Revert_When_NonOwnerBurns() public {

        uint256 amount = 1000  10 * 18;

        token.mint(user1, amount);

        vm.prank(user2);

        vm.expectRevert(

            abi.encodeWithSelector(

                Ownable.OwnableUnauthorizedAccount.selector,

                user2

            )

        );

        token.burn(user1, amount);

    }

}

We have written some tests here to check all our contract’s functionalities working correctly and as we expect. Let’s check our test results. Run the following command in your terminal:

forge test

And here we go… you will see output like this in your terminal our all 6 tests passed ✅

Great! So far… we have built our contract and tested it using forge - one of the most powerful components of the foundry. Now it’s time to deploy our smart contract on RSK test network. And to do that we need to write Deployment script (solidity scripting). Let’s go ahead and create a new file Deploy.s.sol inside your src directory and paste the following script written in solidity.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;


import {Script, console} from "forge-std/Script.sol";

import {StableCoin} from "../src/StableCoin.sol";


/**

 * @title DeployScript

 * @dev Deployment script for the Rootstock Stablecoin

 * This script handles the deployment of the StableCoin contract to the network

 * using Foundry's deployment framework.

 *

 * The script:

 * 1. Takes the deployer's address from msg.sender

 * 2. Deploys a new StableCoin instance with name "Rootstock Stablecoin" and symbol "RST"

 * 3. Logs the deployed contract address for verification

 */

contract DeployScript is Script {

    /**

     * @dev Main deployment function

     * This function is called by Forge during deployment

     * It handles the deployment of the StableCoin contract and logs the deployment address

     */

    function run() public {

        address deployer = msg.sender;

        vm.startBroadcast(deployer);


        StableCoin token = new StableCoin("Rootstock Stablecoin", "RST");

        console.log("Stablecoin deployed to:", address(token));


        vm.stopBroadcast();

    }

}

We finished writing the contract, testing, and writing our deployment script. We are ready to deploy but before deployment on RSK network we need to finish some tasks, in this tutorial, we have been following some best security practices and we will make our progress constant. We not gonna use traditional .env file here instead we will set up our foundry keygen wallet and I will show you how you can import your wallet’s private key here inside foundry and follow some best security practices.
Open your terminal and run the command to import your wallet.

cast wallet import --private-key YOUR_PRIVATE_KEY YOUR_WALLET_NAME

Make sure you are adding your wallets private key and wallet name in this command. Learn how to export MetaMask wallet private key - https://support.metamask.io/configure/accounts/how-to-export-an-accounts-private-key/

It will ask you to set up a password for your wallet; enter the password and set it up.

Now run the following command, it will show you the wallet you imported.

cast wallet list

Now go inside your foundry.toml file and set up evm version to London and RPC URL. Your foundry.toml file should look like this.

[profile.default]

src = "src"

out = "out"

libs = ["lib"]

solc = "0.8.20"

optimizer = true

optimizer_runs = 200

evm_version = "london"


[rpc_endpoints]

rootstock_testnet = "https://public-node.testnet.rsk.co"

All set! Now you are finally ready to deploy your contract on Rootstock network. Ensure you have an RSK wallet set up and funded with RSK tokens for transaction fees.

To add RSK network to your wallet follow the link: https://dev.rootstock.io/dev-tools/wallets/metamask/

To get Rootstock faucets follow the link: https://faucet.rootstock.io/

To deploy our masterpiece go ahead and open your terminal (make sure you are in the stables directory) and run the following command.

forge script script/Deploy.s.sol:DeployScript --rpc-url rootstock_testnet --broadcast --legacy --account YOUR_WALLET_NAME --sender YOUR_WALLET_ADDRESS

Example:

forge script script/Deploy.s.sol:DeployScript --rpc-url rootstock_testnet --broadcast --legacy --account pandit --sender 0x339abb297eb21a0ee52e22e07dde496c0fe98fb9

It will ask you for the password, enter the password we set up while importing our private key in foundry keystore. If you successfully enter the password it will show output in your terminal like this (see the image below)

And here we go… our deployment is successful.

Massive congratulations, you successfully built, tested, and deployed a stablecoin smart contract using the blazing-fast smart contract tool Foundry. You should be incredibly proud of yourself. But wait your job ain’t done yet, we need to check whether our contract is indeed deployed on chain or not. To check that, copy your deployed contract address from the terminal, visit https://explorer.testnet.rootstock.io/, and paste your copied contract address into the search bar and you can see all the information related to your deployment here on this block explorer.

Great. If you are stuck anywhere feel free to ask for help in Rootstock communities. Join the Discord and Telegram communities using the following links:

Discord: https://discord.gg/QRw2XCE6m7

Telegram: @rskofficialcommunity

Docs: https://dev.rootstock.io/

I hope now you have a pretty good understanding of how you can develop and deploy contracts on the Rootstock network.

2
Subscribe to my newsletter

Read articles from Pandit Dhamdhere directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pandit Dhamdhere
Pandit Dhamdhere

01101101 01100001 01100100 01100101 00100000 01111001 01101111 01110101 00100000 01101100 01101111 01101111 01101011