Skip to content
All essays
WebMarch 24, 202413 min

Solidity Smart Contract Basics

Master Solidity fundamentals for Ethereum smart contract development

Ü
Ümit Uz
Mobile & Full Stack Developer

Solidity is the primary programming language for writing smart contracts on Ethereum. It's a statically-typed, high-level language designed for implementing smart contracts on various blockchain platforms.

What is Solidity?

Solidity is influenced by C++, Python, and JavaScript, designed to target the Ethereum Virtual Machine (EVM).

Key Features

  • Static typing
  • Support for inheritance
  • Libraries and complex user-defined types
  • Contract-oriented programming
  • Gas optimization features

Basic Structure

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

contract MyContract {
    // State variables
    uint256 public myValue;

    // Functions
    function setValue(uint256 _value) public {
        myValue = _value;
    }
}

Value Types

Integers

solidity
// Signed integers
int8 smallNumber;
int256 largeNumber;

// Unsigned integers
uint8 counter;
uint256 amount;

// Operations
uint256 a = 5;
uint256 b = 10;
uint256 sum = a + b;

Boolean

solidity
bool isActive;
bool public isRegistered = true;

// Operations
bool result = (a > b) && isActive;

Address

solidity
address public owner;
address payable public wallet;

// Methods
address payable sender = payable(msg.sender);
uint256 balance = sender.balance;

// Transfer
sender.transfer(100);

Bytes and Strings

solidity
// Fixed-size byte arrays
bytes32 public hash;
bytes1 public singleByte;

// Dynamic byte array
bytes public data;

// String
string public name = "My Contract";

// Conversion
bytes32 hash = keccak256(abi.encodePacked("hello"));

Reference Types

Arrays

solidity
// Fixed-size array
uint256[5] public fixedArray;

// Dynamic array
uint256[] public dynamicArray;

// Array operations
dynamicArray.push(1);
dynamicArray.pop();
uint256 value = dynamicArray[0];
uint256 length = dynamicArray.length;

// Memory array
function getArray() public pure returns (uint256[] memory) {
    uint256[] memory arr = new uint256[](3);
    arr[0] = 1;
    arr[1] = = 2;
    arr[2] = 3;
    return arr;
}

Mappings

solidity
// Basic mapping
mapping(address => uint256) public balances;

// Nested mapping
mapping(address => mapping(address => uint256)) public allowances;

// Operations
balances[msg.sender] = 100;
uint256 balance = balances[msg.sender];

Structs

solidity
struct User {
    uint256 id;
    string name;
    uint256 balance;
}

User public user;

function createUser(uint256 _id, string memory _name) public {
    user = User({
        id: _id,
        name: _name,
        balance: 0
    });
}

Functions

Function Basics

solidity
function add(uint256 a, uint256 b) public pure returns (uint256) {
    return a + b;
}

Visibility Specifiers

solidity
// Public - can be called internally and externally
function publicFunc() public {}

// Internal - only within this contract
function internalFunc() internal {}

// Private - only within this contract, not derived
function privateFunc() private {}

// External - only from outside
function externalFunc() external {}

State Mutability

solidity
// View - doesn't modify state
function getBalance() public view returns (uint256) {
    return balance;
}

// Pure - doesn't read or modify state
function add(uint256 a, uint256 b) public pure returns (uint256) {
    return a + b;
}

// Payable - can receive ETH
function deposit() public payable {}

Modifiers

solidity
modifier onlyOwner() {
    require(msg.sender == owner, "Not owner");
    _;
}

function sensitiveFunction() public onlyOwner {}

Events

Events allow logging to the blockchain.

solidity
event Transfer(
    address indexed from,
    address indexed to,
    uint256 value
);

function transfer(address to, uint256 amount) public {
    balances[msg.sender] -= amount;
    balances[to] += amount;
    emit Transfer(msg.sender, to, amount);
}

Error Handling

Require

solidity
function withdraw(uint256 amount) public {
    require(balances[msg.sender] >= amount, "Insufficient balance");
    balances[msg.sender] -= amount;
    payable(msg.sender).transfer(amount);
}

Revert

solidity
function criticalFunction() public {
    if (condition) {
        revert("Something went wrong");
    }
}

Assert

solidity
function internalLogic() public {
    uint256 result = complexCalculation();
    assert(result > 0); // Should never fail
}

Custom Errors

solidity
error InsufficientBalance(uint256 requested, uint256 available);

function withdraw(uint256 amount) public {
    uint256 balance = balances[msg.sender];
    if (balance < amount) {
        revert InsufficientBalance(amount, balance);
    }
    // ...
}

Inheritance

Basic Inheritance

solidity
contract Animal {
    function makeSound() public pure virtual returns (string memory) {
        return "Some sound";
    }
}

contract Dog is Animal {
    function makeSound() public pure override returns (string memory) {
        return "Bark";
    }
}

Multiple Inheritance

solidity
contract A {
    function foo() public pure virtual returns (string memory) {
        return "A";
    }
}

contract B {
    function foo() public pure virtual returns (string memory) {
        return "B";
    }
}

contract C is A, B {
    function foo() public pure override(A, B) returns (string memory) {
        return "C";
    }
}

Interfaces

Interfaces define contract structure without implementation.

solidity
interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract MyContract {
    IERC20 token;

    constructor(address _token) {
        token = IERC20(_token);
    }

    function getTokenBalance(address account) public view returns (uint256) {
        return token.balanceOf(account);
    }
}

Libraries

Libraries are reusable contracts without state.

solidity
library Math {
    function max(uint256 a, uint256 b) public pure returns (uint256) {
        return a >= b ? a : b;
    }
}

contract MyContract {
    using Math for uint256;

    function findMax(uint256 a, uint256 b) public pure returns (uint256) {
        return a.max(b);
    }
}

Global Variables

Block and Transaction

solidity
uint256 blockNumber = block.number;
uint256 timestamp = block.timestamp;
address sender = msg.sender;
uint256 value = msg.value;
bytes4 signature = msg.sig;
bytes calldata data = msg.data;

Contract Information

solidity
address contractAddress = address(this);
uint256 balance = address(this).balance;

Gas Optimization

Use Calldata

solidity
// Expensive
function expensive(string memory data) public {}

// Cheaper
function cheap(string calldata data) public {}

Pack Variables

solidity
// Wastes gas
struct Bad {
    uint128 a;
    uint128 b;
    uint256 c;
}

// Optimized
struct Good {
    uint256 a;
    uint128 b;
    uint128 c;
}

Short-Circuiting

solidity
// Expensive
if (expensiveCheck() || cheapCheck()) {}

// Cheaper
if (cheapCheck() || expensiveCheck()) {}

Security Best Practices

  1. 1Check inputs with require
  2. 2Protect against reentrancy
  3. 3Use SafeMath for critical calculations (pre-0.8.0)
  4. 4Keep functions simple
  5. 5Follow checks-effects-interactions pattern
  6. 6Use Events for important state changes
  7. 7Limit external calls
  8. 8Be careful with loops

Conclusion

Solidity is a powerful language for building smart contracts. Master these fundamentals, and you'll be ready to create secure, efficient contracts for the Ethereum blockchain.

Practice writing contracts, understand gas costs, and always prioritize security. Solidity is your gateway to Web3 development.

Enjoyed this essay? Share it.

Related essays

Next essay
Advanced Code Optimization Techniques