Services Approach Projects Research About Request Engagement
EVM & Solidity

Solidity Assembly, Part 0: Contracts, Compilers, and Gas

The starting point for a four part series on Solidity assembly, assuming nothing. What a smart contract actually is, why Ethereum cannot read the language you wrote it in, what the compiler hands you instead, and what gas is really counting. Everything here is vocabulary the rest of the series depends on.

Beginner Solidity 10 min read Aug 11, 2026

This is the starting point for a four part series on Solidity assembly, and it assumes nothing at all. If you already know what a smart contract is, what the EVM does, and what gas measures, skip ahead to Part 1, which is where the series starts reading compiled code.

One thing this article does not cover is what a blockchain is underneath: blocks, hashing, validators, why any of it is hard to tamper with. If you need that layer first, start with what a blockchain actually is and come back. Everything below assumes only that a blockchain is a shared record that many independent machines agree on.

A contract is a program that lives at an address

An Ethereum address usually means a wallet. It can also mean a program.

A smart contract is code that has been deployed to Ethereum and given its own address. Once it is there, three things are true of it. Anyone can call it, because there is no login and no permission to publish a request. It keeps its own data, separately from every other contract. And finally, it is immutable, meaning its code cannot be changed.

That last point is worth sitting with, because most of the security industry around Ethereum exists because of it. A bug in a normal web service is fixed by deploying a new version over the old one. A bug in a deployed contract is fixed by deploying a different contract and persuading everyone to use that one instead, while the broken version stays where it is, still callable, still holding whatever people put into it.

Contracts also cannot act on their own. There is no timer, no scheduler, and no way for a contract to wake itself up. A contract runs only because something called it, and services that claim to run a contract every hour are bots off the network signing and sending an ordinary transaction on a schedule.

Only a key can start anything

An account controlled by a private key can sign a transaction. Whoever holds the key can sign, and a signature is what allows it onto the network. An account created by deploying a contract has no key behind it, so it can never be the origin of anything. It can be called, and while it is running it can call other contracts, but the first step of every chain of execution is always a signature.

What matters is whether a private key sits behind the account, not whether the account has code. That distinction used to be the same thing and no longer is, because a 2025 protocol change lets a key controlled account attach code to itself. For everything in this series you can read it the simple way: keys start things, contracts respond.

Solidity is a language, and Ethereum does not speak it

Solidity is a programming language for writing smart contracts. It is the most common one, but there are others.

Ethereum has no idea it exists. There is no Solidity anywhere on the network, no Solidity in any block, and nothing in the protocol that mentions it. Solidity is a convenience for people, and it stops existing at the moment you deploy.

What the network stores and runs is a sequence of numbered instructions. Turning your source into that sequence is what a compiler does, and the Solidity compiler is a program called solc.

What the compiler actually gives you

Compiling produces bytecode: a long run of bytes, where each byte is either an instruction or part of a value that an instruction needs.

Those instructions are called opcodes, and there are about 150 of them. There is an opcode to add two numbers, one to compare two numbers, one to read a stored value, one to stop. Nothing in that set knows what a function is, what a string is, or what a mapping is. Those are Solidity's primitives, and the compiler is responsible for turning them into arrangements of the small instructions that do exist.

This is the single most useful thing to understand before Part 1. Your source code and the deployed contract are two different artifacts, and only one of them is on Ethereum. Everything the series does from here is about the gap between them.

Deployed code is capped at 24,576 bytes, which is why real contracts are much smaller than the codebases that produce them, and why large systems are split across several contracts that call each other.

The EVM is the rulebook, and everyone runs it

The Ethereum Virtual Machine is the definition of what those opcodes do.

It is not a server, and it is not running in one place. It is a specification that every Ethereum node implements, and thousands of independent machines each run their own copy. When a new block arrives, every one of them re-executes every transaction in it against their own copy of the network's state, and they must all arrive at the same answer. A machine that computes something different rejects the block.

The instruction set is small and rigid because every implementation has to agree on it. There is no random number instruction, no clock beyond the block's own timestamp, and no way to read a file or call a website, because two machines doing any of those could get different answers and the network would stop agreeing.

Gas: why running code costs money

Every node re-runs your code. So there has to be something stopping you from publishing a contract that loops forever.

Gas is that something. Every operation has a fixed gas cost set by the protocol, and the total for a transaction is the sum of those operations' costs. Arithmetic is very cheap. Storing data is very expensive, because storage has to be held by every node indefinitely rather than computed once and discarded.

Gas is not a token. It is a count of work, and you pay for it in ETH.

Three numbers matter:

The gas your transaction uses. Determined by what your code does. A plain ether transfer to an ordinary account costs exactly 21,000 gas and nothing more, because 21,000 is charged on every transaction before any code runs at all.

The price per unit of gas. Set partly by the protocol and partly by you. The protocol computes a base fee for each block that moves up when blocks are full and down when they are empty, and that portion of your fee is destroyed rather than paid to anyone. On top of it you add a tip, which does go to the validator who includes your transaction.

The gas limit. A ceiling you set on how much gas the transaction may consume. This is the safety valve.

The two ways a transaction fails

These cost differently, and knowing which is which may save you money.

The contract rejects your request. A require that fails compiles to an instruction called REVERT. Everything the transaction changed is undone, and your unused gas is returned. You pay only for the work actually done up to the point of rejection.

The transaction runs out of gas. Everything is undone as before, but no gas comes back. You are charged the entire limit you set.

In both cases the transaction still happened. It goes into a block, it appears on a block explorer marked as failed, and it uses up its slot in your account's sequence of transactions. Failing is not the same as never having been sent.

Reading is free, writing is not

Requesting information from a contract does not cost anything. When a website shows your token balance, it is asking a node to run the relevant code locally and report back. Nothing is signed, nothing is broadcast, and no block is involved.

Changing anything requires a transaction, and a transaction costs gas. That asymmetry is why applications feel instant when displaying information and slow and expensive when you'd like to transfer ether.

One point of precision, because it matters in Part 1: reading is free from outside, but reading storage from inside a running transaction is one of the most expensive things the EVM does. Those are different operations that happen to use the same English word.

Where a contract keeps things

Three places, with three lifetimes.

Storage is the contract's own long term database, and it is part of the state every node keeps. It is the expensive one. Values written there stay until the contract overwrites them. This is where a token's balances live.

Memory is scratch space used while code runs. Every call into a contract starts with fresh, empty memory, and it is discarded when that call returns. It is cheap.

The stack is where the machine actually does arithmetic, holding a small number of values at a time. Part 1 of this series will be largely about the stack.

The words you will meet in the rest of the series

  • Bytecode: the compiled contract, the thing actually stored on Ethereum.
  • Opcode: one instruction in that bytecode.
  • EVM: the virtual machine that executes those opcodes.
  • solc: the Solidity compiler.
  • ABI: the description of a contract's functions and their argument types, used to work out what bytes to send in order to call a given function.
  • Function selector: four bytes at the front of a call that say which function you want.
  • Inline assembly: a block inside Solidity where you write low level instructions directly instead of letting the compiler choose them.
  • Yul: the language those blocks are actually written in. Part 1 explains why that distinction matters more than it sounds like it should.

The rest of the series

Part 1, beginner: a twelve line contract compiled and read instruction by instruction, including where a single + sign ends up and why it turns into four instructions rather than one.

Part 2, intermediate: writing assembly yourself. When it genuinely saves gas, when it silently costs more, and what the compiler stops guaranteeing the moment you open one of those blocks.

Part 3, advanced: reading contracts you did not write and that have no published source, working out where any value lives, and determining what a proxy points at and who is allowed to change it.

Securing the unseen

Almost everything that goes wrong in a deployed contract lives in the gap this article describes. The compiler decides what your code becomes, the deployed result cannot be edited, and every operation in it costs money.

You do not need to write assembly to benefit from knowing that. You need it to read an audit, to understand why a fix requires a redeployment, and to know that the code someone shows you is not necessarily the code at that address.

Learn more at 0xhades.io/research