Introduction
If Ethereum only stores bytecode…
Then how does it access OpenZeppelin after deployment?
When you write:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
It feels like your contract somehow “depends” on OpenZeppelin at runtime.
But here’s the truth:
Ethereum never sees your imports.
Ethereum never loads external files.
Everything happens at compile time.
In this article, we’ll break down:
- What actually happens when you write import
- What the Virtual File System (VFS) is
- What an import callback does
- How Foundry remappings work
- What finally gets deployed to Ethereum
Let’s go deep.
1. First Important Concept: Compile Time vs Runtime
There are two completely different phases:
Compile Time
- Solidity files are read
- Imports are resolved
- Code is merged
- Bytecode is generated
Runtime (On Ethereum)
- Only bytecode exists
- No source files
- No imports
- No remappings
- No VFS
This distinction is critical.
Everything related to import happens during compilation not on-chain.
2. What Really Happens When You Write import
Suppose you write:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor() ERC20("Test", "TST") {}
}
Here's what actually happens:
1. Your tool (Foundry/Hardhat/Remix) reads your file.
2. It sees the import.
3. It resolves the path (using remappings if needed).
4. It loads the OpenZeppelin file.
5. It sends all source code to the Solidity compiler.
6. The compiler merges everything.
7. It produces a single bytecode blob.
After that:
There is no import anymore.
OpenZeppelin’s ERC20 code becomes part of your contract’s bytecode.
3. What Is the Virtual File System (VFS)?
The Solidity compiler does not directly depend on your computer’s folder structure.
Instead, it maintains an internal structure called:
Virtual File System (VFS)
Think of it as an in-memory map like this:
"contracts/MyToken.sol" → source code
"lib/openzeppelin/.../ERC20.sol" → source code
Important facts:
- It exists only during compilation.
- It lives inside the compiler process.
- It is not a real folder.
- It disappears after compilation finishes.
The VFS ensures that compilation is deterministic and platform-independent.
4. What Is an Import Callback?
When the compiler encounters:
import "./B.sol";
It must obtain the contents of B.sol.
But the compiler itself does not know:
- How to read from your disk
- How to fetch HTTP
- How to read from IPFS
- How to resolve npm packages
So it uses something called:
Import Callback
An import callback is simply a function provided by the environment that:
- Receives the import path
- Fetches the file
- Returns the file contents to the compiler
Examples:
-
solcCLI → loads from your local filesystem - Remix → can fetch HTTP/IPFS/NPM
- Foundry/Hardhat → read files and pass them via Standard JSON input
The compiler just says:
“Give me the file content for this import.”
The callback does the fetching.
5. Foundry Remappings: Why @openzeppelin/ Works
You may wonder:
If OpenZeppelin is stored locally in:
lib/openzeppelin-contracts/
Why do we write:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
Instead of:
import "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
The answer is: remappings.
In foundry.toml, you might see:
@openzeppelin/=lib/openzeppelin-contracts/
This means whenever you see:
@openzeppelin/
Replace it with:
lib/openzeppelin-contracts/
So:
@openzeppelin/contracts/token/ERC20/ERC20.sol
Becomes:
lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol
Remappings:
- Make imports cleaner
- Avoid hardcoding folder structure
- Improve portability
- Keep dependencies abstract
The compiler itself does not understand @openzeppelin.
Foundry translates it before compilation.
6. What Gets Deployed to Ethereum?
After compilation, the compiler outputs:
- ABI
- Bytecode
- Metadata
When you deploy to mainnet:
forge create MyToken --rpc-url ...
Only this gets sent to Ethereum:
0x608060405234801561001057600080fd5b506040516101...
That hex string is:
Fully compiled machine code.
It already contains:
- ERC20 logic
- Inherited functions
- All storage layout
- Constructor logic
Ethereum does not:
- Fetch OpenZeppelin
- Load external files
- Process imports
- Know about remappings
It simply executes bytecode.
7. Full Lifecycle Summary
Let’s connect everything.
You write:
Solidity files
Imports
Remappings
Foundry:
Resolves remappings
Loads files
Feeds them to the compiler
Compiler:
Stores files in VFS
Resolves imports
Merges code
Produces bytecode
Ethereum:
Receives bytecode
Stores it
Executes it
No imports exist on-chain.
Final Takeaway
Imports are a compile-time convenience feature.
They:
- Help you structure code
- Manage dependencies
- Reuse libraries
But they do not exist at runtime. Once deployed, your smart contract is just bytecode.
Understanding this difference between:
- Compile-time behavior
- Deployment-time artifact
- Runtime execution
Top comments (0)