DEV Community

Cover image for Solidity Imports Explained: VFS, Remappings, and What Ethereum Actually Sees
Tahzib Mahmud Rifat
Tahzib Mahmud Rifat

Posted on

Solidity Imports Explained: VFS, Remappings, and What Ethereum Actually Sees

Introduction

If Ethereum only stores bytecode…
Then how does it access OpenZeppelin after deployment?

When you write:

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
Enter fullscreen mode Exit fullscreen mode

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") {}
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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:

  • solc CLI → 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/
Enter fullscreen mode Exit fullscreen mode

Why do we write:

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
Enter fullscreen mode Exit fullscreen mode

Instead of:

import "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
Enter fullscreen mode Exit fullscreen mode

The answer is: remappings.

In foundry.toml, you might see:

@openzeppelin/=lib/openzeppelin-contracts/
Enter fullscreen mode Exit fullscreen mode

This means whenever you see:
@openzeppelin/

Replace it with:

lib/openzeppelin-contracts/

So:

@openzeppelin/contracts/token/ERC20/ERC20.sol
Enter fullscreen mode Exit fullscreen mode

Becomes:

lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol
Enter fullscreen mode Exit fullscreen mode

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 ...
Enter fullscreen mode Exit fullscreen mode

Only this gets sent to Ethereum:

0x608060405234801561001057600080fd5b506040516101...
Enter fullscreen mode Exit fullscreen mode

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)