The Ultimate Solidity 0.8 Cheatsheet

Rahul Ravindran

Comments

You can write comments to document your code; Single-line comments (//) and multi-line comments (//) are possible.

// This is a single-line comment.

/*
This is a
multi-line comment.
*/

License

Since smart contract code is always open to anyone, solidity compiler encourages use of machine readable license identifiers to avoid legal problems. Every source file should start with a comment indicating its license:

// SPDX-License-Identifier: MIT

Version Pragma

The pragma keyword is used to enable certain compiler features or checks. Source files can (and should) be annotated with a version pragma to reject compilation with future compiler versions that might introduce incompatible changes.

pragma solidity ^0.8.2;

Imports

Solidity import statements allow code to be imported from another file. This helps break your project into modularise your code across multiple smaller files.

// global imports => import "filename";
import “Math.sol”

// named imports => import * as symbolName from "filename";
import * as formula from “Math.sol”

State Variables

State variables are variables whose values are permanently stored in contract storage.

contract SimpleStorage {
    uint storedData1; // Unsinged integer; can be negative
    int  storedData2; // Signed integer; cannot be negative
    bool storedData3, // Boolean value (true or false)
    address storedData4; // 20 byte ethereum address

    //complex data types
    int[] storedData5; // a dynamic array of int
    int[3] storedData6; // a fixed array of 3 int 
    string storedData7; // a literal string eg "Hello"

}

Structs

Structs are custom defined types that can group several variables

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

contract Ballot {
    // Struct
    struct Voter {
        uint weight;
        bool voted;
        address delegate;
        uint vote;
    }
}

Enums

Enums can be used to create custom types with a finite set of ‘constant values’

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

contract Purchase {
    enum State { Created, Locked, Inactive } // Enum
}

Variable visibility

The visibility of a variable dictates where in the code a variable can be accessed from. There are 3 possible visibility: private, internal (default) and public.

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

contract SimpleStorage { 
    // Visible only inside contract
    private uint storedData1;
  // Visible inside the contract and derived contracts (Default)
    internal int  storedData2; 
    // Like internal, but getter functions are automatically generated
    public bool storedData3, 
}

Functions

Functions are the executable units of code. Functions are usually defined inside a contract, but they can also be defined outside of contracts.

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

contract SimpleAuction {
    function bid() public payable { // Function
        // ...
    }
}

// Helper function defined outside of a contract
function helper(uint x) pure returns (uint) {
    return x * 2;
}

Function Modifiers

Modifiers can be used to change the behaviour of functions in a declarative way. For example, you can use a modifier to automatically ensure only sellers can list an NFT for auction.

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

contract Purchase {
    address public seller;

    modifier onlySeller() { // Modifier
        require(
            msg.sender == seller,
            "Only seller can call this."
        );
        _;
    }


    function list() public view onlySeller { // Modifier usage
        // ...
    }
}

Internal Function Calls

Functions of the current contract can be called directly (“internally”), also recursively, as seen in this nonsensical example:

// SPDX-License-Identifier: GPL-3.0
pragma solidity  >=0.4.22  <0.9.0;

// This will report a warning
contract  C  {
  function  g(uint  a)  public  pure  returns  (uint  ret)  {  return  a  +  f();  }
  function  f()  internal  pure  returns  (uint  ret)  {  return  g(7)  +  f();  }
}

External Function Calls

Functions can also be called using the this.g(8); and c.g(2); notation, where c is a contract instance and g is a function belonging to c.

// SPDX-License-Identifier: GPL-3.0
pragma solidity  >=0.6.2  <0.9.0;

contract  InfoFeed  {
  function  info()  public  payable  returns  (uint  ret)  {  return  42;  }
}

contract  Consumer  {
  InfoFeed  feed;
  function  setFeed(InfoFeed  addr)  public  {  feed  =  addr;  }
  function  callFeed()  public  {  feed.info{value:  10,  gas:  800}();  }
}

Named Calls and Anonymous Function Parameters

Function call arguments can be given by name, in any order, if they are enclosed in { } as can be seen in the following example.

// SPDX-License-Identifier: GPL-3.0
pragma solidity  >=0.4.0  <0.9.0;

contract  C  {
  mapping(uint  =>  uint)  data;

  function  f()  public  {
  set({value:  2,  key:  3});
  }

  function  set(uint  key,  uint  value)  public  {
  data[key]  =  value;
  }

}

Events

Event is an inheritable member of a contract. An event is emitted, it stores the arguments passed in transaction logs. Events are ways to communicate with a client application or front-end website that something has happened on the blockchain.

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

contract SimpleAuction {
    event HighestBidIncreased(address bidder, uint amount); // Event

    function bid() public payable {
        // ...
        emit HighestBidIncreased(msg.sender, msg.value); // Triggering event
    }
}

Errors

Errors allow you to define descriptive names and data for failure situations. You can use NatSpec to describe the error to the user. They can be defined inside and outside of contracts.

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

/// Not enough funds for transfer. Requested `requested`,
/// but only `available` available.
error NotEnoughFunds(uint requested, uint available);

contract Token {

    mapping(address => uint) balances;

    function transfer(address to, uint amount) public {
        uint balance = balances[msg.sender];
        if (balance < amount)
            revert NotEnoughFunds(amount, balance);
         // ...
    }
}

Operators

a+b                 // Addition
a-b                 // Subtraction
a*b                 // Multiplication
a/b                 // Division
a * (b + c)         // grouping
person.age          // member
person[age]         // member
!(a == b)           // logical not
a != b              // not equal
a = b               // assignment
a == b              // equals
a != b              // unequal
a === b             // strict equal
a !== b             // strict unequal
a < b   a > b       // less and greater than
a <= b  a >= b      // less or equal, greater or eq
a += b              // a = a + b (works with - * %...)
a && b              // logical and
a || b              // logical or

Special Units

Ether Units

A literal number can take a suffix of wei, gwei or ether to specify a subdenomination of Ether, where Ether numbers without a postfix are assumed to be Wei.

Time Units

Suffixes like seconds, minutes, hours, days and weeks after literal numbers can be used to specify units of time where seconds are the base unit and units are considered naively in the following way: – 1 == 1 seconds1 minutes == 60 seconds1 hours == 60 minutes1 days == 24 hours1 weeks == 7 days

Global functions and variables

There are special variables and functions which always exist in the global namespace

Block and Transaction Properties

  • blockhash(uint blockNumber) returns (bytes32): hash of the given block when blocknumber is one of the 256 most recent blocks; otherwise returns zero
  • block.basefee (uint): current block’s base fee (EIP-3198 and EIP-1559)
  • block.chainid (uint): current chain id
  • block.coinbase (address payable): current block miner’s address
  • block.difficulty (uint): current block difficulty
  • block.gaslimit (uint): current block gaslimit
  • block.number (uint): current block number
  • block.timestamp (uint): current block timestamp as seconds since unix epoch
  • gasleft() returns (uint256): remaining gas
  • msg.data (bytes calldata): complete calldata
  • msg.sender (address): sender of the message (current call)
  • msg.sig (bytes4): first four bytes of the calldata (i.e. function identifier)
  • msg.value (uint): number of wei sent with the message
  • tx.gasprice (uint): gas price of the transaction
  • tx.origin (address): sender of the transaction (full call chain)

ABI Encoding and Decoding Functions

  • abi.decode(bytes memory encodedData, (...)) returns (...): ABI-decodes the given data, while the types are given in parentheses as second argument. Example: (uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))
  • abi.encode(...) returns (bytes memory): ABI-encodes the given arguments
  • abi.encodePacked(...) returns (bytes memory): Performs packed encoding of the given arguments. Note that packed encoding can be ambiguous!
  • abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory): ABI-encodes the given arguments starting from the second and prepends the given four-byte selector
  • abi.encodeWithSignature(string memory signature, ...) returns (bytes memory): Equivalent to abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)
  • abi.encodeCall(function functionPointer, (...)) returns (bytes memory): ABI-encodes a call to functionPointer with the arguments found in the tuple. Performs a full type-check, ensuring the types match the function signature. Result equals abi.encodeWithSelector(functionPointer.selector, (...))

Mathematical and Cryptographic Functions

  • addmod(uint x, uint y, uint k) returns (uint): compute (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2**256. Assert that k != 0 starting from version 0.5.0.
  • mulmod(uint x, uint y, uint k) returns (uint): compute (x * y) % k where the multiplication is performed with arbitrary precision and does not wrap around at 2**256. Assert that k != 0 starting from version 0.5.0.
  • keccak256(bytes memory) returns (bytes32): compute the Keccak-256 hash of the input
  • sha256(bytes memory) returns (bytes32): compute the SHA-256 hash of the input
  • ripemd160(bytes memory) returns (bytes20): compute RIPEMD-160 hash of the input
  • ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address): recover the address associated with the public key from elliptic curve signature or return zero on error. The function parameters correspond to ECDSA values of the signature:
    • r = first 32 bytes of signature
    • s = second 32 bytes of signature
    • v = final 1 byte of signature ecrecover returns an address, and not an address payable. See address payable for conversion, in case you need to transfer funds to the recovered address.

Error Handling Functions

  • assert(bool condition) causes a Panic error and thus state change reversion if the condition is not met – to be used for internal errors.
  • require(bool condition) reverts if the condition is not met – to be used for errors in inputs or external components.
  • require(bool condition, string memory message) reverts if the condition is not met – to be used for errors in inputs or external components. Also provides an error message.
  • revert() abort execution and revert state changes
  • revert(string memory reason) abort execution and revert state changes, providing an explanatory string

Modifiers Keywords

  • pure for functions: Disallows modification or access of state.
  • view for functions: Disallows modification of state.
  • payable for functions: Allows them to receive Ether together with a call.
  • constant for state variables: Disallows assignment (except initialisation), does not occupy storage slot.
  • immutable for state variables: Allows exactly one assignment at construction time and is constant afterwards. Is stored in code.
  • anonymous for events: Does not store event signature as topic.
  • indexed for event parameters: Stores the parameter as topic.
  • virtual for functions and modifiers: Allows the function’s or modifier’s behaviour to be changed in derived contracts.
  • override: States that this function, modifier or public state variable changes the behaviour of a function or modifier in a base contract.

1 Comment

  1. luax.eth
    May 9, 2022

    So cool thanks!

Leave a Reply

More great articles

How to do Upgradable smart contracts using OpenZeppelin

An Upgradable contract is a contract that can be (kind of) altered, after the deployment. At the time this article…

Read Story

Creating a new Contract from another Contract in Solidity

Introduction In blockchain development, mining sets of data into a blockchain is quite an expensive process because fees are charged…

Read Story

Solidity Security Part 4- Timestamp Dependence

It’s not uncommon to write a contract in Solidity that only allows certain actions to be performed within specific time…

Read Story

Never miss a minute

Get great content to your inbox every week. No spam.
[contact-form-7 id="6" title="Footer CTA Subscribe Form"]
Arrow-up