You wrote a perfect Solidity smart contract. Compiled, deployed on Goerli... and the transaction fails with "out of gas". Or the estimated gas is twice what you expected. It happens even to experienced devs: the problem isn't your code, it's the lack of understanding of how the EVM works and gas as a resource.
At Meteora Web, we've been working with blockchain for years. Not just theory: we've deployed contracts in production, optimized operations to reduce costs, and seen projects fail because the team didn't grasp gas mechanics. This guide takes you inside the Ethereum Virtual Machine, explains how every operation is priced, and shows you how to deploy efficient contracts. No academic lectures: everything you need to avoid burning ether unnecessarily.
How the EVM works and why gas is its fuel
The Ethereum Virtual Machine (EVM) is a distributed computer that executes bytecode. Every elementary instruction has a gas cost: a unit measuring computational effort. Gas is not the price in ether: it's the amount of work. You set the price (gas price) and multiplying gives the total cost in ETH.
Bytecode structure and opcodes
Your Solidity contract compiles into a sequence of opcodes. Each opcode has a base cost defined by the Ethereum Yellow Paper. For instance:
ADDcosts 3 gasSLOAD(load from storage) costs 200 gasSSTORE(write to storage) costs 20,000 gas if zero to non-zero, 2,900 if update
The difference is huge. Storage writes are the most expensive because they modify global blockchain state. Storage reads are costly but less. Arithmetic operations are cheap. Understanding this lets you design contracts that cost little.
Gas limit and gas used
When you send a transaction, you set a gas limit — the maximum gas you're willing to consume. If the contract consumes more, the transaction reverts and gas is consumed up to the failure point. If it consumes less, you pay only what was used. Never set the exact gas limit: leave a margin (e.g., +20%) to avoid unexpected failures.
At Meteora Web, we saw a client lose 0.5 ETH in failures because they used a wallet-computed gas limit without margin. Learning to estimate correctly is free.
Gas: operation costs and how to calculate them
Every operation has a fixed cost plus possible dynamic costs. Storage is the worst. Practical Solidity example:
// Expensive: writes to storage on every call
uint256 public counter;
function increment() public {
counter += 1; // SSTORE: ~20,000 gas
}This function costs about 20,000 gas per call due to storage. Call it 100 times and that's 2 million gas. Now compare with a memory variable:
// Cheap: only in-memory operations
function addInMemory(uint256 a, uint256 b) public pure returns (uint256) {
return a + b; // ADD: 3 gas
}The difference is abysmal. Rule of thumb: if you don't need persistence, use memory or calldata. Avoid storage unless strictly necessary.
Estimating gas with Hardhat
Use Hardhat to estimate gas during development. Example:
const { ethers } = require("hardhat");
async function estimate() {
const Contract = await ethers.getContractFactory("MyContract");
const contract = await Contract.deploy();
const tx = await contract.increment();
const receipt = await tx.wait();
console.log("Gas used:", receipt.gasUsed.toString());
}Compare estimates before and after optimizations. We always use gasUsed in tests to avoid surprises in production.
Optimizing a smart contract to reduce gas
Gas optimization is an art. Here are the techniques we apply at Meteora Web on real projects.
Use uint256 instead of smaller uints
The EVM works on 256 bits. Using uint8 does not save gas; it costs more because the EVM must do conversions. Always use uint256 unless you have storage constraints (e.g., arrays of structs).
Packing in storage
If you have multiple small variables, declare them close together so they pack into one slot. Example:
// Bad: occupies 3 slots
uint128 a;
uint128 b;
uint256 c;
// Good: a and b share a slot
uint128 a;
uint128 b;
uint256 c;Solidity's automatic packing works if variables are declared consecutively and total at most 256 bits. This reduces storage operations.
Use mappings instead of arrays for frequent lookups
Reading an element from a storage array costs SLOAD (200 gas). A mapping has the same cost, but arrays also manage length. For lookups, mappings are more efficient.
Avoid unbounded long loops
A loop that depends on user input can blow up gas. If you must iterate, impose a maximum limit and document it. Better: move logic off-chain and use cryptographic proofs (like Uniswap or ENS).
Deploying on Ethereum: tools and strategies
Deploying a contract also costs gas. Deployment cost is proportional to bytecode size (200 gas per byte) plus creation cost (32,000 gas). A smaller contract costs less.
Hardhat and deployment scripts
We use Hardhat for testnet and mainnet deployment. Minimal script:
async function main() {
const MyContract = await ethers.getContractFactory("MyContract");
const contract = await MyContract.deploy();
await contract.deployed();
console.log("Deployed at:", contract.address);
}Before deploying on mainnet, simulate the cost on Hardhat local or testnet. Use ethers.provider.getFeeData() to get current gas price.
Estimating deployment gas
Hardhat estimates automatically, but you can force a limit. We recommend using 20% above estimate as a safety margin. If the market is congested, consider waiting or using a higher gas price.
Verifying the contract on Etherscan
After deployment, verify the source code. Use the hardhat-etherscan plugin:
npx hardhat verify --network mainnet DEPLOYED_ADDRESSVerification increases user trust and enables interaction directly from Etherscan. We always do it.
Common errors and debugging
We've seen many errors: here are the most frequent and how to avoid them.
Out of gas during deployment
Happens if the bytecode is huge (e.g., contracts with many libraries). Solution: remove unnecessary libraries, use pragma experimental ABIEncoderV2 only if needed, and compile with optimizer enabled (optimizer in Hardhat).
Revert without message
Use require with a message to know where it fails. Or use Hardhat's console.log in tests. Don't leave contracts silent.
Gas price too low
In congested periods, a transaction with low gas price can stay pending for hours. Use ethers.provider.getFeeData() dynamically.
In summary — what to do now
- Study the EVM every time you write a smart contract. Don't assume operation costs. Keep the official EVM documentation open.
- Always measure gas in your tests with Hardhat. Compare optimized and unoptimized versions.
- Apply optimizations: packing, uint256, mappings instead of arrays, bounded loops.
- Deploy with margin: gas limit +20% and dynamic gas price.
- Verify the contract on Etherscan for transparency and interoperability.
The difference between a good contract and a great one lies in the gas details. At Meteora Web, we build contracts that don't make your wallet cry. If you want to dig deeper, check our guide to open source licenses — even for contracts you need to choose the right license.
Sponsored Protocol