Skip to content

Solidity

Solidity is the primary high-level language for developing smart contracts on the Ethereum Virtual Machine (EVM). From a technical strategy perspective, Solidity isn’t just a programming language; it is a tool for defining deterministic business logic in a trustless environment.

Solidity is contract-oriented and statically typed. Its execution environment—the EVM—imposes unique constraints, particularly regarding Gas optimization and Persistence.

  • Deterministic Execution: Every node in the network must arrive at the same state transition.
  • Gas Awareness: Every operation carries a cost. Efficient code reduces operational overhead (OpEx) for users.
  • EVM Target: Compiles down to low-level bytecode executed by the global network of nodes.
  • Inheritance & Polymorphism: Supports modular design patterns through interfaces and multiple inheritance.

Choosing the right data location is critical for both performance and cost.

CategoryTypeDescriptionData LocationStrategic Use Case
Value Typesuint256 / int256-bit integers.Stack / MemoryFinancial balances, counters.
address20-byte account ID.Stack / MemoryIdentifying owners or contracts.
booltrue or false.Stack / MemoryStatus flags, logic gates.
enumDiscrete states.Stack / MemoryState machines (e.g. Open/Closed).
Reference TypesmappingKey-value hash map.StorageLarge-scale lookups (Balances).
structCustom groupings.Storage/MemEntities (e.g. Asset details).
arrayFixed/Dynamic lists.Storage/MemIteration or ordered data.
string / bytesDynamic byte arrays.Storage/MemUTF-8 text or raw data.

Strategy Note: Minimize Storage writes. Writing to the blockchain state is the most expensive operation in the EVM. Use Memory for intermediate calculations.


Modifiers are used to change the behavior of functions in a declarative way. They are essential for security and validating pre-conditions.

The _ (underscore) symbol tells the compiler to execute the rest of the function body.

modifier onlyEven(uint256 _val) {
require(_val % 2 == 0, "Not an even number");
_; // Main function body runs here
}

In production, we use industry-standard libraries like OpenZeppelin to handle security. The Ownable contract provides basic authorization via the onlyOwner modifier.

import "@openzeppelin/contracts/access/Ownable.md";
contract SecureVault is Ownable {
constructor() Ownable(msg.sender) {}
// The 'onlyOwner' modifier restricts access to the admin
function emergencyShutdown() public onlyOwner {
// ... logic
}
}

For enterprise solutions, AccessControl allows for multiple roles (e.g., Admin, Auditor, Minter).

import "@openzeppelin/contracts/access/AccessControl.md";
contract ManagedContract is AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
function mint() public onlyRole(MINTER_ROLE) {
// ... execution logic
}
}

Used for currency, reputation points, or voting power.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// Fixed the extensions from .md to .sol
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract ProjectToken is ERC20, Ownable {
// The constructor mints the initial supply to whoever deploys the contract
constructor(uint256 initialSupply) ERC20("ProjectToken", "PTK") Ownable(msg.sender) {
_mint(msg.sender, initialSupply);
}
// This allows YOU (the owner) to mint more tokens later if needed
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}

Used for unique assets such as land parcels, certificates, or IoT device identities. See the full NFT guide with metadata and images.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
// Inherit from ERC721URIStorage instead of standard ERC721
contract MyNFT is ERC721URIStorage, Ownable {
uint256 private _nextTokenId;
constructor() ERC721("MyTestNFT", "MTNFT") Ownable(msg.sender) {}
// We updated safeMint to require a 'uri' when minting
function safeMint(address to, string memory uri) public onlyOwner {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
// This links the specific token ID to your JSON metadata on IPFS
_setTokenURI(tokenId, uri);
}
}

State machines are used to manage complex multi-step processes securely.

contract SimpleEscrow {
enum State { AWAITING_PAYMENT, AWAITING_DELIVERY, COMPLETE }
address public buyer;
address public seller;
State public currState;
modifier onlyBuyer() { require(msg.sender == buyer); _; }
modifier inState(State _state) { require(currState == _state); _; }
constructor(address _seller) payable {
buyer = msg.sender;
seller = _seller;
currState = State.AWAITING_PAYMENT;
}
function confirmDelivery() external onlyBuyer inState(State.AWAITING_DELIVERY) {
currState = State.COMPLETE;
payable(seller).transfer(address(this).balance);
}
}

  1. Reentrancy: Use OpenZeppelin’s nonReentrant modifier.
  2. Checks-Effects-Interactions: Update state before making external calls.
  3. Visibility: Mark functions as external to save gas.
  4. Pull over Push: Let users withdraw funds rather than auto-sending to prevent DoS.