DEV Community

Cover image for The Ultimate Solidity Cheat Sheet for Beginners
Imam Abubakar for Hack Solidity

Posted on

The Ultimate Solidity Cheat Sheet for Beginners

Welcome to the world of Solidity, the object-oriented, high-level language for implementing smart contracts on the Ethereum Blockchain.In this guide, we will dive into the fundamental concepts of Solidity and provide you with a detailed understanding of its syntax, features, and best practices. Whether you're a beginner or an experienced developer, this guide will serve as a valuable resource for mastering Solidity and building secure and efficient smart contracts.

This guide is brought to you by Hack Solidity, a platform dedicated to empowering individuals with blockchain knowledge. Our goal is to equip you with the necessary skills and insights to excel in the rapidly evolving blockchain industry.

Solidity Logo

What is Solidity?

Solidity is a statically-typed programming language designed for developing smart contracts that run on the Ethereum Virtual Machine (EVM). Smart contracts are self-executing contracts with the terms of the agreement directly written into code. They allow trusted transactions and agreements to be carried out among disparate, anonymous parties without the need for a central authority, legal system, or external enforcement mechanism.

Setting Up Your Environment

Before you start coding, you'll need to set up your development environment. The most common tools for Solidity development are:

Remix IDE: An online IDE specifically built for smart contract development with built-in static analysis, test blockchain, and debugging tools.

Truffle: A popular development framework with built-in smart contract compilation, linking, deployment, and binary management.

Ganache: A personal blockchain for Ethereum development you can use to deploy contracts, develop applications, and run tests.

To get started, head over to Remix IDE on your browser.

Basic Syntax and Structure

A Solidity smart contract is similar to a class in object-oriented languages. Each contract can contain declarations of State Variables, Functions, Function Modifiers, Events, and Struct Types. Here's a basic structure of a Solidity contract:

pragma solidity ^0.8.0;

contract MyContract {
    // State variables
    uint256 public myVariable;

    // Events
    event ValueUpdated(uint256 newValue);

    // Constructor
    constructor() {
        myVariable = 0;
    }

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

    // Functions
    function setValue(uint256 newValue) public onlyOwner {
        myVariable = newValue;
        emit ValueUpdated(newValue);
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above example:

  • The contract is declared using the contract keyword followed by the contract name MyContract.
  • The pragma directive specifies the compiler version to be used (in this case, Solidity version 0.8.0 or higher).
  • The myVariable state variable is declared as a publicly accessible uint256.
  • The ValueUpdated event is defined, which will be emitted whenever the value of myVariable is updated.
  • The constructor initializes myVariable to 0 when the contract is deployed.
  • The onlyOwner modifier is defined to restrict access to certain functions, allowing only the contract owner to call them.
  • The setValue function is defined, which updates the value of myVariable with the provided newValue. It can only be called by the contract owner due to the onlyOwner modifier.
  • The emit keyword is used to trigger the ValueUpdated event and emit the new value.

Note: This example shows a simplified structure of a Solidity contract. In real-world scenarios, smart contracts may have more complex functionality, additional state variables, multiple functions, and interactions with other contracts or external systems.

Data types

Data Type Description Example
bool Boolean value (true or false) bool myBool = true;
uint Unsigned integer uint256 myUint = 42;
int Signed integer int256 myInt = -10;
address Ethereum address address myAddress = 0x123...7890;
string Textual data string myString = "Hello, Solidity!";
array Collection of elements of the same type uint256[] myDynamicArray;
mapping Key-value pairs mapping(address => uint256) balances;
struct Custom data structure struct Person { string name; uint256 age; } Person myPerson = Person("Alice", 25);
enum Enumerated list of possible values enum Status { Active, Inactive, Suspended } Status myStatus = Status.Active;
// Contract showcasing Solidity data types
pragma solidity ^0.8.0;

contract DataTypeExample {
    bool public myBool;
    uint256 public myUint;
    int256 public myInt;
    address public myAddress;
    string public myString;
    uint256[] public myDynamicArray;
    mapping(address => uint256) public balances;
    struct Person {
        string name;
        uint256 age;
    }
    Person public myPerson;
    enum Status { Active, Inactive, Suspended }
    Status public myStatus;

    constructor() {
        myBool = true;
        myUint = 42;
        myInt = -10;
        myAddress = 0x123...7890;
        myString = "Welcome to Hack Solidity!";
        myDynamicArray.push(1);
        balances[msg.sender] = 1000;
        myPerson = Person("Alice", 25);
        myStatus = Status.Active;
    }
}
Enter fullscreen mode Exit fullscreen mode

State Variables

State variables are an important concept in Solidity as they represent the persistent storage of data within a smart contract. These variables hold their values between function calls and are stored on the blockchain. Here's an extensive overview of state variables in Solidity:

1.Declaration:

contract MyContract {
    uint256 public myVariable; // Public state variable
    address private owner;     // Private state variable
    uint256 internal myInternalVariable; // Internal state variable
    string public constant myConstant = "Welcome to Hack Solidity!"; // Public constant state variable
}
Enter fullscreen mode Exit fullscreen mode

2.Visibility Modifiers: The visibility modifier determines how the state variable can be accessed.

  • public: The variable is accessible from within the contract and externally.
  • private: The variable is only accessible within the contract and not externally.
  • internal: The variable is accessible within the contract and any derived contracts.
  • external: The variable is only accessible externally and not within the contract.

3.Accessing State Variables: State variables can be accessed and modified by both internal and external functions within the contract.

  • Internal Access:
function updateVariable(uint256 newValue) internal {
    myVariable = newValue;
}
Enter fullscreen mode Exit fullscreen mode
  • External Access:
function getVariable() external view returns (uint256) {
    return myVariable;
}
Enter fullscreen mode Exit fullscreen mode

4.Constant State Variables: Constant state variables hold values that remain the same throughout the execution of the contract. They can be used for storing fixed values or configuration data.

string public constant GREETING = "Hello!";
uint256 public constant MAX_VALUE = 100;
Enter fullscreen mode Exit fullscreen mode

Note that constant state variables can only be of types bool, int, uint, address, string, or arrays of these types.

5.Default Values: State variables are assigned default values if not explicitly initialized in the constructor or elsewhere.

  • For value types (bool, uint, int, etc.), the default value is 0.
  • For address, the default value is address(0).
  • For string, the default value is an empty string ("").
  • For reference types (arrays, structs, etc.), the default value is an empty instance.

Functions

Functions in Solidity are an essential part of smart contracts. They define the behavior and operations that can be performed on a contract's data. Here's an overview of functions in Solidity:

1.Function Declaration:

contract MyContract {
    // Public function without parameters and return value
    function myFunction() public {
        // Function body
    }

    // External function with parameters and return value
    function add(uint256 a, uint256 b) external pure returns (uint256) {
        // Function body
        return a + b;
    }
}
Enter fullscreen mode Exit fullscreen mode

2.Visibility Modifiers: Functions can have visibility modifiers that control their accessibility.

  • public: The function can be called internally from within the contract and externally from other contracts or accounts.
  • private: The function is only accessible from within the contract.
  • internal: The function is accessible from within the contract and any derived contracts.
  • external: The function can be called externally, but not from within the contract itself.

3.Function Parameters:

function myFunction(uint256 param1, address param2) public {
    // Function body
}
Enter fullscreen mode Exit fullscreen mode

4.Return Values: Functions can return values using the returns keyword, followed by the return type. Multiple return values can be specified using parentheses.

function getSumAndDifference(uint256 a, uint256 b) public pure returns (uint256, uint256) {
    uint256 sum = a + b;
    uint256 difference = a - b;
    return (sum, difference);
}
Enter fullscreen mode Exit fullscreen mode

5.Function Modifiers: Modifiers are used to modify the behavior of functions. They are defined separately and can be applied to functions using the modifier keyword.

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

function myFunction() public onlyOwner {
    // Function body
}
Enter fullscreen mode Exit fullscreen mode

6.Function Overloading: Solidity supports function overloading, which means you can have multiple functions with the same name but different parameter lists. The compiler determines which function to call based on the provided arguments.

function process(uint256 data) public {
    // Function body
}

function process(uint256[] memory dataArray) public {
    // Function body
}
Enter fullscreen mode Exit fullscreen mode

7.Fallback and Receive Functions: Solidity allows defining a fallback function and a receive function in a contract.

  • Fallback Function: The fallback function is executed when a contract receives a message without any matching function or value transfer. It is declared without a function name using the fallback keyword.
fallback() external {
    // Fallback function body
}
Enter fullscreen mode Exit fullscreen mode
  • Receive Function: The receive function is executed when a contract receives a plain Ether transfer without any data. It is declared without a function name using the receive keyword.
receive() external payable {
    // Receive function body
}
Enter fullscreen mode Exit fullscreen mode

Events

Events allow external applications to listen to and react to specific occurrences within the contract. Here's an overview of events in Solidity:

1.Event Declaration:

contract MyContract {
    // Event declaration with parameters
    event MyEvent(address indexed sender, uint256 value);
}
Enter fullscreen mode Exit fullscreen mode

2.Event Emission: To emit an event and provide the associated data, you can use the emit keyword followed by the event name and the corresponding values.

function myFunction() public {
    // Emitting an event
    emit MyEvent(msg.sender, 100);
}
Enter fullscreen mode Exit fullscreen mode

3.Event Parameters: Events can include parameters that define the information to be emitted. These parameters can be indexed or non-indexed.

  • Indexed Parameters: Indexed parameters enable efficient filtering and searching of events. Up to three parameters can be indexed per event.
event MyEvent(address indexed sender, uint256 indexed id, string message);
Enter fullscreen mode Exit fullscreen mode
  • Non-indexed Parameters: Non-indexed parameters are included in the event but cannot be used for filtering.
event MyEvent(address sender, uint256 value, string message);
Enter fullscreen mode Exit fullscreen mode

4.Event Subscription: External applications or other contracts can subscribe to events emitted by a contract and listen for specific occurrences. They can do this by using the contract's address and the event's signature.

// Event subscription example
MyContract myContract = MyContract(contractAddress);
myContract.MyEvent().listen(callbackFunction);
Enter fullscreen mode Exit fullscreen mode

5.Event Log: When an event is emitted, a log entry is created on the blockchain, capturing the event's data. Event logs are stored in the transaction receipt and can be accessed using web3 libraries or blockchain explorers.

6.Event Usage: Events are commonly used for logging and providing information about specific state changes or significant occurrences within a contract. They serve as a means of communication between contracts and external systems, facilitating real-time updates and data synchronization.

contract MyContract {
    event ValueUpdated(uint256 newValue);

    uint256 public myValue;

    function setValue(uint256 newValue) public {
        myValue = newValue;
        emit ValueUpdated(newValue);
    }
}
Enter fullscreen mode Exit fullscreen mode

In the above example, the ValueUpdated event is emitted whenever the setValue function is called, allowing external applications to listen to the event and receive updates whenever myValue is changed.

Struct

Structs in Solidity allow you to define custom data structures to group related variables. Here's a brief overview:

1.Struct Declaration: Structs are declared using the struct keyword within the contract scope.

struct Person {
    string name;
    uint256 age;
}
Enter fullscreen mode Exit fullscreen mode

2.Struct Usage: Structs can be used to create instances and access their variables.

Person public myPerson;

function createPerson(string memory _name, uint256 _age) public {
    myPerson = Person(_name, _age);
}
Enter fullscreen mode Exit fullscreen mode

3.Struct Arrays: Structs can be used to create arrays holding multiple instances.

Person[] public people;

function addPerson(string memory _name, uint256 _age) public {
    Person memory newPerson = Person(_name, _age);
    people.push(newPerson);
}
Enter fullscreen mode Exit fullscreen mode

4.Nested Structs: Structs can be nested within other structs.

struct Person {
    string name;
    uint256 age;
    Address contactAddress;
}

struct Address {
    string street;
    string city;
}

Person public myPerson;

function createPerson(string memory _name, uint256 _age, string memory _street, string memory _city) public {
    Address memory newAddress = Address(_street, _city);
    myPerson = Person(_name, _age, newAddress);
}
Enter fullscreen mode Exit fullscreen mode

Control structures in Solidity

1.If-Else Statements: If-else statements allow you to conditionally execute code based on a certain condition.


function checkValue(uint256 value) public pure returns (string memory) {
    if (value > 10) {
        return "Value is greater than 10";
    } else if (value == 10) {
        return "Value is equal to 10";
    } else {
        return "Value is less than 10";
    }
}
Enter fullscreen mode Exit fullscreen mode

2.While Loop: While loops execute a block of code repeatedly as long as a specified condition is true.

function countDown(uint256 startValue) public pure returns (uint256) {
    while (startValue > 0) {
        startValue--;
    }
    return startValue;
}
Enter fullscreen mode Exit fullscreen mode

3.For Loop: For loops allow you to execute a block of code repeatedly for a specific number of iterations.

function sumArray(uint256[] memory numbers) public pure returns (uint256) {
    uint256 sum = 0;
    for (uint256 i = 0; i < numbers.length; i++) {
        sum += numbers[i];
    }
    return sum;
}
Enter fullscreen mode Exit fullscreen mode

4.Do-While Loop: Do-while loops execute a block of code at least once and continue executing as long as a specified condition is true.

function countUp(uint256 startValue) public pure returns (uint256) {
    do {
        startValue++;
    } while (startValue < 10);
    return startValue;
}
Enter fullscreen mode Exit fullscreen mode

Error Handling

1.require Statement: The require statement is used to validate conditions and revert the transaction if the condition evaluates to false. It is commonly used for input validation and contract pre-conditions.

function deposit(uint256 amount) public {
    require(amount > 0, "Amount must be greater than zero");
    // Deposit logic
}
Enter fullscreen mode Exit fullscreen mode

2.assert Statement: The assert statement is used to check for conditions that should never be false. If the condition evaluates to false, it indicates an internal error in the contract, and the transaction is reverted.

function divide(uint256 numerator, uint256 denominator) public pure returns (uint256) {
    assert(denominator != 0);
    return numerator / denominator;
}
Enter fullscreen mode Exit fullscreen mode

3.revert Statement: The revert statement allows you to explicitly revert the transaction and provide an optional error message. It is typically used for explicit error handling and can include custom error messages to provide more information.

function withdraw(uint256 amount) public {
    if (amount > balance) {
        revert("Insufficient balance");
    }
    // Withdraw logic
}
Enter fullscreen mode Exit fullscreen mode

4.Error Propagation: Errors can be propagated from one function to another by using the revert statement or by propagating them through return values or events. It is important to handle and propagate errors appropriately to ensure contract integrity.

function transfer(address recipient, uint256 amount) public {
    require(balance >= amount, "Insufficient balance");
    // Transfer logic
    if (errorOccurred) {
        revert("Transfer failed");
    }
}
Enter fullscreen mode Exit fullscreen mode

Inheritance and Interfaces

1.Inheritance:
Inheritance allows a contract to inherit properties and functions from another contract, referred to as the base or parent contract. This promotes code reuse and enables contracts to build upon existing functionality. To inherit from a contract, the is keyword is used followed by the name of the parent contract.

contract ParentContract {
    // Parent contract variables and functions
}

contract ChildContract is ParentContract {
    // Child contract variables and functions
}
Enter fullscreen mode Exit fullscreen mode

The child contract ChildContract inherits all the variables and functions from the parent contract ParentContract. It can also override inherited functions or add new functions.

2.Interfaces:
Interfaces define a set of function signatures that must be implemented by any contract that adopts the interface. They provide a way to define standardized communication protocols between contracts. Interfaces do not contain any function bodies; they only specify the function names, input parameters, and return types.

interface MyInterface {
    function myFunction(uint256 value) external;
    function myOtherFunction() external view returns (uint256);
}
Enter fullscreen mode Exit fullscreen mode

Contracts that adopt an interface must implement all the functions defined in the interface. This ensures that contracts conform to a specific set of requirements and can interact with each other based on the interface's defined functions.

contract MyContract is MyInterface {
    function myFunction(uint256 value) external {
        // Implementation of myFunction
    }

    function myOtherFunction() external view returns (uint256) {
        // Implementation of myOtherFunction
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, MyContract implements the functions defined in MyInterface. It provides the necessary function implementations to comply with the interface's requirements.

Global Variables

Global Variable Description
msg.sender The address of the sender of the current message (current caller or contract).
msg.value The amount of ether (in wei) sent with the current message.
msg.data The complete calldata of the current message.
block.number The current block number.
block.timestamp The timestamp of the current block (in seconds since the Unix epoch).
block.difficulty The difficulty of the current block.
block.coinbase The address of the miner who mined the current block.
block.gaslimit The gas limit of the current block.
tx.origin The address of the sender of the transaction (originating external user).
address(this) The address of the current contract.
address payable(this) The payable address of the current contract, allowing sending and receiving of ether.
this.balance The balance (in wei) of the current contract.

Imports and Libraries

In Solidity, importing other contracts and using libraries are important mechanisms for code organization, modularity, and reusability. Here's an overview of import statements and libraries in Solidity:

1.Import Statements:
Import statements are used to include external Solidity files or libraries into your contract. They allow you to access the code and definitions from the imported files within your contract.

import "./MyContract.sol"; // Importing a local file
import "github.com/username/MyLibrary.sol"; // Importing a file from a remote location
Enter fullscreen mode Exit fullscreen mode

By using import statements, you can break your code into separate files, import external contracts, or include libraries to leverage existing functionality.

2.Libraries:
Libraries are reusable code components in Solidity that allow you to define and deploy shared utility functions. They are similar to contracts but cannot have any storage variables or receive Ether. Libraries are typically used to reduce code duplication and provide commonly used functionalities.

library MathLibrary {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }
}
Enter fullscreen mode Exit fullscreen mode

To use a library, you can either deploy it as a separate contract or use the using keyword to attach it to a data type. The using keyword allows you to access library functions as if they were member functions of the data type.

using MathLibrary for uint256;

function myFunction(uint256 a, uint256 b) public pure returns (uint256) {
    return a.add(b); // Accessing library function
}
Enter fullscreen mode Exit fullscreen mode

In the above example, the MathLibrary provides the add function, which can be accessed using the using keyword on the uint256 data type.

It's worth noting that starting from Solidity version 0.8.0, external libraries are preferred over internal libraries. External libraries are deployed separately and linked to the contract during deployment, whereas internal libraries are included within the bytecode of the contract itself.

ABI

Function Description
abi.decode(bytes memory encodedData, (...)) returns (...) ABI-decodes the provided data. The types are given in parentheses as the second argument. Example: (uint256 a, uint256[2] memory b, bytes memory c) = abi.decode(data, (uint256, uint256[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 this 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.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. The result is equal to abi.encodeWithSelector(functionPointer.selector, (...))
abi.encodeWithSignature(string memory signature, ...) returns (bytes memory) Equivalent to abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)

Cryptographic Functions

Function Description
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 the 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 an elliptic curve signature. Returns zero on error.
addmod(uint256 x, uint256 y, uint256 k) returns (uint256) Compute (x + y) % k where the addition is performed with arbitrary precision and does not wrap around at 2^256. Assert that k != 0.
mulmod(uint256 x, uint256 y, uint256 k) returns (uint256) Compute (x * y) % k where the multiplication is performed with arbitrary precision and does not wrap around at 2^256. Assert that k != 0.

Solidity Version Pragma

The Solidity version pragma is a statement used to specify the version of the Solidity compiler for contract compilation. It ensures compatibility and avoids potential issues due to breaking changes. Here are examples:

  1. pragma solidity ^0.8.0;: Specifies compatibility with Solidity version 0.8.0 or higher (excluding 0.9.0).
  2. pragma solidity 0.8.7;: Explicitly uses Solidity version 0.8.7.

Conclusion

Congratulations on completing this comprehensive guide to Solidity! We've covered the essential aspects of Solidity, including its syntax, data types, state variables, functions, events, control structures, error handling, inheritance, interfaces, import statements, gas calculations, and cryptography.

Solidity offers immense potential for developing powerful smart contracts on the Ethereum blockchain. By leveraging the features and best practices discussed in this guide, you can create robust, secure, and efficient decentralized applications.

Remember to stay updated with the latest developments in Solidity by referring to the official Solidity documentation and actively participating in the vibrant blockchain community. Engage in discussions, explore open-source projects, and collaborate with fellow developers to expand your knowledge and network.

Thank you for joining us on this Solidity adventure. Happy coding and building innovative decentralized applications with Solidity!

Check out Hack Solidity here

Top comments (0)