Skip to content

kalaspuff/solidity-template

Repository files navigation

≑ solidity-template

This is a boilerplate repo providing a base setup to get started experimenting with smart contracts built in Solidity. This template should preferably be used with the Foundry toolkit. The setup is mostly tested on macOS.

Smart Contract Development in Solidity for Ethereum

  • ✨ Developer experience improvements using good defaults and with the goal to follow best practices.
  • πŸͺ’ Comes with both a simple example contract and a more complex upgradeable contract + ERC1967 proxy.
  • πŸš€ Includes deployment scripts to easily deploy to different networks, as well as some basic unit tests.
  • πŸͺ„ Sensible setup for linting, code formatting β€” includes VSCode settings for auto code style, etc.
  • πŸ“š Includes some common libraries (such as OpenZeppelin, solmate and contracts from Safe).
  • πŸŽ‰ Aims to make it easy for devs to prototype and code contracts without the effort of the initial chores.

GitHub Codespaces

There's also a dev container setup included in this repo to make it super easy to set up with codespaces.

It takes ~ 90 seconds for the codespace to install and spin up. It provides a complete dev setup with all the dependencies and VSCode extensions installed. Everything needed to develop, build and deploy contracts. πŸ’«

Open in GitHub Codespaces

codespace-vscode-screenshot

Contributions

If you find any bugs πŸ› in the contracts, in this documentation or if you have ideas πŸ’‘ of ways to make this more useful and dev friendly – please open an issue or PR on GitHub βž” kalaspuff/solidity-template. πŸ™

Get started

These section is for setting up the environment without the use of GitHub Codespaces. The instructions expect you to be running a Linux or macOS system with Foundry and build essentials already installed.

πŸƒ ⌁ Initialize and install dependencies

Initialize a project with forge init -t kalaspuff/solidity-template to use this repo as template.

user@cpu:~/code $ forge init -t kalaspuff/solidity-template my-contract
Initializing ./my-contract from https://github.com/kalaspuff/solidity-template...
Initialized forge project.

The new project will be initialized at the given name (in the above example my-contract).

user@cpu:~/code $ cd my-contract
user@cpu:~/code/my-contract $

Populate the .env with the .env.example data (which includes some free RPC nodes from Ankr).

user@cpu:~/code/my-contract $ cp .env.example .env

The linter solhint is installed via npm

user@cpu:~/code/my-contract $ npm install

πŸ”— ⌁ Update the remote origin

After you've initialized your project with forge init you need to change remote repository to be able to push commits towards your own repo.

Change the remote url to for example coder/my-contract (on GitHub)
user@cpu:~/code/my-contract $ git remote set-url origin https://github.com/coder/my-contract.git
user@cpu:~/code/my-contract $ git remote -v
origin https://github.com/coder/my-contract (fetch)
origin https://github.com/coder/my-contract (push)

Development

A couple of make commands are available for convenience and general quality of life.

user@cpu:~/code/my-contract $ make
usage:
  $ make build         βž” build contracts
  $ make test          βž” run unit tests
  $ make lint          βž” lint code
  $ make install       βž” install dependencies
  $ make format        βž” apply code style formatting
  $ make clean         βž” remove build artifacts

Explore Foundry if you haven't already. πŸ’»

  • Highly recommended to become used to the CLI tools from Foundry – specially forge and cast.
  • Start with checking out the help output from forge --help and cast --help.
  • There's also additional information to read up on at https://book.getfoundry.sh/.

🚧 ⌁ Build contracts

Build the contracts using make build or forge build.

user@cpu:~/code/my-contract $ make build
[make: cmd] βž” forge build
[⠊] Compiling...
[⠘] Solc 0.8.17 finished
Compiler run successful

πŸ‘Œ ⌁ Linting and code styling

If you're a VSCode user with the Solidity extension installed you'll get all the linting and automated code formatting you need from within VSCode.

For all other linting needs – run lint checks with make lint or manually with solhint and forge fmt --check.

user@cpu:~/code/my-contract $ make lint
[make: cmd] βž” npx solhint contracts/**/*.sol
[make: cmd] βž” forge fmt --check

πŸ§ͺ ⌁ Test cases implemented in Solidity

Run the Solidity tests that lives in the ./test folder with make test or forge test.

user@cpu:~/code/my-contract $ make test
[make: cmd] βž” forge test
Running 2 tests for test/SimpleContract.t.sol:SimpleContractTest            βœ”οΈŽβœ”οΈŽ
Running 5 tests for test/UpgradeableContract.t.sol:UpgradeableContractTest  βœ”οΈŽβœ”οΈŽβœ”οΈŽβœ”οΈŽβœ”οΈŽ

Installation of Foundry

In case you haven't installed the Solidity development toolset Foundry (forge, cast and anvil), follow the installation instructions at https://book.getfoundry.sh/getting-started/installation.html.

In short, here's the easiest option on Linux and macOS.

Install the foundryup shell script, followed by installation of forge, cast and anvil
user@cpu:~ $ curl -L https://foundry.paradigm.xyz | bash
Installing foundryup...

When installing foundryup, the PATH to where Foundry is located is added to your shell's profile file. Follow the instructions that's shown of how to reinitialize your shell (usually source ~/.zshrc or source ~/.bashrc).

Use foundryup to complete the installion of forge, cast and anvil
user@cpu:~ $ foundryup
foundryup: installing foundry (version nightly, tag nightly)
foundryup: done

The .env file and environment values

Set up your credentials required for deployments (for testnets + mainnet or other EVM chains), together with API keys for etherscan, etc. to automate the verification part during deployments. The .env file is ofc. ignored using .gitignore so that it isn't accidentally committed to a Git repo.

πŸ”’ ⌁ Example of .env configuration values

The default .env.example file comes with a few examples of of values, but you usually will only need to specify a few of them - here's an example for set up where we want to test on Goerli and later deploy to Ethereum mainnet:

# the private key of the eoa address you want to initiate deploy scripts from
DEPLOYER_PRIVATE_KEY="0x0000000000000000000000000000000000000000000000000000000000000000"

# for goerli and mainnet we'll often use the same etherscan api key
MAINNET_ETHERSCAN_API_KEY="000000000000ETHERSCANAPIKEYEXAMPLE"
GOERLI_ETHERSCAN_API_KEY="000000000000ETHERSCANAPIKEYEXAMPLE"

# any rpc url of your choice or for example rpc urls provided from alchemy, ankr, etc.
MAINNET_RPC_URL="https://rpc.ankr.com/eth"
GOERLI_RPC_URL="https://rpc.ankr.com/eth_goerli"

Deployment

DISCLAIMER

  • πŸ§‘β€πŸ”¬ Code in this repo may be experimental.
  • πŸ› Code may not function in your intended way.
  • 🚨 Know what you're doing.
  • πŸ’Έ Use at your own risk.

A few examples of how the provided example contracts could be deployed and verified on Etherscan. Please understand the disclaimer above so you don't accidentally end up losing your precious ETH.

πŸš€ ⌁ Deployment script for flexible or complex deployments

Deployment by running a Solidity script + verification of all deployed contracts during broadcast.

  • βž” contracts/SimpleContract.sol

    • Deployment using the DEPLOYER_PRIVATE_KEY which you can specify in your .env.

      FOUNDRY_PROFILE="goerli" \
      forge script script/deploy/DeploySimple.s.sol -vvvv --verify --broadcast
    • Deployment with a Ledger wallet (use your address as env value to FOUNDRY_SENDER).

      FOUNDRY_PROFILE="goerli" \
      FOUNDRY_SENDER="0x39bEb60bc4c1b8b0eBeEDC515c7A56e7DfB3a5A9" \
      forge script script/deploy/DeploySimple.s.sol -vvvv --verify --broadcast -l
  • βž” contracts/UpgradeableContract.sol
    βž” contracts/proxy/ERC1967Proxy.sol

    • Deployment using the DEPLOYER_PRIVATE_KEY which you can specify in your .env.

      FOUNDRY_PROFILE="goerli" \
      forge script script/deploy/DeployUpgradeable.s.sol -vvvv --verify --broadcast
    • Deployment with a Ledger wallet (use your address as env value to FOUNDRY_SENDER).

      FOUNDRY_PROFILE="goerli" \
      FOUNDRY_SENDER="0x39bEb60bc4c1b8b0eBeEDC515c7A56e7DfB3a5A9" \
      forge script script/deploy/DeployUpgradeable.s.sol -vvvv --verify --broadcast -l

πŸ¦„ ⌁ Alternative deployment for simpler contracts

This method instead deploys the contract using forge create.

This method is most likely only suitable for simpler contracts or contracts that doesn't require any dynamic arguments for their constructor.

  • βž” contracts/SimpleContract.sol

    • Deploying using forge create (specify the private key for the address to deploy from).

      FOUNDRY_PROFILE="goerli" \
      forge create --verify --private-key "PRIVATE KEY HERE" \
          contracts/SimpleContract.sol:SimpleContract
    • Deploying using forge create with a Ledger (FOUNDRY_SENDER value should be your address).

      FOUNDRY_PROFILE="goerli" \
      FOUNDRY_SENDER="0x39bEb60bc4c1b8b0eBeEDC515c7A56e7DfB3a5A9" \
      forge create --verify -l \
          contracts/SimpleContract.sol:SimpleContract

πŸ“’ ⌁ Etherscan verification for previously unverified contracts

Usually the contract should be verified during deployment if a correct API key for Etherscan is given and if the deployment was done together with the --verify option.

This is only needed to manually verify contracts that weren't verified during their deployment.

  • βž” contracts/SimpleContract.sol

    • Verify contract (specify the actual contract address instead of "CONTRACT ADDRESS").

      forge verify-contract \
          --chain "goerli" \
          --watch \
          "CONTRACT ADDRESS" \
          --constructor-args $(cast abi-encode "constructor()") \
          contracts/SimpleContract.sol:SimpleContract
  • βž” contracts/UpgradeableContract.sol
    βž” contracts/proxy/ERC1967Proxy.sol

    The upgradeable contract has an implementation contract as well as a proxy contract that needs verification. Replace the values for "IMPLEMENTATION ADDRESS", "PROXY ADDRESS" and "DEPLOYER ADDRESS" in the examples below.

    • Verification of the implementation contract and the proxy contract.

      forge verify-contract \
          --chain "goerli" \
          --watch \
          "IMPLEMENTATION ADDRESS" \
          --constructor-args $(cast abi-encode "constructor()") \
          contracts/UpgradeableContract.sol:UpgradeableContract
      forge verify-contract \
          --chain "goerli" \
          --watch \
          "PROXY ADDRESS" \
          --constructor-args \
              $(cast abi-encode \
                  "constructor(address,bytes)" \
                  "IMPLEMENTATION ADDRESS" \
                  $(cast abi-encode "initialize(address)" "DEPLOYER ADDRESS") \
              ) \
          contracts/proxy/ERC1967Proxy.sol:ERC1967Proxy

Adding and removing dependencies

🧭 Whenever you install new libraries / dependencies using Foundry (using foundry install), make sure to update your remappings.txt file by running forge remappings > remappings.txt.

βœ‚οΈ This boilerplate template includes a few different libs which you most likely won't need. Feel free to remove them from your setup – for example forge remove safe-contracts to remove the safe-contracts library.

Opinions and feedback

We're all learning. Feedback and contributions most welcome!

πŸ’¬ ⌁ Contact info

  • πŸŽ‰ If you think this is 🌟🀩🌹πŸͺ„πŸŒˆ and want to connect β€” shoot me a message and say hi. πŸ‘‹
  • 🀬 If you think this is πŸ’©πŸ›πŸ’₯πŸ™…πŸ‘Ž and want to help out β€” shoot me a message and say hi. πŸ™
twitter ◼️ https://twitter.com/carloscaraaro
coa.eth ◼️ 0x39bEb60bc4c1b8b0eBeEDC515c7A56e7DfB3a5A9
discord ◼️ carloscar#0001
void    ◼️ [email protected]

If we're not already friends on the internet, the risk is high that I'll miss your message.

About

πŸš€ A boilerplate repo with some nice-to-have features and sensible standards for building smart contracts in Solidity using Foundry.

Topics

Resources

License

Stars

Watchers

Forks