VelvetShark

Solidity Smart Contract Template

Published on

Home of the Jetsons (The Jetsons) by Mark Bennett

When you start writing smart contracts that are a step or two beyond simplistic, tutorial-level code, you quickly have more code than can easily fit on a screen, and with each new function or added variable, there's more and more need for structure and for easy way to know exactly what to put where, and how.

Importance of a proper contract layout

  • Readability: Makes the code understandable for developers and auditors.
  • Maintainability: Facilitates easy updates and bug fixes, both by you and by whoever uses your code.
  • Efficiency: Properly structured contracts can lead to optimized gas usage.
  • Security: Reduces the risk of vulnerabilities due to code complexity or ambiguity.
  • Reusability: Makes it easy to reuse certain components or modules in different projects.
  • Education: Good-quality code teaches best practices whoever reads the code you have written.

What you need to consider

When writing smart contracts in Solidity, you need to take many things into account. Here are just a few:

  • Style guide - general conventions for writing Solidity code, changing over time as useful conventions are found and old conventions are rendered obsolete
  • Solidity file elements - what goes where in a Solidity file
  • Contract elements - the order of elements inside a contract, an interface, or a library
  • Function grouping - the order of functions based on their types
  • Naming convention - how to name functions, variables, contracts so that it's easier to see what they do and where they belong

Solidity contract starter template

To make it easier, so that you don't need to remember everything, I have created a Solidity contract starter template, with all the best practices, conventions, recommendations, good layout, and proper structure.

You can reference it as a cheatsheet or use it as a better alternative for an empty file. In it, you'll find all the placeholders telling you what goes where. You'll only need to fill it with your code.

Because style guides change and the Solidity language itself also changes, the conventions are not set in stone, so I'm creating this template also as a GitHub repository, where you can add issues and create PRs. Let's build the best starter template, together.

Solidity smart contract template with explanations

/*
├── Pragma (version)
├── Imports
├── Events
├── Errors
├── Interfaces
├── Libraries
└── Contracts
├── Type declarations
├── State variables
├── Events
├── Errors
├── Modifiers
└── Functions
├── Constructor
├── Receive function (if exists)
├── Fallback function (if exists)
├── External
├── Public
├── Internal
└── Private
*/

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.16 <0.9.0; // ^0.8.16;

// Import other contracts
import "./Ownable.sol"; // Import from local
import "@openzeppelin/contracts/access/Ownable.sol"; // Import from npm
import "github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol"; // Import from GitHub

// Events
event Change(string message, uint newVal);

// Errors
error NotEnoughBalance(uint requested, uint available);

// Interfaces
interface IERC20 {
function transfer(address recipient, uint256 amount) external returns (bool);
}

// Libraries
library SquareLib {
function square(uint a) internal pure returns (uint) {
return a * a;
}
}

// Contract

/// @title Solidity contract template
/// @author Radek Sienkiewicz
/// @notice You can use this contract for quickly and easily building your layout and structure
/// @dev Explain to a developer any extra details here (e.g. "The functions are for illustration purposes only")
contract SimpleContract is Ownable {
// Type declarations
struct Person {
string name;
uint age;
}

// State variables
uint storedData;
address owner;
mapping(address => uint) balances;
uint startingBalance = 1_000_000_000_000_000_000; // Underscores are commonly used in Solidity to improve readability of large numbers. Much easier to read than 1000000000000000000.

// Events
event Change(string message, uint newVal);

// Errors
error NotEnoughBalance(uint requested, uint available);

// Modifiers
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function.");
_;
}

/* *** Functions *** */

// Constructor
constructor(uint initVal) {
storedData = initVal;
}

// Receive function
receive() external payable {
// ...
}

// Fallback function
fallback() external payable {
// ...
}

// External
function externalFunc() external {
// ...
}

function externalViewFunc() external view { // Within a grouping of functions (public, external, internal, private), put view and pure functions last
// ...
}

// A function with a full NatSpec comment
// It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI).
// All tags in NatSpec are optional. Use what makes sense for your contract.

/// @notice Describe what the function does
/// @dev Add any extra details for developers here
/// @param a Describe a parameter
/// @param b Describe a parameter
/// @return What does it return? (e.g. "The sum of a and b")
function publicFuncWithNatSpec(uint a, uint b) external pure returns (uint) {
return a + b;
}

function externalPureFunc() external pure {
// ...
}

// Public
function publicFunc() public {
// ...
emit Change("Changed!", value); // Triggering event
}


// Internal
function internalFunc() internal {
// ...
}

// Private
function privateFunc() private {
// ...
}
}

Here you can find the code in GitHub: Solidity Smart Contract Template repository - GitHub You can go ahead and give it a ⭐.

You can refer to this template as a cheatsheet or use it as a better alternative for an empty file. In it, you'll find all the placeholders telling you what goes where.

The only thing left to do is to fill it with your code.

References and further reading