Posted on :: 200 Words :: Tags: ,

How to use Foundry with OpenZeppelin

Installation - Foundry Book

forge init {ProjectName}
cd {ProjectName}

Install openzeppelin-contracts:

forge install OpenZeppelin/openzeppelin-contracts
echo "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/" >> remmapings.txt

The project's structure should be:

.
├── README.md
├── foundry.toml
├── lib
│   ├── forge-std
│   └── openzeppelin-contracts
├── remmapings.txt
├── script
│   └── Counter.s.sol
├── src
│   └── Counter.sol
└── test
    └── Counter.t.sol

We can replace all the occurrences of Counter to Token i.e:

.
├── README.md
├── foundry.toml
├── lib
│   ├── forge-std
│   └── openzeppelin-contracts
├── remmapings.txt
├── script
│   └── Token.s.sol
├── src
│   └── Token.sol
└── test
    └── Token.t.sol

Then, inside Token.sol, which is the main contract, the example from the OZ docs' can be used:

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

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

contract Token is ERC20 {
    constructor(uint256 initialSupply) ERC20("Token", "TKN") {
        // The deployer will have all the initialSupply
        _mint(msg.sender, initialSupply);
    }
}

Then we have to change the deployment script script/Token.s.sol:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

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

import "../src/Token.sol";

contract TokenScript is Script {
    uint256 deployerPrivateKey;
    function setUp() public {
        // foundry will read the env variable named PRIVATE_KEY
        // and it will use it to deploy the contract
        deployerPrivateKey = vm.envUint("PRIVATE_KEY");
    }

    function run() public {
        vm.startBroadcast(deployerPrivateKey);

        // This is the initialSupply the constructor will use
        Token token = new Token(1000000);

        vm.stopBroadcast();
    }
}

For simplicity, the contract will be deployed in a local environment created with anvil. Run anvil in a terminal, the output should be:

Available Accounts
==================

(0) 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000.000000000000000000 ETH)
.
.

Private Keys
==================

(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
.
.

This funded account can be used to deploy the contract, with anvil running, open a new terminal and run:

echo "PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" > .env
echo "ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" >> .env

Then, deploy the contract, forge will automatically read the .env file:

forge script script/Token.s.sol:TokenScript --rpc-url http:localhost:8545 --broadcast

Note: if using http:localhost:8545, which is anvil's default port, the flag --rpc-url is not necessary.

Finally, the output should contain the Contract Address:

✅  [Success]Hash: 0x07ba8c8365d97fa68eef4d623fe5c1a91b7318dee6071b3c4e0de3b8a8e6551b
Contract Address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Block: 1
Paid: 0.002214764 ETH (553691 gas * 4 gwei)

It can be used with cast to ask for the erc20 balance, the constructor of the Contract was defined to mint the initialSupply to the deployer address, and the initialSupply was defined in the Token.s.sol: Token token = new Token(1000000);:

source .env
cast balance --erc20 0x5FbDB2315678afecb367f032d93F642f64180aa3 $ADDRESS --rpc-url http:localhost:8545

Output:

1000000 [1e6]