No history yet

Solidity Syntax and Variables

The Anatomy of a Solidity File

Every Solidity smart contract lives in a file ending with .sol. At the top of this file, you'll find two crucial lines that set the stage for everything that follows: a license identifier and a version pragma.

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

The first line is the SPDX license identifier, a standardized way to declare how your code can be used by others. Since smart contracts are typically open-source, this is a critical piece of information for building trust in the ecosystem. You're telling the world, "Here are the rules for using my code."

The second line, pragma solidity, locks in the compiler version. This command tells the Solidity compiler which version your code is written for. The caret ^ means the code is compatible with any version from 0.8.20 up to, but not including, 0.9.0. This prevents a newer compiler with breaking changes from compiling your code incorrectly, which could have disastrous financial consequences.

Contracts, State, and Memory

The core of a Solidity file is the contract. Think of it as a class in other object-oriented languages. It's a container for data and functions that live at a specific address on the Ethereum blockchain.

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

contract SimpleStorage {
    // This is a state variable
    uint256 public myNumber;

    // This is a function that modifies the state
    function setNumber(uint256 _newNumber) public {
        // This is a local variable
        uint256 tempNumber = _newNumber;
        myNumber = tempNumber;
    }
}

Inside a contract, variables have distinct lifetimes. Variables declared at the contract level, like myNumber, are called state variables. Their values are permanently stored on the blockchain in the contract's storage. Every time you change a state variable, you are writing a new value to the blockchain, which is a transaction that costs fees.

Variables declared inside a function, like tempNumber, are local variables. They exist only while the function is executing and are stored in memory, which is much cheaper and temporary. Once the function call ends, local variables disappear.

Solidity is statically typed, meaning you must specify the type of every variable. Let's look at the most common ones.

TypeDescriptionExample
uint256Unsigned integer of 256 bits. This is the default for integers.uint256 public balance = 100;
int256Signed integer of 256 bits (can be positive or negative).int256 public credit = -50;
boolA boolean value, either true or false.bool public isActive = true;
addressHolds a 20-byte Ethereum address.address public owner;
address payableSame as address, but with the ability to receive Ether.address payable public wallet;
bytes32A fixed-size byte array of 32 bytes. Efficient for short strings or raw data.bytes32 public data;

While uint256 is standard, you can use smaller integer types like uint8 or uint32 to save space. If you have multiple small variables, the Solidity compiler can sometimes pack them together into a single 256-bit storage slot, reducing gas costs.

Global Context and Immutability

Smart contracts don't run in a vacuum. They can access information about the transaction and the blockchain itself through special global variables. These are essential for authentication and logic.

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

contract ContextExample {
    address public owner;
    uint256 public lastChangeTimestamp;
    uint256 public totalDeposits;

    constructor() {
        // msg.sender is the address that deployed the contract
        owner = msg.sender;
    }

    function deposit() public payable {
        // msg.value is the amount of Ether sent with the transaction
        totalDeposits += msg.value;
        // block.timestamp is the timestamp of the current block
        lastChangeTimestamp = block.timestamp;
    }
}

The msg object provides details about the current function call:

  • msg.sender (address): The address that initiated the call. This is the cornerstone of authentication in Solidity. For example, in a constructor, msg.sender is the creator of the contract.
  • msg.value (uint): The amount of Ether (in wei, the smallest unit) sent with the function call.

Another useful global is block.timestamp, which gives the timestamp of the current block. Keep in mind this can be slightly manipulated by miners, so it's not suitable for high-precision timekeeping.

Finally, sometimes you have a value that should be set once and never changed. Solidity provides two keywords for this, which also help save gas because the values aren't read from storage every time.

  • constant variables must be assigned a value at compile time. Their value is hardcoded directly into the contract's bytecode.
  • immutable variables can be assigned once, either at the point of declaration or within the constructor. This is perfect for values you won't know until deployment, like the address of another contract.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Constants {
    // Value is known at compile time
    uint256 public constant MAX_SUPPLY = 1000;

    // Value is set once during deployment
    address public immutable OWNER;

    constructor() {
        OWNER = msg.sender;
    }
}

Using constant and immutable makes your code's intentions clearer and your contract more efficient.

Quiz Questions 1/6

What does the caret ^ in the line pragma solidity ^0.8.20; signify?

Quiz Questions 2/6

In a Solidity contract, a variable declared at the contract level (outside of any function) is called a ________ variable, and its value is stored permanently on the ________.