https://github.com/adnpark/solidity-cheatsheet
master cheatsheet for solidity developers
https://github.com/adnpark/solidity-cheatsheet
cheatsheet contract-solidity ethereum resources solidity solidity-cheatsheet solidity-codes solidity-compiler solidity-contract solidity-contracts solidity-coverage solidity-dapps solidity-language solidity-security web3 web3-dapp web3-solidity
Last synced: about 1 month ago
JSON representation
master cheatsheet for solidity developers
- Host: GitHub
- URL: https://github.com/adnpark/solidity-cheatsheet
- Owner: adnpark
- Created: 2024-12-27T08:39:57.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2025-01-28T07:01:17.000Z (4 months ago)
- Last Synced: 2025-03-29T17:51:11.277Z (about 2 months ago)
- Topics: cheatsheet, contract-solidity, ethereum, resources, solidity, solidity-cheatsheet, solidity-codes, solidity-compiler, solidity-contract, solidity-contracts, solidity-coverage, solidity-dapps, solidity-language, solidity-security, web3, web3-dapp, web3-solidity
- Homepage:
- Size: 1.43 MB
- Stars: 10
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Security: security.md
Awesome Lists containing this project
README
# Solidity Master Cheatsheet
Welcome to the Solidity Master Cheatsheet—created especially for new Solidity developers! Whether you’re just starting to explore the fundamentals of smart contract programming or need a convenient reference while building your DApps, this guide has you covered.
_This cheatsheet is based on version 0.8.29_
# Table of Contents
- [Solidity Master Cheatsheet](#solidity-master-cheatsheet)
- [Table of Contents](#table-of-contents)
- [Getting Started](#getting-started)
- [Specifying compiler version](#specifying-compiler-version)
- [Basic Data Types](#basic-data-types)
- [Summary](#summary)
- [`bool`](#bool)
- [Integers: `uint` and `int`](#integers-uint-and-int)
- [`address` and `address payable`](#address-and-address-payable)
- [`bytes` and `bytesN`](#bytes-and-bytesn)
- [`string`](#string)
- [Variables \& Visibility](#variables--visibility)
- [State Variables](#state-variables)
- [Constants](#constants)
- [Immutable Variables](#immutable-variables)
- [Local Variables](#local-variables)
- [Global (Built-in) Variables](#global-built-in-variables)
- [Visibility Keywords](#visibility-keywords)
- [Visibility Accessible By Common Use Cases](#visibility-accessible-by-common-use-cases)
- [`public`](#public)
- [`external`](#external)
- [`internal`](#internal)
- [`private`](#private)
- [Best Practices for Visibility](#best-practices-for-visibility)
- [Functions](#functions)
- [Basic Syntax](#basic-syntax)
- [Visibility](#visibility)
- [State Mutability: view, pure, and payable](#state-mutability-view-pure-and-payable)
- [Return Values](#return-values)
- [Function Parameters and Data Location](#function-parameters-and-data-location)
- [Overloading and Overriding](#overloading-and-overriding)
- [Internal vs External Calls](#internal-vs-external-calls)
- [Gas Considerations](#gas-considerations)
- [Best Practices for Functions](#best-practices-for-functions)
- [Control Flow](#control-flow)
- [If / Else Statements](#if--else-statements)
- [`require`](#require)
- [For Loops](#for-loops)
- [While Loops](#while-loops)
- [Do-While Loops](#do-while-loops)
- [Break and Continue](#break-and-continue)
- [Best Practices for Loops](#best-practices-for-loops)
- [Vanila Loop](#vanila-loop)
- [Array Loop](#array-loop)
- [Error Handling](#error-handling)
- [`require` vs `revert` vs `assert`](#require-vs-revert-vs-assert)
- [`require(condition, "Error Message")`](#requirecondition-error-message)
- [`revert("Error Message")`](#reverterror-message)
- [`assert(condition)`](#assertcondition)
- [Key Points:](#key-points)
- [Custom Errors (\>=0.8.4)](#custom-errors-084)
- [Benefits:](#benefits)
- [`try/catch` for External Calls](#trycatch-for-external-calls)
- [Best Practices for Error Handling](#best-practices-for-error-handling)
- [Arrays, Mappings, Structs, Enums](#arrays-mappings-structs-enums)
- [Arrays](#arrays)
- [Fixed-size Arrays](#fixed-size-arrays)
- [Dynamic Arrays](#dynamic-arrays)
- [Declaring and Using Arrays](#declaring-and-using-arrays)
- [Storage vs. Memory](#storage-vs-memory)
- [Accessing Elements](#accessing-elements)
- [Length](#length)
- [Push and Pop (Dynamic Arrays in Storage)](#push-and-pop-dynamic-arrays-in-storage)
- [Gas Considerations:](#gas-considerations-1)
- [Mappings](#mappings)
- [Structs](#structs)
- [User Defined Value Types](#user-defined-value-types)
- [Motivation](#motivation)
- [Syntax](#syntax)
- [Wrapping and Unwrapping](#wrapping-and-unwrapping)
- [Operations and Conversions](#operations-and-conversions)
- [Enums](#enums)
- [Enum Advantages:](#enum-advantages)
- [Best Practices and Tips](#best-practices-and-tips)
- [Modifiers](#modifiers)
- [Syntax](#syntax-1)
- [Anatomy of a Modifier](#anatomy-of-a-modifier)
- [Multiple Modifiers on One Function](#multiple-modifiers-on-one-function)
- [Events](#events)
- [Key Characteristics](#key-characteristics)
- [Declaring Events](#declaring-events)
- [Emitting Events](#emitting-events)
- [Viewing Events Off-Chain](#viewing-events-off-chain)
- [Indexed vs. Non-Indexed Parameters](#indexed-vs-non-indexed-parameters)
- [Common Use Cases](#common-use-cases)
- [Simple Example](#simple-example)
- [Best Practices](#best-practices)
- [Contract Inheritance \& Interfaces](#contract-inheritance--interfaces)
- [Contract Inheritance](#contract-inheritance)
- [Single Inheritance](#single-inheritance)
- [Multiple Inheritance](#multiple-inheritance)
- [Overriding Functions](#overriding-functions)
- [Constructors in Inheritance](#constructors-in-inheritance)
- [Interfaces](#interfaces)
- [Implementing an Interface](#implementing-an-interface)
- [Using Interfaces](#using-interfaces)
- [Combining Inheritance and Interfaces](#combining-inheritance-and-interfaces)
- [Abstract Contracts](#abstract-contracts)
- [Diamond Inheritance and the “Linearization of Base Contracts”](#diamond-inheritance-and-the-linearization-of-base-contracts)
- [Best Practices](#best-practices-1)
- [Libraries](#libraries)
- [Key Characteristics of Libraries](#key-characteristics-of-libraries)
- [Library Function Types](#library-function-types)
- [Example: Internal Library](#example-internal-library)
- [Example: External Library](#example-external-library)
- [Library for Struct Extensions](#library-for-struct-extensions)
- [Best Practices](#best-practices-2)
- [Payable, Fallback, and Receive](#payable-fallback-and-receive)
- [`payable` Keyword](#payable-keyword)
- [`receive()` Function](#receive-function)
- [`fallback()` Function](#fallback-function)
- [Best Practices](#best-practices-3)
- [Data Locations: `storage`, `memory`, `calldata`](#data-locations-storage-memory-calldata)
- [`storage`: Persistent, On-Chain Data](#storage-persistent-on-chain-data)
- [`memory`: Temporary, In-Function Workspace](#memory-temporary-in-function-workspace)
- [`calldata`: Read-Only External Input](#calldata-read-only-external-input)
- [Best Practices](#best-practices-4)
- [Transient Storage](#transient-storage)
- [Sending Ether](#sending-ether)
- [Overview](#overview)
- [transfer](#transfer)
- [send](#send)
- [call](#call)
- [Best Practices](#best-practices-5)
- [Function Selector](#function-selector)
- [How is the Function Selector Computed?](#how-is-the-function-selector-computed)
- [Layout of Calldata](#layout-of-calldata)
- [Function Overloading and Selectors](#function-overloading-and-selectors)
- [Collision Issues](#collision-issues)
- [Call \& Delegatecall](#call--delegatecall)
- [Introduction to Low-Level Calls](#introduction-to-low-level-calls)
- [Why Use Low-Level Calls?](#why-use-low-level-calls)
- [`call`](#call-1)
- [Behavior](#behavior)
- [`delegatecall`](#delegatecall)
- [Behavior](#behavior-1)
- [`staticcall`](#staticcall)
- [Comparison](#comparison)
- [Security \& Best Practices](#security--best-practices)
- [CREATE, CREATE2, Create3, and CreateX](#create-create2-create3-and-createx)
- [CREATE](#create)
- [CREATE2](#create2)
- [Create3](#create3)
- [Comparison](#comparison-1)
- [CreateX](#createx)
- [ABI Encode \& Decode](#abi-encode--decode)
- [References](#references)# Getting Started
```solidity
// SPDX-License-Identifier: MIT
// Filename: HelloWorld.sol
// The function `greet()` will return the message "Hello, Solidity!"
pragma solidity ^0.8.29;contract HelloWorld {
string public message = "Hello, Solidity!";function greet() external view returns (string memory) {
return message;
}
}
```# Specifying compiler version
```solidity
pragma solidity 0.8.29; // The contract must be compiled with exactly version 0.8.29pragma solidity ^0.8.29; // Any version greater than or equal to 0.8.29, but strictly less than 0.9.0
pragma solidity >=0.8.0 <0.9.0; // Any version greater than or equal to 0.8.0, but strictly less than 0.9.0
```**Best Practice**
- When releasing production contracts, you may want to pin to a narrower range or exact version for complete determinism.
- During development, using ^0.8.x can be more convenient.# Basic Data Types
## Summary
| Type | Description | Example |
| ------------------- | ---------------------------------------------------------- | ----------------------------------------------- |
| **bool** | Boolean type, true or false | `bool isReady = true;` |
| **uint** | Unsigned integer (by default 256 bits, i.e. `uint256`) | `uint256 count = 10;` |
| **int** | Signed integer (by default 256 bits, i.e. `int256`) | `int256 temperature = -5;` |
| **address** | 20-byte Ethereum address (e.g. `0xABC123...`), non-payable | `address owner = msg.sender;` |
| **address payable** | Same as address, but can receive Ether | `address payable wallet = payable(msg.sender);` |
| **bytes** | Dynamically sized byte array | `bytes data = hex"001122";` |
| **bytesN** | Fixed-size byte array of length N (1 ≤ N ≤ 32) | `bytes32 hash = keccak256(...);` |
| **string** | Dynamically sized UTF-8 data | `string name = "Alice";` |## `bool`
- Stores a single bit of information (true or false).
- Default value is false if not initialized.## Integers: `uint` and `int`
- uint stands for unsigned integer and does not allow negative values.
- Range for uint256 is 0 to 2^256 - 1.
- You can also specify smaller sizes like uint8, uint16, ..., uint256 (increments of 8 bits).
- int stands for signed integer and allows negative values.
- Range for int256 is -(2^255) to (2^255 - 1).
- Similarly, you can specify int8, int16, ..., int256.
- Default value for both uint256 and int256 is 0.```solidity
// Unsigned integer
uint256 public totalSupply = 10000;
// Signed integer
int256 public temperature = -25;
```**Best Practice**
- Use uint256 unless you have a specific reason to use a smaller size (like uint128, etc.).
- Smaller types can save storage (gas) if you can tightly pack multiple variables in a struct or the same storage slot.
- Avoid using signed integers if you only deal with non-negative values.## `address` and `address payable`
- An address type holds a 20-byte value.
- address has built-in attributes like:
- balance (returns the account’s balance in wei),
- code and codehash (get contract code or its hash),
- and methods like call, delegatecall, staticcall (low-level).
- address payable is a special variant that allows sending Ether via methods like transfer or send.**Important**
In Solidity ^0.8.0, you must explicitly convert an address to address payable if you want to send Ether:
```solidity
address payable receiver = payable(someAddress);
```## `bytes` and `bytesN`
bytes: dynamically sized array of bytes.
- Good for arbitrary-length binary data.
- More expensive to store than fixed-size arrays due to dynamic nature.bytesN: fixed-size array of length N (where 1 <= N <= 32).
- Commonly used for storing hashes (bytes32) or other fixed-length data (e.g., signatures).
- bytes32 is a popular choice for storing keccak256 hashes.```solidity
// Dynamically sized
bytes public data = hex"DEADBEEF";// Fixed-size, exactly 32 bytes
bytes32 public myHash = keccak256(abi.encodePacked("Solidity"));
```## `string`
- A dynamically sized UTF-8 encoded data type typically used for text.
- In practice, string is very similar to bytes (both are dynamically sized), but string is meant for text, while bytes is better for raw binary data.
- Default value is an empty string "".```solidity
string public greeting = "Hello, World!";
```# Variables & Visibility
In Solidity, variables are categorized based on **where** they are declared and **how** they can be accessed:
1. State Variables
2. Local Variables
3. Global (Built-in) Variables
4. Visibility Keywords## State Variables
- **Declared inside** a contract but **outside** of any function.
- **Stored permanently** on the blockchain as part of the contract’s state (in storage).
- **Gas cost**: Writing and updating state variables costs gas. Reading is cheaper but not free.
- **Initialization**: If not explicitly initialized, they are given default values (e.g., 0 for integers, false for booleans, address(0) for addresses).```solidity
pragma solidity ^0.8.29;contract MyContract {
// State variables
uint256 public count; // defaults to 0
bool public isActive = true;// ...
}
```### Constants
- Constants are declared using the `constant` keyword.
```solidity
uint256 public constant constantVariable = 10;
```### Immutable Variables
- Immutable variables are declared using the `immutable` keyword.
- They are initialized once and cannot be changed after that.```solidity
uint256 public immutable immutableVariable = 10;
```**Best Practice**
- Mark state variables as `public` only if you need external read access.
- Use `private` or `internal` for variables that should not be directly accessible outside the contract.
- Use `immutable` for variables that should not be changed after initialization.## Local Variables
- **Declared and used within function scope** (including function parameters).
- **Stored in memory or stack**, not in contract storage (unless explicitly specified otherwise).
- **Cheaper than state variables** because they’re only used temporarily during function execution.```solidity
function multiplyByTwo(uint256 _x) public pure returns (uint256) {
// Local variable
uint256 result = _x * 2;
return result; // This value is not saved on-chain
}
```**Note**
- Local variables are destroyed after the function call ends.
- For arrays, structs, or strings passed as function parameters, you often must specify memory or calldata (in external functions) to define the data location.## Global (Built-in) Variables
These are pre-defined variables and functions that give information about the blockchain, transaction, or message context. Examples include:
- `msg.sender` — the address that called the function.
- `msg.value` — how much Ether (in wei) was sent with the call.
- `msg.data` — the data sent with the call.
- `block.timestamp` — the current block timestamp (a.k.a. Unix epoch time).
- `block.number` — the current block number.
- `block.chainid` — the chain ID of the blockchain.
- `tx.gasprice` — the gas price of the transaction.These variables are read from the environment and cannot be directly overwritten. They do not require a declaration like normal variables.
For more global variables, see [here](https://docs.soliditylang.org/en/v0.8.29/units-and-global-variables.html#units-and-globally-available-variables).
Example:
```solidity
function whoCalledMe() public view returns (address) {
// msg.sender is a global variable
return msg.sender;
}
```## Visibility Keywords
In Solidity, visibility determines which parts of the contract or external entities can access a function or state variable.
### Visibility Accessible By Common Use Cases
| Visibility | Accessible By | Common Use Cases |
| ------------ | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
| **public** | - Externally (via transactions or other contracts)
- Internally within the contract itself | Functions/variables that need to be read or called externally |
| **external** | - Externally only (cannot be called internally without `this.`) | Functions intended solely for external interaction (e.g., an API for Dapp users) |
| **internal** | - Only within this contract or inheriting contracts | Helper functions and state variables used by derived contracts |
| **private** | - Only within this specific contract | Sensitive logic or state variables that shouldn't be accessed even by child contracts |**Note**
- `public` and `private` keywords are used to define the visibility of state variables and functions.
- `external` and `internal` keywords are used to define the visibility of functions only.### `public`
- A `public` state variable automatically generates a getter function. For example:
```solidity
uint256 public count;
```This allows reading `count` externally. The contract ABI will have a function `count()` that returns the variable’s value.
```solidity
function getCount() public view returns (uint256) {
return count;
}
```- A `public` function can be called from:
- Outside via a transaction or another contract
- Inside by other functions within the same contract### `external`
- Functions only callable from outside the contract (or via `this.functionName(...)`).
- Typically used to indicate a function is part of the contract’s external interface.
- Slightly more gas-efficient if you don’t plan to call that function from within the contract.```solidity
function externalFunction() external view returns (uint256) {
return address(this).balance;
}// Inside another function in the same contract, you'd have to call: (but not recommended)
this.externalFunction();
```### `internal`
- Accessible **only** within the contract and child contracts that inherit from it.
- Not part of the public ABI, so cannot be called externally.
- Useful for shared logic across parent-child relationships.```solidity
function internalHelper() internal pure returns (uint256) {
return 42;
}
```### `private`
- **Only** accessible within the same contract.
- Not accessible in derived contracts.
- Typically used for sensitive or low-level logic that you don’t want child contracts to override or manipulate directly.```solidity
bool private privateVariable = true;function privateHelper() private pure returns (uint256) {
return 123;
}
```## Best Practices for Visibility
1. Explicitly Specify Visibility
- In Solidity, the default function visibility is internal if not specified.
- Always define visibility (public, external, internal, private) for every function and state variable to avoid confusion and ensure clarity in your code.2. Use external for Functions Called Externally Only
- If a function is never intended to be called internally, mark it external.
- external functions can be slightly more gas-efficient than public because Solidity handles arguments differently for external calls.3. Restrict Access Whenever Possible
- Follow the principle of least privilege.
- Use private or internal whenever you don’t need external or inherited access.
- This minimizes the contract’s attack surface and reduces the likelihood of unintended behavior.# Functions
Solidity functions define the behavior of your smart contract. They can be used to read or modify the contract’s state, interact with other contracts, or perform computations.
## Basic Syntax
```solidity
function functionName(Type param1, Type param2) [visibility] [stateMutability] returns (ReturnType) {
// function body
}
```Where:
- `functionName` is the identifier (name) of the function.
- `param1, param2` are parameters with specified types.
- `visibility` can be `public`, `external`, `internal`, or `private`.
- `stateMutability` includes `view`, `pure`, `payable`, or can be omitted if the function modifies state.
- `returns (ReturnType)` specifies the output type(s) (can be multiple).## Visibility
As covered in [Visibility Keywords](#visibility-keywords), a function’s visibility determines who can call it. The most common visibilities for functions are:
- `public`: callable from outside and inside the contract
- `external`: callable from outside only (or via `this.functionName()` inside)
- `internal`: callable only inside this contract and derived contracts
- `private`: callable only inside this contract# State Mutability: view, pure, and payable
1. `view`
- The function can read state variables but cannot modify them.
```solidity
function getCount() public view returns (uint256) {
return count; // reading a state variable
}
```2. `pure`
- The function cannot read or modify state variables (nor use `this.balance` or `block.number` etc.).
- Ideal for pure math or utility functions.```solidity
function addNumbers(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
```3. `payable`
- The function can accept Ether sent to it.
- Without `payable`, the function will reject any Ether transfer.```solidity
function deposit() public payable {}
```## Return Values
You can return one or more values from a function. There are multiple ways to do so:
1. Return Single Value
```solidity
function getNumber() public pure returns (uint256) {
return 42;
}
```2. Return Multiple Values
```solidity
function getValues() public pure returns (uint256, bool) {
return (100, true);
}
```3. Named Returns
- You can name your return variables for clarity.
```solidity
function namedReturn() public pure returns (uint256 count, bool status) {
count = 10;
status = true;
}
```- This can sometimes make the code more readable but is optional.
## Function Parameters and Data Location
For parameters of reference types (e.g., `string`, `bytes`, `arrays`, `structs`), you must specify the data location (memory, storage, or calldata):
- `memory`: non-persistent, used for local copies within a function.
- `calldata`: non-persistent, immutable/read-only, only used in **external** function parameters. It’s more gas-efficient than memory for external calls because it doesn’t copy the data.
- `storage`: persists in the contract’s state storage (rarely used as a parameter location, mostly used for state variables). Can be used for internal or private function if you want to pass a reference to an existing storage variable.Example using calldata:
```solidity
function concatStrings(
string calldata str1,
string calldata str2
) external pure returns (string memory) {
return string(abi.encodePacked(str1, str2));
}
```## Overloading and Overriding
- **Overloading**: Defining multiple functions with the same name but different parameters.
```solidity
function setValue(uint256 _value) public {
// ...
}function setValue(uint256 _value, bool _flag) public {
// ...
}
```- **Overriding**: When a function in a child contract overrides a function from a parent contract.
- The parent function must be marked as `virtual`.
- The child function must use `override`.```solidity
contract Parent {
function greet() public virtual pure returns (string memory) {
return "Hello from Parent";
}
}contract Child is Parent {
function greet() public pure override returns (string memory) {
return "Hello from Child";
}
}
```## Internal vs External Calls
- When you call a function internally in Solidity, it uses jump instructions without creating a new message call. This is more gas-efficient.
- When you call an external function from within the same contract (e.g., this.myExternalFunction()), it triggers a new contract call. This is less gas-efficient and changes msg.sender to the contract itself.## Gas Considerations
1. Function Complexity:
- Avoid excessive loops or large data copy operations within a single function.
- If possible, break down large operations into smaller functions or use off-chain solutions for heavy computations.2. Function Parameters:
- For external functions, using calldata for parameters instead of memory is cheaper in many cases.
- Passing large arrays around increases gas due to data copy overhead.## Best Practices for Functions
- Use public or external only if the function needs to be called from outside. Otherwise, consider internal or private.
- Separate reading (view/pure) and state-changing functions for clarity and potential performance benefits.
- Overloading can be handy, but use it sparingly; it can cause confusion if the parameter differences are subtle.
- Overriding is essential for inheritance hierarchies. Make sure to mark parent functions as virtual and child overrides as override.# Control Flow
Control flow in Solidity is largely similar to other languages like JavaScript, C, or Python. You can use if/else, for, while, and do-while loops to direct program execution.
## If / Else Statements
Syntax:
```solidity
function checkValue(uint256 _x) public pure returns (string memory) {
if (_x > 100) {
return "Greater than 100";
} else if (_x == 100) {
return "Exactly 100";
} else {
return "Less than 100";
}
}
```## `require`
`require` statements revert the transaction immediately if a condition is not met:
```solidity
require(condition, "Error message");
```This is often used for input validation or access control checks.
## For Loops
```solidity
function sumArray(uint256[] memory _arr) public pure returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < _arr.length; i++) {
total += _arr[i];
}
return total;
}
```## While Loops
```solidity
function decrement(uint256 _x) public pure returns (uint256) {
while (_x > 0) {
_x--;
}
return _x; // returns 0
}
```## Do-While Loops
Solidity also supports do-while loops, which execute the loop body at least once before checking the condition:
```solidity
function doWhileExample(uint256 _x) public pure returns (uint256) {
uint256 counter = _x;do {
counter--;
} while (counter > 0);return counter;
}
```## Break and Continue
Like many languages, Solidity provides `break` and `continue` statements for early termination or skipping an iteration in loops.
- `break`: Exits the loop immediately.
- `continue`: Skips the remaining statements in the current iteration and moves to the next iteration.```solidity
function loopWithBreak(uint256 _x) public pure returns (uint256) {
for (uint256 i = 0; i < _x; i++) {
if (i == 5) break; // exits loop when i equals 5
if (i == 3) continue; // skips remaining statements when i equals 3
// ...
}
}
```## Best Practices for Loops
### Vanila Loop
**Don't use >= and <= in the condition**
```solidity
// 37975 gas (+347)
function loop_lte() public returns (uint256 sum) {
for(uint256 n = 0; n <= 99; n++) {
sum += n;
}
}
```- The EVM has the opcodes LT, GT and EQ for comparison, but there are no convenient LTE or GTE opcodes for the operation we are doing.
- Therefore each time the condition n <= 99 is checked, 3 instructions must be executed: LT n 99,EQ n 99 and OR to check if either one of those returned true, which leads to extra gas cost.**Increment the variable in an unchecked block**
```solidity
// 32343 gas (-5285)
function loop_unchecked_plusplus() public returns (uint256 sum) {
for(uint256 n = 0; n < 100;) {
sum += n;
unchecked {
n++;
}
}
}
```- Since version 0.8 Solidity implements safety checks for all integer arithmetic, including overflow and underflow guards
- `n++` Solidity will insert extra code to handle the case if `n` would overflow after incrementing it
- We can skip the overflow check by using `unchecked` block
- Use only if you are sure the variables will never overflow**Just write it in assembly**
```solidity
// 26450 gas (-11178)
function loop_assembly() public returns (uint256) {
assembly {
let sum := 0
for {let n := 0} lt(n, 100) {n := add(n, 1)} {
sum := add(sum, n)
}
mstore(0, sum)
return(0, 32)
}
}
```- Removed the declaration uint256 sum from the function header in order to escape Solidity's type system as much as possible
- Using assembly is the most gas-efficient way to write a loop, but it's also the most complex and harder to read and maintain. So make sure to use it only when necessary.### Array Loop
**"Cache" the array's length for the loop condition**
```solidity
// 25182 gas (-230)
function loopArray_cached(uint256[] calldata ns) public returns (uint256 sum) {
uint256 length = ns.length;
for(uint256 i = 0; i < length;) {
sum += ns[i];
unchecked {
i++;
}
}
}
```- We know the length won't change during execution and we can reduce the number of ns.length calls to just 1 for a modest reduction in gas.
# Error Handling
## `require` vs `revert` vs `assert`
### `require(condition, "Error Message")`
- Verifies **input conditions** or **other external conditions**.
- Commonly used at the start of a function to validate function inputs, access control, or other preconditions (e.g., ensuring msg.value is sufficient).```solidity
require(msg.sender == owner, "Not the owner");
require(amount > 0, "Amount must be greater than 0");
```- If the condition fails, it reverts with the provided error message.
-### `revert("Error Message")`
- **Explicitly** trigger a revert from any part of the code.
- Often used to handle more complex logic or custom flows.```solidity
if (balance < amount) {
revert("Insufficient balance to withdraw");
}
```### `assert(condition)`
- Used to check for internal errors and invariants that should never fail.
- If an assert fails, it indicates a bug in your code (e.g., an invariant has been violated).
- From Solidity 0.8.x onwards, failing assert also reverts the transaction but uses a different kind of error—often highlighting a critical code issue.```solidity
assert(totalSupply == sumOfAllBalances);
```### Key Points:
- `require` is for **invalid user inputs** or **external conditions**.
- `revert` is a **manual** revert trigger.
- `assert` is for **internal invariants**—if it fails, something is fundamentally wrong.## Custom Errors (>=0.8.4)
- Custom errors allow you to define error types with optional parameters.
- They are more **gas-efficient** than revert strings because the encoded error data is smaller.```solidity
error Unauthorized(address caller);
error InvalidAmount(uint256 requested, uint256 available);function restrictedAction() public view {
if (msg.sender != owner) {
revert Unauthorized(msg.sender);
}
// ...
}function withdraw(uint256 amount) public {
if (amount > balances[msg.sender]) {
revert InvalidAmount(amount, balances[msg.sender]);
}
// ...
}
```### Benefits:
- Saves gas compared to long string messages.
- Provides typed parameters, making debugging easier off-chain.
- Especially useful in large or complex contracts.## `try/catch` for External Calls
- When calling **external** functions or doing contract creations, you can use `try/catch` to handle failures without reverting your entire function:
```solidity
interface DataFeed { function getData(address token) external returns (uint value); }contract FeedConsumer {
DataFeed feed;
uint errorCount;
function rate(address token) public returns (uint value, bool success) {
// Permanently disable the mechanism if there are
// more than 10 errors.
require(errorCount < 10);
try feed.getData(token) returns (uint v) {
return (v, true);
} catch Error(string memory /*reason*/) {
// This is executed in case
// revert was called inside getData
// and a reason string was provided.
errorCount++;
return (0, false);
} catch Panic(uint /*errorCode*/) {
// This is executed in case of a panic,
// i.e. a serious error like division by zero
// or overflow. The error code can be used
// to determine the kind of error.
errorCount++;
return (0, false);
} catch (bytes memory /*lowLevelData*/) {
// This is executed in case revert() was used.
errorCount++;
return (0, false);
}
}
}
```- You can choose to handle or re-revert. This can be used to do partial rollbacks or alternate logic.
## Best Practices for Error Handling
- **Use require for Input Validation**
- Validate function arguments, check `msg.value`, verify permissions, etc.
- **Keep Revert Messages Short**
- Revert strings add to the bytecode size and runtime cost.
- If you need to pass detailed data, consider custom errors.
- **Use custom errors for More Complex Logic**
- Save gas and convey structured data (like addresses, amounts) in the revert reason.
- **Fail Fast**
- Place `require` checks at the top of your functions to prevent unnecessary computations.# Arrays, Mappings, Structs, Enums
- Arrays: Sequential lists of elements of the same type.
- Mappings: Key-value data structure that does not store keys (only values).
- Structs: Custom types that group different data fields together.
- Enums: Defines a finite set of possible states.## Arrays
### Fixed-size Arrays
- Their size must be known at compile time.
- You cannot change their length afterward.```solidity
uint256[3] public fixedArray = [1, 2, 3];
```### Dynamic Arrays
- Size can be modified at runtime using built-in methods like push and pop.
- Declared with empty brackets: `uint256[]`.```solidity
uint256[] public dynamicArray;
```### Declaring and Using Arrays
#### Storage vs. Memory
- State arrays (declared at the contract level) live in **storage** (on-chain).
- Function parameters and local arrays can be declared in **memory** or **calldata** (for external functions).
- `calldata` is read-only and more gas-efficient for external function parameters.#### Accessing Elements
```solidity
uint256[] public numbers;function addNumber(uint256 num) external {
numbers.push(num); // dynamic array
}function getNumber(uint256 index) external view returns (uint256) {
return numbers[index];
}
```#### Length
- For a dynamic array, `arrayName.length` gives the current length.
- You can also set a new length (in storage arrays) by assigning: `numbers.length = newLength;` (this can truncate or extend the array).#### Push and Pop (Dynamic Arrays in Storage)
- `push(value)` appends a new element at the end.
- `pop()` removes the last element (reducing the array length by one).
- Time complexity of `push` and `pop` is O(1).```solidity
function removeLast() external {
numbers.pop();
}
```#### Gas Considerations:
- Each push, pop, and indexing operation in storage arrays incurs gas costs.
- Minimizing on-chain array size or using more efficient data structures can be crucial for large data sets.## Mappings
- A mapping is a reference type that stores key-value pairs.
- Mappings in Solidity:
- **Do not allow iteration** over keys.
- **Do not store** or keep track of the keys themselves—only the values and their hashed keys.
- Are often used for **fast lookups** (constant time access).```solidity
mapping(address => uint256) public balances;function deposit() external payable {
balances[msg.sender] += msg.value;
}
```- **KeyType**: Usually a built-in type like `address`, `uint256`, or `bytes32`. (Structs, mappings, or dynamically-sized arrays cannot be used as keys.)
- **ValueType**: Can be any type, including another mapping or a struct.```solidity
// Nested mapping
mapping(address => mapping(address => uint256)) public allowance;function approve(address spender, uint256 amount) external {
allowance[msg.sender][spender] = amount;
}
```## Structs
- **Structs** let you define custom data types that group multiple variables (of possibly different types).
- They help in making code more readable and organizing complex data.```solidity
struct User {
string name;
uint256 age;
address wallet;
}User[] public users; // dynamic array of User structs
function createUser(string memory _name, uint256 _age) external {
// Option 1: Direct struct creation
User memory newUser = User(_name, _age, msg.sender);
users.push(newUser);// Option 2: Struct with named fields
users.push(User({name: _name, age: _age, wallet: msg.sender}));
}
```## User Defined Value Types
- User-Defined Value Types allow you to create a custom type name that wraps an existing built-in value type (like `uint256`, `int128`, `bytes32`, etc.).
- This feature was introduced in Solidity 0.8.8 and provides a way to give **semantic meaning** to a primitive type, helping catch logic errors and improving code readability.### Motivation
- **Type Safety & Readability**: By assigning a descriptive name to a built-in type, you can differentiate between, say, a `UserId` and a `Balance`, even if they’re both `uint256` under the hood.
- **Compile-Time Checks**: Conversions between different user-defined value types (and between the user-defined type and its underlying type) require **explicit** wrapping/unwrapping. This helps avoid mixing up incompatible values in your code.### Syntax
```solidity
type TypeName is UnderlyingType;
```- `TypeName`: The name of your new type (PascalCase by convention).
- `UnderlyingType`: Any built-in value type (e.g., `uint256`, `int128`, `bool`, `bytes32`, etc.).```solidity
type UserId is uint256;
type Price is uint128;
type Flag is bool;
```### Wrapping and Unwrapping
- Wrapping: Converting an underlying type to a user-defined type.
- Unwrapping: Converting a user-defined type back to its underlying type.```solidity
uint256 rawId = 123;
// Wrap a uint256 into a UserId
UserId userId = UserId.wrap(rawId);// Unwrap a UserId to a uint256
uint256 unwrappedId = UserId.unwrap(userId);// ❌ This will fail: Type uint256 is not implicitly convertible to UserId
UserId invalidConversion = 123;
```### Operations and Conversions
By default, **no** arithmetic or comparison operations are defined for user-defined value types. If you need to perform operations on your custom type, you can:
1. **Unwrap** your custom type, perform the operations on the underlying type, then **re-wrap** the result.
2. Define **inline** or **library** functions that handle the logic for your custom type.```solidity
pragma solidity ^0.8.29;type Distance is uint256;
library DistanceLib {
function add(Distance a, Distance b) internal pure returns (Distance) {
// unwrap, add, then re-wrap
return Distance.wrap(Distance.unwrap(a) + Distance.unwrap(b));
}function greaterThan(Distance a, Distance b) internal pure returns (bool) {
return Distance.unwrap(a) > Distance.unwrap(b);
}
}contract Road {
using DistanceLib for Distance;// store total length of roads
Distance public totalDistance;function addDistance(Distance d) external {
totalDistance = totalDistance.add(d);
// internally uses DistanceLib.add
}function compareDistances(Distance a, Distance b) external pure returns (bool) {
return a.greaterThan(b);
}
}```
## Enums
- Enums define a finite list of constant values, improving code clarity when dealing with limited states.
- Internally, enums map to uint8 by default (or the smallest fitting unsigned integer, though conceptually it can occupy a full storage slot). Each value corresponds to an index, starting at `0`.```solidity
enum Status {
Pending, // 0
Shipped, // 1
Delivered, // 2
Canceled // 3
}Status public currentStatus;
function setStatusShipped() external {
currentStatus = Status.Shipped;
}function cancelOrder() external {
currentStatus = Status.Canceled;
}function getStatus() external view returns (Status) {
return currentStatus;
}
```- **Converting Enums**:
- You can cast an integer to an enum if it’s a valid index.
- Example: `Status s = Status(1); // s == Status.Shipped`
- Be careful with casting, as invalid casts can lead to out-of-range enum values, which can corrupt state.### Enum Advantages:
- Makes state transitions self-documenting.
- Avoids using magic numbers (`0, 1, 2, 3`) or strings for statuses.
- Typically used for order states, workflow stages, or roles with limited states.## Best Practices and Tips
1. **Use Arrays for Ordered Collections**
- Great for sequential data like lists of user IDs.
- Remember that iterating over large arrays on-chain is costly.2. **Use Mappings for Fast Lookups**
- Ideal for key-value pairs like balances or allowances.
- You cannot iterate over a mapping, so maintain auxiliary data if you need a list of keys.3. **Structs for Grouped Data**
- Keep related data fields together.
- This improves readability and can reduce storage slot usage if packed correctly (e.g., storing small-size types in one slot).4. **Guard Against Invalid Enum Values**
- If you do type-casting from integers to enums, ensure you handle out-of-range values.
- In Solidity 0.8.x, you get a revert on overflow, but be explicit about checking valid indices if you do custom casting.5. **Avoid Large Arrays in Storage**
- If you expect an unbounded number of elements, consider a more efficient approach like indexing or partial off-chain storage.
- Or provide a mechanism to paginate through array elements or handle them in batches.6. **Be Aware of Storage Collisions**
- When dealing with nested data structures or inherited contracts, be sure you understand how Solidity’s storage layout works.
- Typically, using the standard approach (top-level declarations) avoids collisions.# Modifiers
- Modifiers are a powerful feature in Solidity that allow you to augment or condition the execution of functions.
- Think of them as wrappers around your functions that can run additional code (e.g., access checks, state validations) before (and/or after) the function body is executed.## Syntax
```solidity
modifier onlyOwner() {
require(msg.sender == owner, "Caller is not owner");
_;
}function sensitiveAction() public onlyOwner {
// This code runs after the `onlyOwner` checks
// ...
}
```1. When `sensitiveAction()` is called, Solidity first executes the code in the `onlyOwner` modifier.
2. If all checks (e.g., `require`) pass, it proceeds to execute the body of `sensitiveAction()`.
3. If any check fails, it reverts and never calls the function body.## Anatomy of a Modifier
- A modifier can contain code before and after the special `_` (underscore):
```solidity
modifier checkValue(uint256 _value) {
// Code executed before the function body
require(_value > 0, "Value must be greater than zero");_; // The function body is inserted here
// Code executed after the function body
emit ValueChecked(_value);
}function doSomething(uint256 amount) public checkValue(amount) {
// function body
}
```the compiled code effectively looks like:
```solidity
function doSomething(uint256 amount) public {
require(amount > 0, "Value must be greater than zero");// function body (original code of doSomething)
emit ValueChecked(amount);
}
```## Multiple Modifiers on One Function
- You can apply multiple modifiers to a single function. The modifiers will execute in order, left to right:
```solidity
modifier onlyOwner() { /* ... */ _; }
modifier whenNotPaused() { /* ... */ _; }function specialAction() public onlyOwner whenNotPaused {
// function body
}
```- Be mindful of the order, especially if the modifiers change state.
- Also ensure your modifiers don’t conflict or replicate the same checks unnecessarily.# Events
- Events in Solidity are mechanisms that allow smart contracts to emit logs during execution.
- These logs are stored on the blockchain as part of the transaction receipt, but do not directly consume contract storage.
- Off-chain applications (like DApps, block explorers, etc.) can listen for these events to react or update their interfaces.## Key Characteristics
- **Logs vs. Storage**: Events produce logs that are cheaper than storing data in contract state, because they’re kept in the transaction receipt.
- **Indexed Parameters**: You can mark up to **3 parameters** as indexed, enabling more efficient filtering on the client side.
- **Not Accessible On-Chain**: Event data (logs) are **not** accessible to other contracts. You can’t read events from within Solidity; they’re purely for off-chain usage.## Declaring Events
```solidity
event Transfer(address indexed from, address indexed to, uint256 value);
```- Up to three parameters can be labeled as `indexed`.
- `indexed` parameters are **indexed** in the event log, which makes them searchable by off-chain tools.## Emitting Events
```solidity
function transfer(address _to, uint256 _amount) external {
// ... perform transfer logic ...
emit Transfer(msg.sender, _to, _amount);
}
```## Viewing Events Off-Chain
- Web3 Libraries (Viem, ethers.js) allow you to listen for these events or query past events by searching transaction logs.
Example(in Viem):
```typescript
import { parseAbiItem } from "viem"
import { publicClient } from "./client"publicClient.watchEvent({
address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
event: parseAbiItem(
"event Transfer(address indexed from, address indexed to, uint256 value)"
),
args: {
from: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
to: "0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac"
},
onLogs: (logs) => console.log(logs)
})
```## Indexed vs. Non-Indexed Parameters
- Indexed parameters are **searchable** in the logs and stored as topics.
- Non-indexed parameters are stored in the log’s data section, which is not easily searchable but can be read once you retrieve the log.For example, in `event Transfer(address indexed from, address indexed to, uint256 value)`;:
- `from` => topic[1]
- `to` => topic[2]
- `value` => part of the data payload- You can filter logs by indexed topics like `from = 0x1234...abcd`, but you cannot directly filter by `value` because it’s not indexed.
```typescript
const filter = await publicClient.createContractEventFilter({
abi: wagmiAbi,
address: "0xfba3912ca04dd458c843e2ee08967fc04f3579c2",
eventName: "Transfer",
args: {
from: "0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
to: "0xa5cc3c03994db5b0d9a5eedd10cabab0813678ac"
}
})
```## Common Use Cases
1. Token Transfers
- ERC20 tokens emit a `Transfer` event whenever tokens move from one address to another.
2. State Changes
- Logging changes of ownership, updates to config variables, or changes in contract status.
3. Debugging
- Emitting certain events during testing can help you trace contract execution.
4. Off-Chain Processing
- Subgraphs (e.g., The Graph) or other indexing services use events to build databases of contract activity, enabling complex queries and analytics.## Simple Example
```solidity
pragma solidity ^0.8.21;contract Payment {
event Deposit(address indexed sender, uint256 amount);
event Withdraw(address indexed recipient, uint256 amount);function deposit() external payable {
require(msg.value > 0, "No ether sent");
emit Deposit(msg.sender, msg.value);
}function withdraw(uint256 amount) external {
require(amount > 0, "Zero withdrawal");
require(address(this).balance >= amount, "Insufficient contract balance");(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Withdraw failed");emit Withdraw(msg.sender, amount);
}
}
```## Best Practices
- Emit Events for Important State Changes
- Especially for actions that off-chain services or UIs need to track: token transfers, ownership changes, auctions ending, etc.
- Index Only What’s Needed
- Up to three indexed parameters can be used. Indexing more variables doesn’t exist (the rest must be unindexed).
- Too many indexed parameters or large data can increase log size and gas cost.- Avoid Emitting Too Many Events in a Single Transaction
- Each event adds to the gas cost. If you’re in a loop, consider alternative approaches or batch events if possible.
- Be Aware of Event Ordering
- The order in which you emit events is the order they appear in the logs. Off-chain listeners often rely on log order to interpret data.
# Contract Inheritance & Interfaces
- Solidity supports an **object-oriented programming** model where **contracts** can **inherit** from one or more base contracts.
- This allows for code reuse and hierarchical organization of functionality.
- **Interfaces** in Solidity define the required function signatures (and optionally events) without providing an implementation, allowing for standardized interaction between different contracts.## Contract Inheritance
Inheritance is achieved using the `is` keyword. A derived (child) contract can access and override the functions and state variables of its parent (base) contract(s).
### Single Inheritance
```solidity
contract Parent {
string public parentName = "Parent";function greet() public pure returns (string memory) {
return "Hello from Parent";
}
}contract Child is Parent {
function sayHello() public view returns (string memory) {
// Access parent's state variable
return parentName;
}
}
```- `Child` inherits everything from `Parent` except **constructor**, **private** state variables/functions, and **internal** data can only be accessed within `Child`.
### Multiple Inheritance
A contract can inherit from multiple base contracts using comma separation:
```solidity
contract A {
function foo() public pure returns (string memory) {
return "A";
}
}contract B {
function bar() public pure returns (string memory) {
return "B";
}
}// Child inherits both A and B
contract C is A, B {
// C now has both foo() from A and bar() from B
}
```### Overriding Functions
- Parent functions must be marked `virtual` to allow them to be overridden.
- Child overrides must use `override`.```solidity
contract Parent {
function greet() public pure virtual returns (string memory) {
return "Hello from Parent";
}
}contract Child is Parent {
// Overriding greet()
function greet() public pure override returns (string memory) {
return "Hello from Child";
}
}
```- If you have multiple levels (e.g., `Grandparent -> Parent -> Child`), and `Child` wants to override a function from `Grandparent`, you typically chain `override(Base, Grandparent)` if needed.
```solidity
contract Child is Parent, Grandparent {
function greet() public pure override(Parent, Grandparent) returns (string memory) {
return "Hello from Child";
}
}
```### Constructors in Inheritance
- If a parent contract has a **constructor**, the child must **explicitly** call it (unless it has a parameterless constructor).
- If multiple parents have constructors, each must be called with the needed arguments in the child’s constructor.```solidity
contract Parent1 {
uint256 public x;constructor(uint256 _x) {
x = _x;
}
}contract Parent2 {
uint256 public y;constructor(uint256 _y) {
y = _y;
}
}contract Child is Parent1, Parent2 {
// Must call Parent1,2 constructor
constructor(uint256 _childValue) Parent1(_childValue) Parent2(_childValue) {
// additional child init
}
}
```## Interfaces
An **interface** in Solidity is like a contract but with these restrictions:
1. **No** state variables or constructor definitions.
2. **All functions must be `external`** (or public in older versions of Solidity, but typically we use `external`).
3. **No** function implementations—**only signatures**.
4. Usually includes events that implementing contracts should emit.Purpose: They define a **standard** for other contracts to implement, ensuring compatibility without forcing an implementation approach.
```solidity
interface ICounter {
function increment() external;
function getCount() external view returns (uint256);
event Counted(address indexed caller, uint256 newCount);
}
```### Implementing an Interface
- A contract **implements** an interface by providing **all** the functions declared in the interface and matching their signatures exactly.
```solidity
contract Counter is ICounter {
uint256 private count;function increment() external override {
count++;
emit Counted(msg.sender, count);
}function getCount() external view override returns (uint256) {
return count;
}
}
```### Using Interfaces
Contracts can call interface functions to interact with external contracts that implement them:
```solidity
contract Caller {
function doIncrement(ICounter _counter) external {
_counter.increment();
}function readCount(ICounter _counter) external view returns (uint256) {
return _counter.getCount();
}
}
```### Combining Inheritance and Interfaces
You can mix inheritance and interfaces. For example, you could have a base abstract contract that implements some parts of an interface and leaves others for child contracts.
```solidity
interface IVault {
function deposit(uint256 amount) external;
function withdraw(uint256 amount) external;
}abstract contract VaultBase is IVault {
// partially implement deposit logic
// but keep withdraw abstract or add some logicfunction deposit(uint256 amount) external virtual override {
// partial logic
}
}contract MyVault is VaultBase {
// Must override deposit if not fully implemented
// Must implement withdraw
function deposit(uint256 amount) external override {
// full deposit logic
}function withdraw(uint256 amount) external override {
// implement withdraw logic
}
}
```- `VaultBase` is an abstract contract because it might not fully implement all methods of `IVault`.
- `MyVault` completes the implementation.### Abstract Contracts
**Abstract contracts** are those that cannot be deployed directly because they have at least one function without an implementation (`virtual` function). They serve as base contracts.
- Typically contain shared logic and placeholders for functions to be defined in child contracts.
- Marked with `abstract` keyword in Solidity 0.6.x+.```solidity
abstract contract Animal {
function speak() public virtual returns (string memory);
}contract Dog is Animal {
function speak() public pure override returns (string memory) {
return "Woof!";
}
}
```- `Animal` is abstract because `speak()` has no implementation.
- `Dog` provides an implementation for `speak()`.### Diamond Inheritance and the “Linearization of Base Contracts”
- Solidity uses the **C3 linearization** algorithm to resolve conflicts in multiple inheritance.
- If multiple parent contracts have the **same function** name, you must explicitly **override** and specify which base contract’s implementation to use.```solidity
contract A {
function foo() public pure virtual returns (string memory) {
return "A";
}
}contract B is A {
function foo() public pure virtual override returns (string memory) {
return "B";
}
}contract C is A {
function foo() public pure virtual override returns (string memory) {
return "C";
}
}// D inherits from both B and C
contract D is B, C {
// Must override foo() again
function foo() public pure override(B, C) returns (string memory) {
// decide which parent's implementation to call, or write new logic
// it will return "C"
return super.foo(); // picks the rightmost parent's override by default (C) in linearization
}
}
```- The `override(B, C)` explicitly states you are overriding `foo()` from both `B` and `C`.
- `super.foo()` calls the next-most derived override in the linearization.## Best Practices
- **Use Inheritance for Shared Logic**
- Common functionality (e.g., access control, utility functions) can be placed in base contracts for easy reuse.
- **Favor Composition over Deep Inheritance**
- Avoid extremely deep inheritance chains—they can become confusing and error-prone.
- Sometimes, libraries or composition patterns are clearer.- **Mark Functions as `virtual` and `override`**
- Always use `virtual` in the base contract to signal it can be overridden.
- Always use `override` in child contracts to signal you’re overriding a parent function.- **Use Interfaces for Inter-Contract Communication**
- Define an interface that external contracts must implement.
- This reduces dependencies, as your contract does not rely on the internal details of the other contract.- **Separate Interfaces into Their Own Files**
- This aids clarity and reusability.
- Keep your interface definitions minimal—only the required function signatures and events.- **Avoid Conflicting State in Multiple Inheritance**
- Overlapping storage layouts from multiple parents can cause confusion or even storage collisions.
- Ensure each parent contract uses distinct state variable slots (this usually happens naturally if you’re just inheriting standard contracts without complicated overrides).# Libraries
- In Solidity, **libraries** are special types of contracts intended to provide reusable code.
- They can be thought of as utility or helper modules that other contracts can call without needing to implement the same logic repeatedly.
- Libraries help keep code DRY (Don’t Repeat Yourself), save gas, and improve readability.## Key Characteristics of Libraries
- **No Ether**
- Libraries **cannot** hold Ether (i.e., they cannot have a balance).
- Any attempt to send Ether to a library will fail.- **No State Variables**
- Libraries cannot store or modify their own state; they are stateless.
- **No Inheritance**
- Libraries cannot be inherited or extend other contracts (nor can you inherit from a library).
- **Linked at Compile Time or Deployment Time**
- **Internal library functions** are typically inlined or statically called.
- **External library functions** can be deployed and then “linked” to your contract.## Library Function Types
Solidity libraries support **two** ways of using their functions:
- **Internal Functions**
- If a library function is declared `internal`, it is inlined into the calling contract’s bytecode at compile time (similar to macros).
- This reduces overhead since no external call is made.- **External Functions**
- If a library function is declared `public` or `external`, the calling contract will **delegatecall** into the library at runtime.
- This can save bytecode size in the calling contract but introduces a small overhead for each external call.
- You must **deploy** the library contract first and link it before deploying the final contract.### Example: Internal Library
Most libraries are used as **internal** because it’s more gas-efficient (no external call) and simpler to manage.
```solidity
// MathLib.sol
pragma solidity ^0.8.29;library MathLib {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
return a + b;
}function multiply(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b;
}
}// TestMath.sol
pragma solidity ^0.8.21;import "./MathLib.sol";
contract TestMath {
function testAdd(uint256 x, uint256 y) public pure returns (uint256) {
return MathLib.add(x, y);
}function testMultiply(uint256 x, uint256 y) public pure returns (uint256) {
return MathLib.multiply(x, y);
}
}
```- `MathLib.add` and `MathLib.multiply` are called from `TestMath` without an external call because they’re `internal` functions in a library.
- The compiler will inline or statically link these calls, increasing `TestMath`’s bytecode size, but saving on runtime gas costs.### Example: External Library
You can define a library with public or external functions. In that case, your contract calls the library via delegatecall at runtime.
```solidity
// ExternalLib.sol
pragma solidity ^0.8.29;library ExternalLib {
function externalAdd(uint256 a, uint256 b) external pure returns (uint256) {
return a + b;
}
}
```To use this library in another contract:
```solidity
pragma solidity ^0.8.29;import "./ExternalLib.sol";
contract UseExternalLib {
// The compiler inserts a reference that must be linked to the ExternalLib deployed address
function compute(uint256 x, uint256 y) public pure returns (uint256) {
return ExternalLib.externalAdd(x, y);
}
}
```- **Deployment**: You must first deploy `ExternalLib` and then link its address into `UseExternalLib`’s bytecode.
- At runtime, calls to `ExternalLib.externalAdd` happen via DELEGATECALL.## Library for Struct Extensions
- A popular pattern is to write libraries that extend built-in types or custom structs with `using for`.
- This adds **library functions** as if they were member functions of the type.```solidity
pragma solidity ^0.8.29;library ArrayUtils {
function findIndex(uint256[] storage arr, uint256 value) internal view returns (int256) {
for (uint256 i = 0; i < arr.length; i++) {
if (arr[i] == value) {
return int256(i);
}
}
return -1; // Not found
}
}contract MyArray {
using ArrayUtils for uint256[]; // "using for" directiveuint256[] private data;
function addValue(uint256 value) external {
data.push(value);
}function findValue(uint256 value) external view returns (int256) {
// We can now call findIndex() as if it's a member of data
return data.findIndex(value);
}
}
```- The `using ArrayUtils for uint256[];` directive allows you to call `data.findIndex(value)` directly.
- This improves readability and encapsulates utility logic for arrays.## Best Practices
- **Keep Libraries Focused**
- A library should do **one thing** well—e.g., math operations, array helpers, address utilities, etc.
- **Avoid Large External Libraries for Single Contracts**
- If you’re only using a library for a single contract and that library has few functions, it might be simpler and cheaper to make them internal.
- **Mark Functions `pure` or `view` Where Possible**
- Avoid unintentional state modifications in libraries unless that’s the library’s explicit purpose.
- This also ensures your library is safer and can be reasoned about more easily.- **`using for` Pattern**
- Great for readability and a more object-oriented feel.
- Clarifies which types are extended by the library.- **Security**
- Libraries can mutate the caller’s storage if called with `delegatecall`. Ensure your library code is trusted and thoroughly reviewed.
- **Solady, OpenZeppelin and Other Standard Libraries**
- Before writing your own, check if established libraries (e.g., Solady, OpenZeppelin) cover your needs. This reduces risk and leverages well-audited code.
# Payable, Fallback, and Receive
- Smart contracts in Solidity can receive Ether if they have specific functions set up.
- Before Solidity `0.6.x`, there was only a single fallback function.
- From `0.6.x` onward, Solidity introduced a split: a dedicated `receive()` function to handle plain Ether transfers, and a `fallback()` function to handle non-matching function calls (and optionally Ether if `receive()` is not found).## `payable` Keyword
1. `payable` is required for functions to receive Ether.
2. It can apply to constructors and functions.
3. If a function or constructor is not marked `payable`, it will reject any incoming Ether with a revert.```solidity
pragma solidity ^0.8.21;contract PayableExample {
// A payable constructor allows you to deploy the contract with initial Ether
constructor() payable {
// Contract can receive Ether on deployment
}// A payable function can receive Ether
function deposit() external payable {
// Funds are added to contract balance
}// This function is NOT payable, so it cannot receive Ether
function nonPayableFunction() external {
// ...
}
}
```- Attempting to send Ether (e.g., via `msg.value`) to `nonPayableFunction()` will revert.
- Conversely, calling `deposit()` without sending Ether is valid (but `msg.value` would be `0`).## `receive()` Function
- **Introduced** in Solidity `0.6.x` as a **special** function to receive plain Ether with no data (i.e., calls with an empty calldata).
- It can be declared `external payable` with **no** arguments and **no** return value.```solidity
receive() external payable {
// custom logic or leave it empty
}
```- If someone sends Ether to your contract without calling a specific function (like a simple `transfer()` or `send()` from an external account), the `receive()` function is triggered if it is defined.
- If you want your contract to accept plain Ether transfers, define a `receive()` function (or a fallback that is payable).**Behavior:**
- If a contract **does not** define `receive() external payable` but does define a `fallback() external payable`, then sending Ether with no data triggers the `fallback()` function.
- If neither function is payable, the contract cannot receive Ether outside of a payable function call and will revert.## `fallback()` Function
- The `fallback()` function is a special function in Solidity, called when:
- A function that doesn’t exist in the contract is called.
- No function signature matches the calldata.
- If the call has data but there is no matching function.```solidity
fallback() external [payable] {
// custom logic
}
```- `fallback()` is **not** required to be `payable` by default. If you want it to receive Ether when no function matches, mark it `payable`. Otherwise, calls sending Ether will revert.
**Use Cases:**
- **Proxy/Forwarding**: This is common in proxy contracts where all unknown calls are forwarded to another contract.
- **Emergency catch-all**: If a user or contract calls a function that does not exist, you can handle or revert gracefully.
- **Receiving Ether + Data**: If you need to handle Ether transfers that include data, you can do so in `fallback()` if `receive()` is not suitable or is absent.## Best Practices
- **Decide if you want to accept Ether**:
- If yes, define a `receive()` function (or a `payable fallback()`).
- If no, either omit them or revert in `fallback()`.
- **Keep Fallback/Receive Minimal**:
- They can be triggered frequently, and often with limited gas (2,300).
- Complex logic can lead to out-of-gas errors.
- For reentrancy safety, minimize state changes or use reentrancy guards.
- **Test for Edge Cases**:
- Test calls with valid function signatures, invalid ones, random data, zero-length data, and with Ether attached.
- Make sure you get the intended behavior (especially for proxies or other fallback patterns).
- **Event Logging**:
- If your contract receives Ether, consider emitting an event to easily track inbound transfers.# Data Locations: `storage`, `memory`, `calldata`
- In Solidity, **complex data types**—like arrays, structs, and mappings—require specifying a **data location**.
- This tells the compiler where the data physically resides. The three main data locations are:1. `storage`: Persistent on-chain storage (the contract’s state).
2. `memory`: Temporary, in-memory area used within function execution. Not persisted on-chain.
3. `calldata`: Read-only area for function arguments in external functions. It directly references call data without copying it into memory.## `storage`: Persistent, On-Chain Data
- **Location**: The contract’s long-term memory on the Ethereum blockchain.
- **Persistence**: Any changes to data in storage are permanent and cost **significant gas**.
- **State Variables**: By default, state variables declared at the contract level (e.g., `uint256 public count;`) reside in `storage`.```solidity
contract MyContract {
// This array is in storage (part of contract state).
uint256[] public numbers;function storeValue(uint256 value) external {
numbers.push(value); // Modifying storage costs gas
}
}
```**Key Points:**
- **Expensive to Modify**: Writing to `storage` is the costliest operation because you’re permanently updating the blockchain state.
- **Reference Types in Storage**: Arrays, structs, and mappings can be stored in `storage`. Modifying their contents is also costly.
- **Assignments in Storage**: When you do `storageRef = someStateVar;`, you create a reference pointing to the same data. Changes to one reflect in the other.## `memory`: Temporary, In-Function Workspace
- **Location**: A transient area used for the duration of a function call.
- **Lifecycle**: Data in `memory` is wiped after the function executes.
- **Cost**: Reading/writing `memory` is cheaper than `storage` (though not free), but the data does not persist.```solidity
function incrementValuesMemory(uint256[] memory arr)
public
pure
returns (uint256[] memory)
{
// We can mutate the array elements because 'arr' is a mutable copy in memory.
for (uint256 i = 0; i < arr.length; ++i) {
arr[i] += 10;
}
// Returning the changed array
return arr;
}
```## `calldata`: Read-Only External Input
- **Location**: A special data location for external function parameters.
- **Read-Only**: You cannot modify data in calldata; it’s immutable.
- **Gas Optimization**: Using calldata for external function parameters (instead of memory) can save gas, because Solidity can read directly from the transaction call data instead of making a full copy in memory.```solidity
function sumArray(uint256[] calldata arr) external pure returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
```- Any attempt to modify arr (e.g., `arr[i] = ...`) will fail to compile.
## Best Practices
- **Use `calldata` for External Read-Only Params**:
- If you only need to read from parameters, mark them as `calldata` to save gas (no copying into memory).
- **Avoid Large Copies**:
- Copying large arrays from `storage` to `memory` can be expensive. Consider streaming or chunking if you need partial data.
- **Use `storage` Wisely**:
- Minimize writes to `storage`. If possible, only store essential data. Reading from and especially writing to `storage` is the biggest cost in Solidity.# Transient Storage
- Transient storage is a new feature introduced to Ethereum through EIP-1153.
- It provides a special type of storage for Ethereum smart contracts that:- Exists only **during a single transaction**.
- Automatically resets at the end of the transaction.
- Does not incur persistent storage costs (unlike regular storage, which is expensive because it remains on-chain indefinitely).- Transient storage is implemented through two new opcodes:
- `TSTORE`: Temporarily stores a value in transient storage.
- `TLOAD`: Retrieves a value from transient storage.```solidity
contract ReentrancyGuard {
bytes32 constant SLOT = 0;modifier nonreentrant() {
assembly {
if tload(SLOT) { revert(0, 0) }
tstore(SLOT, 1)
}
_;
assembly {
tstore(SLOT, 0)
}
}
}
```- One practical example of transient storage is using it for a reentrancy guard.
- Normally, you’d store a boolean flag in contract storage to indicate that a function is currently being executed.
- With transient storage, you can use the `TSTORE` and `TLOAD` opcodes instead, which let you store and read this flag during the transaction without writing to storage.
- This not only makes the guard cheaper to implement, but also avoids cluttering your contract’s persistent state.**Differences from Existing Data Locations**
| Data Location | Persistence | Cost | Typical Usage |
| --------------------- | ---------------------------- | ------------------ | ------------------------------------------------------------ |
| **storage** | Persists across transactions | High | State variables, permanent contract data |
| **memory** | Temporary (function scope) | Medium | Local variables, function operations |
| **calldata** | Read-only, external inputs | Low | External function parameters |
| **Transient Storage** | Only within one transaction | Lower than storage | Ephemeral data used across calls within the same transaction |# Sending Ether
## Overview
| **Method** | **Gas Forwarded** | **On Failure** | **Return Type** | **Recommended Usage** |
| ------------ | ---------------------------------------- | -------------------------- | ------------------------------ | --------------------------------- |
| **transfer** | 2,300 gas (fixed) | Auto-reverts | No return (reverts) | Not recommended |
| **send** | 2,300 gas (fixed) | Does not revert | bool (success/fail) | Not recommended |
| **call** | All remaining gas (or a specified value) | Does not revert by default | (bool, bytes) (success + data) | Recommended with reentrancy guard |### transfer
- Forwards a **fixed 2,300 gas** to the recipient’s fallback/receive function.
- If the call fails (e.g., the fallback function uses more than 2,300 gas or reverts), `transfer` will automatically revert the entire transaction.
- If future Ethereum upgrades raise the fallback gas cost or the logic changes, `transfer` might break.```solidity
function transfer(address payable _to, uint256 _amount) external {
_to.transfer(_amount);
// If it fails, entire function reverts automatically
}
```### send
- Also forwards 2,300 gas to the recipient’s fallback function.
- Does not revert on failure by default. Instead, it returns false if the call fails.
- Generally considered obsolete in favor of `transfer`, or `call`.```solidity
function send(address payable _to, uint256 _amount) external {
bool success = _to.send(_amount);
require(success, "Send failed");
}
```### call
```solidity
(bool success, bytes memory data) = recipient.call{value: amount, gas: gasAmount}("");```
- Forwards all remaining gas by default, or a specified amount if you use `gas: gasAmount`.
- Does not automatically revert; you must handle the success boolean.
- `call` is more future-proof and flexible. Recommended for most use cases with reentrancy guards or checks-effects-interactions pattern.Simple Ether send:
```solidity
function flexibleCall(address payable _to, uint256 _amount) external {
(bool success, ) = _to.call{value: _amount}("");
require(success, "Call failed");
}
```Calling a function with data:
```solidity
function callFunction(address _contract, bytes memory _data, uint256 _amount) external {
(bool success, bytes memory returnedData) = _contract.call{value: _amount}(_data);
require(success, "Low-level call failed");
// returnedData may contain a return value
}
```### Best Practices
- `transfer` and `send` are no longer recommended in most modern Solidity patterns because of their 2,300 gas limit and potential for breakage if fallback logic changes.
- `call` with proper error handling (checking the return boolean) and reentrancy protections is the current best practice for sending Ether.# Function Selector
A **function selector** is a **4-byte (8-hex-digit)** identifier for a function. When a contract is called, **the first 4 bytes** of the calldata are used to determine which function should be invoked within the contract.
1. **Location**: The function selector resides at the **start** of the transaction’s data (calldata).
2. **Purpose**: It **selects** which function in the contract to call. If a contract doesn’t have a matching selector, it triggers the fallback or receive function (if defined).## How is the Function Selector Computed?
The function selector for a given function is computed as the **first 4 bytes** of the **Keccak-256 hash** of the function’s **signature string**.
```solidity
bytes32 hash = keccak256("transfer(address,uint256)");
bytes4 selector = bytes4(hash); // first 4 bytes
```## Layout of Calldata
When you call a function on a contract via a low-level call or transaction, the calldata typically follows this format:
| **Bytes Range** | **Purpose** |
| --------------- | -------------------------------- |
| 0x00 - 0x03 | Function selector (4 bytes) |
| 0x04 - end | Encoded function arguments (ABI) |For instance, a call to `transfer(address to, uint256 amount)` might have:
- **First 4 bytes**: `0xa9059cbb` (the selector).
- **Next 32 bytes**: Encoded `to` address (padded to 32 bytes).
- **Next 32 bytes**: Encoded `amount` (256-bit integer).## Function Overloading and Selectors
Solidity supports function overloading: multiple functions can share the same name but have different parameter types.
- Each overloaded function has a unique signature string (because the parameter types differ).
- This results in different keccak-256 hashes and thus different 4-byte selectors.```solidity
function foo(uint256 x) external { /* ... */ }
function foo(uint256 x, uint256 y) external { /* ... */ }
```## Collision Issues
- The function selector is derived from: `bytes4(keccak256(functionSignatureString))`.
- Because it’s only 4 bytes, there are `2^32` possible selectors, which is about 4.29 billion unique values.
- In theory (and sometimes in practice), **two different function signatures** can **hash** to the same 4-byte prefix. This is called a **collision**.
- In most modern Solidity compilers, the compiler **fails** or warns at compile time if it detects a collision among the function signatures in your contract.```solidity
contract WontCompile {
// function selector of collate_propagate_storage: 0x42966c68
function collate_propagate_storage(bytes16 x) external {}
// function selector of burn: 0x42966c68
function burn(uint256 amount) external {}
}
```# Call & Delegatecall
## Introduction to Low-Level Calls
In Solidity, you can interact with other contracts or addresses at a low level using:
- `call`
- `delegatecall`
- (less common) `staticcall` (for read-only calls)These are lower-level functions than the usual contract function calls because they bypass the Solidity function name → selector → ABI encoding process (unless you manually encode/decode arguments and return data). They return success/failure booleans and raw byte data rather than automatically reverting on failure or decoding data.
### Why Use Low-Level Calls?
1. **Dynamic function calls**: If you only know at runtime which contract or function signature you’re calling.
2. **Proxies**: Common pattern for upgradeable contracts or decoupling logic from storage.
3. **Manual gas management**: You can specify the exact gas or handle reverts manually.## `call`
`call` allows you to call a specified address (contract or EOA) with custom calldata, value (Ether), and an optional gas parameter.
```solidity
(bool success, bytes memory returnedData) = targetAddress.call{value: etherAmount, gas: gasAmount}(calldata);
```### Behavior
- **Does Not Auto-Revert**: If the call fails (e.g., the target reverts), `success` will be `false`. The state changes in your contract up to that point remain unless you manually revert.
- **Returns Raw Data**: `returnedData` is the raw bytes the target contract returned. You must decode it if you expect a specific type (e.g., an integer or a boolean).
- **Forwards Gas**: By default, `call` will forward all remaining gas. You can specify a `gas: someAmount` to limit it.
- **Potential Reentrancy**: Because you may be forwarding a lot of gas, be mindful of reentrancy vulnerabilities if your contract’s state is updated before calling out.```solidity
function callTransfer(address _token, address _to, uint256 _amount) external {
// Encode the function selector and arguments for an ERC20 transfer
bytes memory data = abi.encodeWithSelector(
bytes4(keccak256("transfer(address,uint256)")),
_to,
_amount
);// Low-level call
(bool success, bytes memory returnData) = _token.call(data);require(success, "call to transfer failed");
// Optionally decode the returned data (many ERC20s return bool, but not all)
// bool transferSuccess = abi.decode(returnData, (bool));
}
```## `delegatecall`
`delegatecall` is similar to `call` but crucially **executes the code of the target contract in the context of the caller’s state**. This is the foundation for **proxy** contracts or **upgradeable** contract patterns.
```solidity
(bool success, bytes memory returnedData) = targetAddress.delegatecall(calldata);
```### Behavior
- **Executes in Caller’s Context**:
- `msg.sender` and `msg.value` remain the same as in the original call that reached the caller.
- Any storage changes (writes) made by the executed code affect the caller contract’s storage layout.
- **No Ether Transfer**:
- Unlike `call`, you can’t directly send Ether with `delegatecall`. It only executes code with the current call’s context.
- **State & Storage Layout**:
- You must ensure the storage layout of the caller and the target match if the target code writes to storage. Mismatched layouts lead to corruption or undefined behavior.
- **Returns**:
- Similar to `call`, you get `(bool success, bytes memory returnData)`. You must handle them manually.```solidity
contract Proxy {
address public implementation; // Points to logic contractconstructor(address _impl) {
implementation = _impl;
}fallback() external payable {
// Forward all calls to 'implementation' using delegatecall
(bool success, bytes memory data) = implementation.delegatecall(msg.data);
if (!success) {
assembly {
revert(add(data, 32), mload(data))
}
}
assembly {
return(add(data, 32), mload(data))
}
}
}
```## `staticcall`
`staticcall` is another low-level function used for read-only calls. It reverts if the called code attempts to modify state (like writing to storage or emitting events).
```solidity
(bool success, bytes memory returnData) = targetAddress.staticcall(calldata);
```## Comparison
| Aspect | call | delegatecall |
| -------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| **State Context** | Runs in the **target’s** context. Writes in the **target’s** storage. | Runs in the **caller’s** context. Writes in the **caller’s** storage. |
| **msg.sender / msg.value** | **msg.sender** is the caller (the contract executing `call`). | **msg.sender** remains the original external address or higher-level caller. |
| **Ether Transfer** | You can send Ether along with it (`value: X`). | No direct Ether transfer is possible. |
| **Common Use Case** | Directly calling external contracts, optionally sending Ether. | Proxy patterns, libraries, or upgradeable contracts (code is borrowed but state remains in the caller). |
| **Gas Forwarding** | Forwards all remaining gas unless specified otherwise. | Same, but in the caller’s context (no Ether). |## Security & Best Practices
1. **Check Return Values**
- Both `call` and `delegatecall` return a boolean indicating success/failure. Always check it.
2. **Handle returnData**
- If the called function returns data, decode it if you care about it.
- If the call reverts with a reason string, that’s included in `returnData`. You can bubble it up using inline assembly or revert with your own error.3. **Protect Against Reentrancy**
- If you use `call` to forward large amounts of gas, your contract can be reentered. Use patterns like **Checks-Effects-Interactions** or **ReentrancyGuard**.
4. **Proxy Storage Layout**
- With `delegatecall`, ensure the **implementation** contract’s storage layout is compatible with the proxy contract’s layout. If you add new variables in the implementation or reorder them, you can break or corrupt the proxy’s storage.
5. **Avoid Arbitrary Delegatecalls**
- Letting users choose any target for `delegatecall` is a critical security hole. Restrict target addresses or function signatures if you want safe upgrade patterns.
6. **Gas Estimation**
- Low-level calls might confuse the Solidity gas estimator. You sometimes need to manually specify gas or test thoroughly to avoid out-of-gas issues.
# CREATE, CREATE2, Create3, and CreateX
## CREATE
`CREATE` is the most basic and commonly used opcode for contract deployment. It works as follows:
- Deploys contracts dynamically within other contracts
- More cost-effective for deploying multiple contracts
- The resulting contract address is determined by the deployer's address and nonce
- Doesn't provide address predictabilityUsing `CREATE`, the address of the newly deployed contract is determined by:
- `address = rightmost 160 bits of keccak256(rlp(sender, nonce))`
1. `sender`: The address that deploys the contract (an EOA or another contract).
2. `nonce`: The internal transaction count (nonce) of the sender at the time of creation.```solidity
function deployWithCREATE() external returns (address childAddress) {
// Simple CREATE deployment
Child child = new Child();
childAddress = address(child);emit Deployed(childAddress, 0);
}
```## CREATE2
`CREATE2` was designed to let you compute a contract’s address in advance, based on a salt and the contract’s init code, enabling so-called “counterfactual deployments.”
`address = rightmost 160 bits of keccak256(0xff | sender | salt | keccak256(init_code))`
Where:
- `0xff`: A constant single byte to avoid collisions with regular `CREATE`.
- `sender`: The address that issues the `CREATE2`.
- `salt`: A 32-byte value supplied by the deployer.
- `keccak256(init_code)`: The hash of the contract’s creation bytecode.Normally, CREATE2 is used with the [Deterministic Deployment Proxy contract](https://github.com/Arachnid/deterministic-deployment-proxy) to ensure a fixed `msg.sender` address.
```solidity
contract Child {
uint public x;
constructor(uint a) {
x = a;
}
}contract Parent {
function deployWithCREATE2(bytes32 salt, uint arg) public {
// This complicated expression just tells you how the address
// can be pre-computed. It is just there for illustration.
// You actually only need ``new Child{salt: salt}(arg)``.
address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(abi.encodePacked(
type(Child).creationCode,
abi.encode(arg)
))
)))));Child child = new Child{salt: salt}(arg);
require(address(child) == predictedAddress);
}
}
```## Create3
`Create3` is not an opcode but a method combining `CREATE` and `CREATE2`:
- Generates deterministic contract addresses without depending on the contract's bytecode
- Address is based **only on `msg.sender` and `salt`**
- You can use **different constructor arguments** on multiple chains
- More expensive than `CREATE` or `CREATE2` (extra ~55k gas)
- Allows deploying contracts to the same address across multiple EVM-compatible blockchainsUsually, Create3 is used with [Create3 Factory](https://github.com/ZeframLou/create3-factory)
```solidity
/**
@notice Creates a new contract with given `_creationCode` and `_salt`
@param _salt Salt of the contract creation, resulting address will be derivated from this value only
@param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
@param _value In WEI of ETH to be forwarded to child contract
@return addr of the deployed contract, reverts on error
*/
function create3(bytes32 _salt, bytes memory _creationCode, uint256 _value) internal returns (address addr) {
// Creation code
bytes memory creationCode = PROXY_CHILD_BYTECODE;// Get target final address
addr = addressOf(_salt);
if (codeSize(addr) != 0) revert TargetAlreadyExists();// Create CREATE2 proxy
address proxy; assembly { proxy := create2(0, add(creationCode, 32), mload(creationCode), _salt)}
if (proxy == address(0)) revert ErrorCreatingProxy();// Call proxy with final init code
(bool success,) = proxy.call{ value: _value }(_creationCode);
if (!success || codeSize(addr) == 0) revert ErrorCreatingContract();
}
```## Comparison
| Feature | CREATE | CREATE2 | Create3 |
| ------------------- | ------- | -------- | --------- |
| Address Determinism | No | Yes | Yes |
| Bytecode Dependency | Yes | Yes | No |
| Multi-chain Support | Limited | Possible | Excellent |
| Gas Cost | Lowest | Medium | Highest |## CreateX
[CreateX](https://github.com/pcaversaccio/createx) is a factory smart contract designed to simplify and enhance the usage of CREATE, CREATE2, and Create3:
- Provides a unified interface for different contract creation methods
- Offers additional features and optimizations# ABI Encode & Decode
# References
- [Solidity Docs](https://docs.soliditylang.org/en/latest/)
- [Solidity by Example](https://solidity-by-example.org/)
- [Solidity Cheatsheet and Best practices](https://github.com/manojpramesh/solidity-cheatsheet/)
- [Solidity Gas Optimization Techniques: Loops](https://hackmd.io/@totomanov/gas-optimization-loops#Solidity-Gas-Optimization-Techniques-Loops)
- [Gas Optimization In Solidity: Strategies For Cost-Effective Smart Contracts](https://hacken.io/discover/solidity-gas-optimization/)
- [Understanding the Function Selector in Solidity](https://www.rareskills.io/post/function-selector)