Written by
0xIchigo
Published on
January 3, 2024
Copy link

How to Migrate From Ethereum to Solana: A Guide for Devs

What’s this Article About?

Ethereum is one of the most important innovations in recent times. For the first time in history, we have a decentralized, global platform built for social coordination with the potential to revolutionize many industries. Despite its importance, Ethereum’s runtime environment, the Ethereum Virtual Machine (EVM), is not built for consumer-grade applications in its current state. It is a single-threaded, gas-based network with volatile fees. In contrast, Solana emerges as a high-throughput, low-latency network. It offers a parallelized infrastructure with low and predictable fees. It addresses the EVM’s limitations directly and improves upon its original design, making it a compelling choice for developers looking to build scalable and efficient applications.

This article is a comprehensive migration guide for interested EVM developers wanting to build on Solana. It covers the fundamental differences between the two, examining Ethereum and Solana’s consensus mechanisms, how they process transactions, and the languages used to develop smart contracts. Then, it covers Solana’s account model, which proposes a more uniform and multifaceted approach to accounts. The article also explores Solang and Neon EVM, two Solidity-friendly tools that enhance the Solana development experience.

Fundamental Differences

This section explores the critical differences between Ethereum and Solana, two blockchains that aim to create a decentralized state machine for global coordination. However, the two differ significantly in their consensus mechanisms, transaction processing methods, and languages used for smart contract development. A deeper understanding of these fundamental differences illuminates Solana's distinct advantage over Ethereum as a highly performant global state machine.

Consensus Mechanisms

Consensus mechanisms underpin every blockchain network. They are responsible for determining how transactions are verified and how blocks are added to the blockchain in a secure, efficient, and decentralized manner. Both Ethereum and Solana are Proof of Stake (PoS) networks. While they share a common foundation in PoS, their approaches to achieving consensus differ due to their confirmation rules.

Ethereum uses Gasper, a combination of Casper the Friendly Finality Gadget (Casper-FFG), and the LMD-GHOST fork choice algorithm. This combination forms the consensus mechanism that secures Ethereum.

Casper is a PoS-based finality system that upgrades a block’s confirmation status to “finalized.” A block is considered finalized on Ethereum when two-thirds of the total stake votes in favor of that block’s inclusion, and another block is built upon it. Since finality requires two-thirds of the total stake in agreement that a block is canonical, an attacker cannot create an alternate finalized chain without owning or manipulating two-thirds of the total stake, and destroying at least one-third via slashing. Caspers allows new entrants to sync with the canonical chain confidently. 

LMD-GHOST is an acronym for “Latest Message-Driven Greedy Heaviest Observed Sub-Tree.” It is an algorithm that decides which fork of Ethereum to follow. It does this by choosing the fork that has received the most support (i.e., “weight”) from the network’s validators (hence the “greedy heaviest sub-tree”). Then, it ensures that only the most recent message from each validator is considered. Every time a new block is proposed to Ethereum, validators use this rule to decide whether it should become part of the canonical chain.

In comparison, Solana uses Proof of History (PoH) hashes to achieve consensus. Despite its confusing name, PoH is not a consensus algorithm. It is a way to prove time in an adversarial network. More specifically, it is a cryptographic time-stamping function that allows nodes to agree on an order of events without communicating with one another. The leader (i.e., the validator appending entries to the ledger) applies timestamps to blocks to prove that some time has passed since the last block. 

Adding these timestamps establishes a historical record as they prove data existed at a specific time. A historical record is created using a sequential preimage-resistant hash function, where each hash depends on its predecessor. Verifiable Delay Functions (VDFs) are pivotal in generating these hashes. They ensure that each hash builds upon the last and incorporates the time elapsed since then. The integration of VDFs introduces a temporal dimension to the hashing process, making it possible to create a verifiable sequence of time-stamped events on Solana.

Having established how Solana’s Proof of History mechanism creates a reliable sequence of events via cryptographic timestamps, it is vital to understand how this integrates with Solana’s consensus mechanism, Tower BFT. Tower Byzantine Fault Tolerance (BFT) is Solana’s variant of the traditional Byzantine Fault Tolerance consensus model, optimized for PoH. Tower BFT uses the historical record created by PoH as a reference framework. This framework enables validators to efficiently and accurately vote on the ledger's state. Tower BFT functions by having validators make votes that are locked in for a long time, with them becoming more committed as more votes are added. Due to PoH, validators can make faster, more informed decisions about the ledger's state without needing to communicate with one another. Combining PoH and Tower BFT accelerates consensus and enhances the network's security and reliability. The result is a high-throughput, scalable blockchain environment where transactions are confirmed quickly.

Transaction Processing and Execution Environments

A blockchain’s efficiency and scalability are primarily determined by its transaction processing capabilities and execution environment. These factors dictate the speed at which transactions are executed and the cost-efficiency of operations on the network. A blockchain’s approach to transaction processing and the nuances surrounding its execution environments impact the developer experience significantly.

Ethereum’s runtime environment operates as a deterministic, single-threaded stack machine known as the Ethereum Virtual Machine (EVM). It behaves as a mathematical function. That is, the EVM produces a deterministic output when given an input. It’s helpful to define Ethereum as having a state transition function: f(S, T) = S’. Given an old state (S) and a new set of valid transactions (T), the EVM produces a new valid output state (S’). The EVM will always achieve the same final state given the same set of transactions. This is crucial for maintaining consistency across the network’s nodes.

The EVM processes transactions sequentially. Sequential processing ensures that each transaction executes in an environment that accurately reflects the state of the network up to that point. Sequential execution allows for precise state change and gas cost calculations. This predictability gives developers and users a clear understanding of transaction outcomes and costs.

Ethereum simplifies the smart contracts execution environment by adopting a single-threaded execution model. This gives developers the advantage of predictable state changes since each transaction is processed sequentially. Predictable state changes make Ethereum’s development environment arguably more approachable to new developers as they can focus more on their smart contract logic than the complexities of execution. However, this simplicity makes scalability challenging. Sequential processing limits the network’s throughput. This leads to potential congestion, longer transaction wait times, and higher gas fees during high demand. Since every transaction consumes gas, developers are forced to optimize for gas efficiency. Optimization is done to improve the overall user experience and combat congestion times, where inefficient code can render apps unusable for the average user.

Solana developers do not have to worry about optimizing gas usage like Ethereum developers. Solana is designed as a high throughput, low latency state machine known as the Solana Virtual Machine (SVM). A vital component of the SVM is Sealevel. It is a runtime engine that processes transactions in parallel. On Solana, each transaction tells the runtime which parts of state it’ll read or write when executed. The runtime processes non-conflicting transactions and transactions reading the same state in parallel. Sealevel optimizes smart contract execution by distributing the transaction workload across multiple threads on a validator’s hardware. So, while the validator processes a transaction on one core, another transaction can simultaneously be processed on another.

Average Transaction Fee for Solana, Bitcoin, and Ethereum
Source: A Deep Dive on Solana, a High Performance Blockchain Network by Visa

Solana’s innate parallelism significantly reduces transaction costs. Solana transactions have two fees: a base fee and a priority fee. The base fee is fixed per signature at 5000 lamports; most transactions only have one signature. The priority fee is optional, allowing transactions to prioritize themselves against other transactions. Transactions with a higher priority fee are prioritized non-deterministically by the scheduler. Transaction fees often fall below $0.001 USD, with the average non-voting fee in the 0.000005 to 0.00007 SOL range, with the upper bound higher than usual due to a recent increase in using priority fees. At the current SOL price of ~$98.96, this fee translates to roughly $0.000494 to $0.006968 USD. 

Comparing Solana and Ethereum's Fee Markets
Source: A Deep Dive on Solana, a High Performance Blockchain Network by Visa

These fees are also predictable. Solana uses localized fee markets to manage demand. Blockspace is structured to prevent any individual hotspot of activity (e.g., a hyped NFT mint) from dominating blockspace and raising fees across the network. Only transactions trying to access a specific high-demand hotspot will see increased fees. Localized fee markets allow for priority fees without leading to fully-blown gas wars. This localization differs from gas-based networks like Ethereum, where transactions are processed sequentially, and global congestion leads to volatile fees.

Ethereum is a single-threaded runtime environment that processes only one contract at a time. It does not currently take advantage of modern multi-core hardware, resulting in the under-utilization of validator hardware. The desire to maintain low hardware requirements for nodes adds to Ethereum’s transaction processing limitations. Comparatively, Solana’s parallel processing capabilities allow Solana to process more transactions by utilizing all of a validator’s available cores. Coupled with localized fee markets, Solana is a far more performant global state machine. 

Smart Contract Languages

The primary language for smart contract development on Ethereum is Solidity. It is a statically typed, curly-braces language designed to create smart contracts running on the EVM. It is heavily influenced by JavaScript, C++, and Python, making it easy for developers familiar with these languages to pick up.

Yul is an intermediate, low-level language optimized for EVM-compatible blockchains. Yul offers developers greater control over bytecode execution, proving highly efficient for fine-tuning gas consumption and managing other low-level operations. Developers can create more efficient contracts by coding directly in Yul or using it as a compilation target.

Vyper is a popular Pythonic language that emphasizes simplicity and security. It deliberately has fewer features than Solidity as it aims to reduce complexity and potential security vulnerabilities. Vyper’s design philosophy emphasizes readability and auditability above all. This approach has inspired the development of similar languages, such as Fe, and languages compiled to Vyper, like Dasy.

Rust is the lingua franca for smart contract (colloquially referred to as programs) development on Solana. It is a fast, memory-efficient language with performance comparable to C++. These characteristics make it suitable for developing applications for a high-throughput, low-latency network. Rust’s rich type system and ownership model ensure memory and thread safety, forcing developers to build reliable and secure applications. 

While Rust has a steep learning curve for those new to systems programming, the tradeoff is the ability to create robust and efficient code. Most Rust development is done using Anchor. Anchor is an opinionated framework that simplifies program development by reducing boilerplate, performing various standard security checks, and streamlining the (de)serialization process. This makes advanced Rust knowledge less critical for getting started. For context, the Anchor documentation recommends that users be familiar with the first nine chapters of the Rust Book (i.e., be familiar with the Rust basics). Solana Playground has several Anchor tutorials to get you started.

While Rust is preferred, developers are not limited to it. C, C++, and any language that targets the LLVM’s BPF backend (i.e., any language that can be compiled into BPF bytecode) can be used. For those familiar with Pythonic languages such as Vyper, Seahorse Lang allows developers to write programs in Python. Here, developers gain Python’s ease of use while retaining the same safety guarantees as if they coded in Rust. Several helpful Seahorse tutorials, such as Seahorse University and Seahorse Cookbook, are available to get you started programming on Solana today.

Developers migrating from Ethereum to Rust aren’t confined to Rust. While it is recommended to learn and use frameworks such as Anchor and Seahorse due to their popularity and security guarantees, this isn’t always feasible. Thanks to recent developments by Solang and Neon Labs with their compiler and EVM-compatible developer environment, developers can use Solidity in program development. Solidity has a home on Solana. To get started with Solidity program development, we need to dive deeper into the nuances of Solana’s programming model. Understanding Solana’s account model is crucial for developers making this transition, as it forms the foundation for program development and interaction on the network.

Understanding the Account Model

Ethereum categorizes accounts into two main categories: externally owned accounts (EOAs) and contract accounts. EOAs are the standard type of account for users. They are controlled by private keys and can hold Ether balances, send transactions, and interact with contract accounts. On the other hand, contract accounts are distinct in that their operations are governed by the smart contract code embedded within them. They cannot initiate transactions autonomously and can only act in response to transactions received from EOAs or other contract accounts.

Solana adopts a more uniform account model, treating accounts as multifaceted containers that store data persistently. This model allows any account to be a program, blurring the traditional boundary between an account and a smart contract. Unlike Ethereum, where code and state are combined into a single account, Solana programs are stateless. This means they do not store any state internally. Instead, all the data they need to operate on is stored in a separate account and is passed in by reference with a transaction. Passing in accounts by reference allows for one generic program deployment that can interact with different accounts. Solana’s account model separates code and data, fostering a more efficient and modular development environment. This is advantageous for users who want to interact with multiple protocols without moving assets across different programs. Everything on Solana is an account. 

Solana accounts can be generalized as executable and non-executable accounts. Simply put, executable accounts are accounts capable of running code. Non-executable accounts are used for data storage without the ability to execute code. This is because they don’t store any code.

A Program on Solana

Executable accounts are accounts that hold programs. Programs can own additional accounts, read from or credit other accounts, modify data, or debit the accounts they own. Executable accounts can broken down further into on-chain or native programs. On-chain programs are user-written code deployed to the network. They can be upgraded by their upgrade authority, which is typically the account that deployed them. Native programs are a special subset of executable accounts, analogous to Ethereum’s precompiled contracts, but with broader functionality. These programs are integrated into Solana’s core, providing the necessary functionality for validators to operate. An example of a native program is the System Program. This program is responsible for creating new accounts, allocating account data, assigning accounts to programs, transferring lamports from accounts owned by it, and paying transaction fees. A full list of native programs can be found here.

Regardless of whether they are executable or non-executable, all accounts have the same fields: 

  • A lamports field for tracking an account's native balance in SOL
  • A data field, which refers to the raw data byte array stored by the account
  • An owner field indicating the program that can modify this account
  • A signer field, which is used for transactions to indicate whether the account can approve transactions
  • A writable field to specify whether an account’s data can be modified. This is to facilitate parallel processing by marking included accounts in transactions as either read-only or writable
  • An executable field to indicate whether an account stores a program
  • A rent epoch field to indicate the next epoch that the account owes rent

Rent is a storage cost to keep accounts alive on Solana and ensure they are held within validator memory. This requires accounts to maintain a minimum balance to remain active. Rent reduces state bloat by ensuring the network eventually recovers unused or underfunded accounts. Recent updates have made it so that there are no rent-paying accounts on mainnet. Instead, all accounts must be rent-exempt upon creation. An account is rent-exempt if it maintains a minimum balance equivalent to two years’ rent payments. Tools such as Test Drive and the Solana CLI’s rent sub-command can be used to estimate the amount of SOL needed for an account to be rent-exempt. This differs from Ethereum’s resource allocation system, where storage persists unless explicitly cleared. Solana’s approach offers a more predictable cost structure for state storage while reducing state bloat.

Addresses and Program Derived Addresses (PDAs)

ed2559 Elliptic Curve
Source: Generating PDAs section in “Program Derived Addresses (PDAs)” for the Solana Cookbook

All accounts are identified by their address, a unique 32-byte public key. This differs from Ethereum’s data model, where addresses are 20 bytes. Solana uses ed25519 (i.e., an EdDSA signature scheme that uses SHA-512 and the Curve22519 elliptic curve) to generate addresses. An account must be a point on the ed25519 curve to have a valid keypair. 

Program Derived Addresses (PDAs) are accounts generated off-curve using a bump (i.e., a value that nudges the output off the curve). PDAs require three main parts: the address of its parent program, a set of seeds, and a bump. The seeds are an array of strings that can be of arbitrary value. However, most developers create specific seeds related to state variables in the parent program to create hashmap-esque data structures. Thus, a PDA is created by hashing the program ID, seeds, and bump using the SHA-512 hash function.

The purpose of PDAs being off-curve is so that only the program a PDA is derived from can sign on behalf of the PDA. This streamlines the transaction flow by generating transaction signatures programmatically so that trustless dApps can operate smoothly without intervention.

Interacting with an Account

An Ethereum transaction is a state-changing action initiated by an EOA. Smart contracts cannot initiate transactions independently; they can only react to them. The contract’s code dictates these reactions, with the cost of transaction execution measured in gas. Users must supply enough Ether to cover this gas fee.

Ethereum transactions typically perform a single operation, such as calling a specific smart contract function. Although a single transaction can lead to multiple state changes within a contract, all changes are confined to the scope of that single contract call.

A Solana Transaction

On Solana, a transaction comprises an array of accounts to read or write from, one or more signatures, and one or more instructions. An instruction is a directive for a single program invocation. It is the smallest unit of execution logic and is the most fundamental operational unit. Instructions specify the executing program, all the accounts involved, and the operational data. Programs interpret the data from an instruction and operate on the specified accounts. This structure enables a single transaction to perform a series of actions across multiple programs in an atomic manner. This means that all instructions either succeed or fail together. Solana’s transaction flow differs from Ethereum’s model, where a transaction is typically linked to a single smart contract or an EOA action.

Cross-Program Invocations (CPIs) enable a program to invoke another program during the same transaction. The number of account data bytes per compute unit charged during a CPI is 250. This is roughly 50MB at 200,000 units. CPIs allow programmatically generated signatures to be used when calling between programs. This is similar to an Ethereum smart contract calling another contract efficiently and atomically. However, the invoking program is halted until the invoked program finishes processing the instruction. Reentrancy is limited to direct self-recursion capped at a fixed depth of 4. This prevents situations where a program invokes another from an intermediary state without the knowledge that it might later be called back into.

Legacy Account Lookup vs Address Lookup Tables

Solana transactions adhere to a size limit for efficiency, similar to Ethereum’s gas limit. However, the focus is on data size. Solana transactions adhere to IPv6 Maximum Transmission Unit (MTU) standards to guarantee reliable data transmission. After setting aside the necessary header space, 1232 bytes are available for packet data. Solana introduced versioned transactions to support multiple transaction formats to overcome size constraints. In addition to the legacy format (i.e., the original transaction format), Version 0 was released to support Address Lookup Tables (ALTs). ALTs store addresses in a table-like data structure on-chain where each address is indexed using a 1-byte u8 index. This significantly reduces transaction sizes as each account only needs 1 byte instead of 32 bytes.

Solang

Solang is a Solidity compiler for Solana and Polkadot. It aims to achieve source file compatibility with version 0.8 of the EVM’s Solidity compiler, albeit with a few variations to align with Solana’s and Polkadot’s architectures. A significant aspect of Solang is its use of LLVM, a powerful compiler infrastructure. LLVM’s flexibility allows Solang to extend support to other programming languages in the future, facilitating easier implementations and compilations. This characteristic aligns with Solang’s goal of easing the transition for developers to Solana or Polkadot and broadening the scope of Solidity development. Solang is under continuous development, focusing on enhancing compatibility, efficiency, and user-friendliness. It is essential for Ethereum developers looking to take their skills cross-chain to understand Solang and its caveats.

Installation

There are a few ways to install Solang. On Mac, users can download Solang with Brew using a private tap: brew install hyperledger/solang/solang. Another way to install Solang is to use Solang containers. This is perfect if you prefer using Docker, as new images are automatically made available on these containers. There is a v.0.3.3 tag and a latest tag: docker pull ghcr.io/hyperledger/solang:latest

One of the easier ways to install and use Solang is through Ancor. To start, ensure Rust and Node.js are installed on your system. Windows users will also need to set up Windows Subsystem for Linux. Now, we need to install Solana’s Tool Suite. On Mac and Linux, this can be done with the following command: sh -c "$(curl -sSfL https://release.solana.com/v1.17.13/install)"

If you prefer a different software version, replace “v1.17.13” with the appropriate release tag. Alternatively, you can use one of the following symbolic channel names: stable, beta, or edge. Depending on your system, you may need to update your PATH environment variable. If you encounter this message, copy and paste the recommended command below to update PATH. You can confirm your desired version of solana by running solana --version.

On Windows, open up an instance of Command Prompt as an Administrator. Copy and paste the following command to download the Solana installer into a temporary directory:


cmd /c "curl https://release.solana.com/v1.17.13/solana-install-init-x86_64-pc-windows-msvc.exe --output C:\solana-install-tmp\solana-install-init.exe --create-dirs"

Then, copy and paste the following command to install the latest version of Solana: C:\solana-install-tmp\solana-install-init.exe v1.17.13. When the installation is finished, press Enter. Close the command prompt window and re-open a new instance as a regular user. Confirm the desired version of solana is installed by running solana –-version.

Next, install Anchor. It is recommended to install Anchor using the Anchor version manager (avm). This can be done via cargo with the command: cargo install -git https://github.com/coral-xyz/anchor avm --locked --force. Then, install and use the latest version:


avm install latest
avm use latest

# Verify the installation
avm –version

Anchor version 0.28 allows developers to build with Solang directly. Developers can create a new Solang project with the command: anchor init project_name —solidity. This creates a new Solang program and a test file demonstrating how to interact with the program via the client.

Consider installing the Solang extension to assist with syntax highlighting if you use Visual Studio Code. Disable any other active Solidity extensions to ensure the Solang extension works correctly.

Creating a New Project

Create a new project with the command anchor-init project_name –solidity. This creates a new directory with your project’s name. The Solidity flag tells Anchor that we want to use Solang. The project’s ./solidity directory will provide a starter program. The contract will look like the following:


@program_id("F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC")
contract test {
    bool private value = true;

    @payer(payer)
    constructor() {
        print("Hello, World!");
    }

    /// A message that can be called on instantiated contracts.
    /// This one flips the value of the stored `bool` from `true`
    /// to `false` and vice versa.
    function flip() public {
            value = !value;
    }

    /// Simply returns the current value of our `bool`.
    function get() public view returns (bool) {
            return value;
    }
}

This program has a constructor to create the program, initialize the state variable value to true, and log “Hello, World!” to the program logs. The flip function updates the state variable when called. The get function returns the current value of the state variable. This looks like a normal Solidity smart contract with a few caveats. The most significant difference is the use of annotations.

Annotations

Annotations are used for account management. Notice the @program_id(“...”) annotation. This is used to specify the on-chain address of the program if it is known beforehand. If you want to call a contract via an external call, the program must have either the @program_id notation or be called using the {program_id: … } argument:


@program_id(“...”);

contract Foo {
	function hello() public pure {
		print(“Hello”);
	}
}

contract Foo2 {
	function bye() public pure {
		print(“Bye”);
	}
}

contract Bar {
	function new_foo() external {
		Foo.new();
	}
}

contract Bar2 {
	function new_foo(address new_foo_id) external {
		Foo2.new{program_id: new_foo_id}();
	}
}

When we created a new project, the provided sample contract had the @payer annotation over the constructor. This annotation defines the account that will pay for the initialization of the program’s data account. The @payer(payer) syntax declares an account named payer, which will be required for every call to the constructor.

When a contract is instantiated, it requires a program account to hold the executable code and a data account to save the state variables. The data account can be created using client-side code and then passed into the transaction that invokes the constructor. Alternatively, the data account can be created by the constructor. At a minimum, the @payer annotation must be provided. A seed and a bump must be provided if the data account will be a PDA. The @seed annotation specifies a seed for deriving the PDA, and can be a string literal or a hex string with the format hex”1234”. The seed annotation must refer to an argument of type bytes, address, or fixed length byte array if it is before an argument. The @bump annotation specifies the value used to produce an off-curve address. It must be a single byte of type bytes1. The optional @space annotation can be used to specify the size of the data account. It is a uint64 expression that can be a constant or use one of the constructor arguments. According to the Solang documentation, at a minimum, the @space should be the size given when running the solang -v command:


$ solang compile --target solana -v examples/solana/flipper.sol
...
info: contract flipper uses at least 17 bytes account data
…

If a program does not have a constructor, these annotations can be paired with an empty constructor. The Solang documentation provides the following example:


@program_id("Foo5mMfYo5RhRcWa4NZ2bwFn4Kdhe8rNK5jchxsKrivA")
contract Foo {

    @space(500 + 12)
    @seed("Foo")
    @payer(payer)
    constructor(@seed bytes seed_val, @bump bytes1 bump_val) {
        // ...
    }
}

Function annotations are used to declare the necessary accounts for external functions:

  • @account(foo) declares the foo account as a read-only account
  • @mutableAccount(bar) declares the bar account as a mutable account
  • @signer(fizz) declares the fizz account as a read-only signer
  • @mutableSigner(buzz) declares the buzz account as a mutable signer

Accounts declared with the @payer annotation on the constructor are available to access inside of it. Accounts declared with function annotations are available in the tx.accounts vector. For example, you’d use tx.accounts.ichigo to access the account declared @account(ichigo). This will return the AccountInfo built-in struct. This struct follows the same structure we outlined in the “Understanding the Account Model” section. The naming is a bit different:

  • key: the address or public key of the account. It is of type address
  • lamports: the account’s lamport balance. It is of type uint64
  • data: the account’s data. It is of type bytes
  • owner: the account’s owner. It is of type address
  • rent_epoch: the next epoch when the account’s rent is due. It is of type uint64
  • is_signer: specifies whether the account signed the transaction. It is of type bool
  • is_writable: specifies whether the account is writable in this transaction. It is of type bool
  • executable: specifies whether the account is a program. It is of type bool

Limitations

The main incompatibilities of Solang and traditional Ethereum development are:

  • msg.sender is not available on Solana. With the account model, a Rust contract can access various data accounts, and which of those would we consider the caller? There are many cases where we cannot identify a single account as the caller. Also, the runtime does not have a mechanism to fetch caller accounts
  • There is no ecrecover() function, but a signatureVerify() function to check ed25519 signatures exists.
  • Try-catch statements don’t work. The runtime will halt execution and revert the entire transaction if any external call or contract creation fails.
  • Error definitions and reverts with error messages are not yet working
  • Transferring native value with a function call doesn’t work.
  • Many Yul built-ins are not available. Solang does support most built-ins; however, memory and chain operations are not implemented.
  • The ERC-20 format is not currently supported. SPL Tokens are defined according to the Token Program. The Token Program is Solana's native way of creating, minting, transferring, and burning tokens. The spl_token.sol file should be copied into your source tree, and imported where required to use the SplToken library

Moreover, the SVM’s registers are 64-bits wide, meaning 64-bit integers (i.e., uint64 and int64) are preferable over 256-bit integers. An operation with types wider than 64 bits, such as uint256 or int256, is split into multiple operations. This makes them slower and consumes more compute units.

Regarding addresses, an address literal has to be specified using the address"36VtvSbE6jVGGQytYWSaDPG7uZphaxEjpJHUUpuUbq4D" syntax. Ethereum’s hex syntax (e.g., 0xE0f5206BBD039e7b0592d8918820024e2a7437b9) is not supported. All balances and values on Solana are 64-bit wide. This means that built-in functions for address (i.e., .balance(), .transfer(), and .send()) use 64-bit integers.

While Solang offers an exciting pathway for EVM developers to venture into the Solana ecosystem, it has several limitations that require careful consideration. The shift to Solana’s account-based model is not merely syntactic - it represents a fundamental change in smart contract logic. Certain functions, coding patterns, and EVM-specific features are not available with Solang. Developers cannot port an Ethereum smart contract to Solang and expect it to work without significant adjustments. Missing features, such as the lack of proper error definitions and reverts with error messages, can be quite damning. However, it is important to recognize that Solang is a constantly evolving tool, with every update aimed at improving the Solidity developer experience on Solana. There are several distinct advantages that Solang offers to make this experience better.

Advantages

Despite these limitations, there are a number of built-in functions and design choices to improve the developer experience. For example, contracts developed in Solang can interact with Anchor programs. This can be done by generating a Solidity interface from an Anchor program’s IDL. IDL stands for “Interface Description Language.” Essentially, it is a JSON file that contains all the specifications for a program. It includes everything you need to know to interact with an Anchor program. Anchor automatically generates an IDL when developing a program using its framework. This is very similar to ABIs on Ethereum. To generate a Solidity interface from an IDL, use the following command: solang idl [-output directory] [IDL file]. Now, the file can be imported using the import “...”; syntax.

Solang provides Solana library, a series of libraries for Solidity contracts to interact with Solana-specific instructions. Solang provides the SPL Token library for minting, burning, and transferring tokens. It can be thought of as an ERC-20 and ERC-721 equivalent. Solang also provides the System Instructions library so developers can interact with Solana’s System Program.

Solang also includes some built-in functionality that can be imported with solana. This includes the AccountMeta and AccountInfo structs. The AccountMeta struct is used to specify which accounts should be passed to the callee in an external call (i.e., a CPI). The AccountMeta struct has the following structure:

  • pubkey: the address or public key of the account. It is of type address
  • is_writable: specifies whether the callee can write to this account. It is of type bool
  • is_signer: specifies whether the callee can assume this account signed the transaction. It is of type bool

If the accounts argument is omitted in an external call, the Solang compiler automatically generates an AccountMeta array. This only works if the function is declared as external. If not, the AccountMeta array must be created manually according to the account ordering specified in the IDL. If a certain call does not need accounts, pass in an empty vector: {accounts: []}. The Solang documentation provides a solid example of how to build the AccountMetas array:


function build_this() external {
	// When calling a constructor from an external function, the data account for the contract
	// 'BeingBuilt' should be passed as the 'BeingBuilt_dataAccount' in the client code.
	BeingBuilt.new("my_seed");
}

function build_that(address data_account, address payer_account) public {
	AccountMeta[3] metas = [
		AccountMeta({
			pubkey: data_account,
      is_signer: true,
      is_writable: true
		}),
    AccountMeta({
    	pubkey: payer_account,
      is_signer: true,
      is_writable: true
    }),
    AccountMeta({
      pubkey: address"11111111111111111111111111111111",
      is_writable: false,
      is_signer: false
    })
  ];
  BeingBuilt.new{accounts: metas}("my_seed");

	// No accounts are needed in this call, so we pass an empty vector.
	BeingBuilt.say_this{accounts: []}("It's summertime!");
}

Solang also has built-in functions for:

Solang bridges the gap between Solana and Ethereum by providing a compiler with a rich toolset that eases the transition for EVM developers. Albeit its limitations, Solang offers several features to enhance the developer experience, including the ability to interact with Anchor programs, access Solana-specific libraries, and built-in functions. Integrating familiar concepts such as IDLs and token standards further eases the learning curve. Not having to worry or optimize for gas is a welcomed addition. Solang represents a significant step toward interoperability between Solana and Ethereum. For EVM developers looking to expand into the highly performant world of Solana, Solang emerges as a possible tool.

Neon EVM

Neon EVM

Solang is not the only option for Solidity developers looking to build on Solana. Neon EVM describes itself as the world’s first parallelizable EVM. It is a fully compatible Ethereum environment on Solana. This is a synergistic solution for anyone looking to scale Ethereum dApps on Solana in a developer-friendly way. Developers can deploy their dApps without reconfiguring their smart contracts, written in the languages they love, using the tools they love.

Architecture

Neon EVM consists of three main components: the Neon EVM program, Neon Proxy, and Neon DAO.

The Neon EVM is a Solana program that accepts Ethereum-like transactions and processes them on Solana according to the EVM’s rules. These Ethereum-like transactions directed towards Neon EVM are referred to as Neon Transactions. They are a subset of JSON RPC methods according to the Ethereum JSON-RPC API.

The Neon Proxy allows Ethereum developers to port their dApps to Neon with minimal changes. It packages EVM transactions into Solana transactions, acting as a containerized solution for Neon Operators. These operators run Neon Proxy servers, accept payments in NEON, and make payments within the Solana ecosystem in SOL. NEON is both a utility and a governance token: Neon Operators collect it to pay for gas fees required for transaction execution, and owners may participate in Neon DAO.

Neon DAO is a community-driven governance model designed to empower NEON token holders in decision-making processes for the Neon EVM. It comprises users, operators, contributors, core and application developers who collectively deliberate on governance rules and the evolution of the protocol. The DAO operates through various decentralized assemblies focused on the ecosystem, development, and security. Each assembly facilitates collaborative decision-making and proposal vetting for their respective domain. The ecosystem-focused assembly oversees the sustainable growth of the ecosystem, managing funds for grants and initiatives. The development-focused assembly handles technical upgrades to the Neon program and emergency interventions. The security-focused assembly safeguards Neon’s treasury and the Neon program against potential threats.

EVM Compatability

Neon EVM facilitates EVM interactions on Solana by:

  • Implementing most of Ethereum’s JSON-RPC API methods
  • Utilizing a special proxy to handle Ethereum calls
  • Adapting to the differences and limitations posed by Solana’s architecture

Interacting with Neon EVM is similar to engaging with any other EVM. Developers can use familiar RPC API methods directed to the Neon Proxy, facilitating a seamless developer experience. Key features include:

  • Compatibility with Solidity and Vyper smart contracts, as well as standard Ethereum development tools like Metamask, Foundry, and Remix
  • Support for most Ethereum Opcodes verbatim
  • Acceptance of type 0 / legacy Ethereum transaction requests. EIP-1559 transactions are not currently supported

Of course, certain adaptations are necessary for Neon EVM to function correctly on Solana. Notable differences include:

  • Neon EVM supports all precompiled contracts defined on evm.code. However, Solidity contracts that contain the following calls will not be executed: bigModExp, bn256Add, bn256ScalarMult, and bn256Pairing. The Neon EVM requires the implementation of Solana system calls to support these contracts in the future
  • While the majority of opcodes are supported verbatim, the COINBASE, PREVRANDAO (FKA DIFFICULTY), GASLIMIT, BASEFEE, and GAS opcodes are not. These are known as variant opcodes, and are adapted for use in the Neon EVM.
  • Gas consumption and fee calculations differ from Ethereum. They generally result in lower costs due to Solana’s role as the settlement layer
  • Solidity’s transfer() and send() methods are not reentrancy safe in Neon EVM due to different gas calculations
  • Solana’s account model impacts smart contract storage with different storage functionalities and access permissions for executable and non-executable accounts
  • Neon EVM uses versioned transactions, limiting the maximum number of accounts used in a single transaction to 64
  • Neon EVM uses Solana’s Berkley Packet Filter (BPF) with a heap memory limit of 256 KB. This restricts the size of contract calls and necessitates optimization strategies to manage memory usage effectively
  • Time-based functions such as block.number and block.timestamp behave differently. Developers are strongly cautioned against using them when developing on Neon EVM

While Neon EVM provides a familiar, compatible environment, EVM developers must be aware of and adapt to the key differences to successfully migrate and build on Solana.

Connecting to a Neon RPC

Chainlist

Developers can easily connect to a Neon RPC using Chainlist. Here, developers can connect to either the Neon EVM Mainnet or Devnet. Click Connect Wallet on either the Mainnet or Devnet modal, and click Approve when your wallet pops up.

Users should choose the optimal operator before sending transactions to the Neon EVM. Expand the card details to view the available RPC endpoints for each network:

If you opt for an Operator different from the default one provided during wallet connection, a manual connection will be necessary. The Neon EVM documentation provides comprehensive guides on connecting to a Proxy using Foundry, Hardhat, Remix, and Truffle. We’ll cover how to connect with Foundry under the deployment section.

Once connected, developers can use the Neon Faucet to acquire NEON or other ERC-20 test tokens. Developers can also use the request_neon endpoint to request tokens programmatically:


curl -i -X POST \
	-d '{"wallet": "Your wallet", "amount": 1}' \
	'http://localhost:3333/request_neon'

This command sends a POST request to receive tokens to the specified wallet address.

Transaction Lifecycle and Gas Fees

There are three primary steps for executing a transaction from an Ethereum dApp on Solana through the Neon EVM:

  • A user initiates a signed Ethereum-like transaction directed at a Neon RPC endpoint
  • The transaction is passed to the Neon Proxy via the Ethereum API. The Proxy estimates the gas required to execute the transaction, initiates a broadcast by wrapping the Ethereum-like transaction into a Solana transaction, and sends the wrapped transaction to the Neon EVM. This results in a Solana receipt and a corresponding Neon EVM receipt of the transaction. The Neon smart contract unwraps the transaction, verifies the user’s signature, and loads the EVM state from Solana storage. The transaction is executed inside Solana’s BPF
  • Solana and Neon EVM update their states to complete the transaction request

This is the entire transaction lifecycle, from initiation to execution, in the Neon EVM. Developers can visit NeonScan to see the latest transactions and blocks and query accounts, tokens, blocks, or transaction hashes.

Developers can also send gasless transactions. This was implemented to support users with insufficient NEON tokens to cover their initial transaction fees. Developers can obtain a starter pack of gasless transactions by contacting info@neonevm.org. The developer’s chosen Proxy Operator will still handle these transactions; however, Neon covers the transaction cost. A minimum of three gasless transactions is typically offered per new Neon account.

The process for gasless transactions is as follows:

  • An end-user initiates a transaction via a dApp
  • The dApp requests the current gas price from their chosen Proxy Operator. If eligible, the Proxy Operator marks the Neon account for a specified number of gasless transactions
  • For those transactions, the dApp will show a gas cost of zero
  • The end-user signs the transaction with no gas fee
  • The Proxy Operator executes the transaction, with the SOL gas fee paid by the Neon Foundation

The Neon EVM documentation provides the following extract to demonstrate how to request a gasless transaction:


try {
	// Get gasless transaction if user account is eligible
  const rawGasPrice = await axios.post(rpcApiUrl, {
  	method: 'neon_gasPrice',
    params: [{ from: address }],
    jsonrpc: "2.0",
    id: new Date().getTime()
   })

   tx.gasPrice = rawGasPrice.data?.result;
	} catch (e) {
  	//Else, get standard GAS price
   	setError('Can\'t retrieve gas price for transaction')

    const rawGasPrice = await web3.eth.getGasPrice();

    tx.gasPrice = web3.utils.toHex(rawGasPrice);
} finally {
    setTx(tx)
}

NeonPass

NeonPass is a tool for transferring tokens between Solana and the Neon EVM. It allows seamless asset transfers between Solana’s Associated Token Accounts and Neon EVM’s ERC-20 token accounts. NeonPass uses Neon EVM’s interface contract and specialized account storage. Solana Program Library (SPL) tokens are packaged into an ERC-20 interface within Neon EVM’s factory contract. This allows the SPLs to be stored in ERC-20 token accounts compatible with Solidity dApps. NeonPass allows the bidirectional transfer of tokens between Solana and Neon EVM accounts. Unlike traditional bridges that lock and mint new assets, it moves tokens directly between the two account types. This enables a seamless transition of Solana and Neon EVM tokens.

Deployment

Developers can deploy to the Neon EVM using Hardhat, Foundry, Truffle, and Remix. Since the easiest way to use Solang is through Anchor, all the testing is done in TypeScript. However, we can finally test and deploy Solidity dApps to Solana via Foundry for all the Solidity maxis.

First, ensure you have an EVM-compatible wallet connected to Neon EVM Devnet. Then, clone Neon’s example Foundry project and navigate to it:


git clone https://github.com/neonlabsorg/neon-tutorials
cd neon-tutorials/foundry

Then, install Foundryup, the Foundry toolchain installer and run foundryup to install the latest (nightly) precompiled binaries (i.e., force, cast, anvil, and chisel):


curl -L https://foundry.paradigm.xyz | bash
foundryup

Install the required libraries:


forge install foundry-rs/forge-std --no-commit
forge install openzeppelin/openzeppelin-contracts --no-commit

Now, obtain the private key for your wallet account. In Metamask, for example, you can view your private key by clicking the hamburger menu and navigating to Account Details > Show Private Key. It’ll prompt you to enter your password. Click Confirm to access your account’s private key. Remember not to share this key with anyone; take the necessary steps to protect it.

Then, create an .env file with the following variables:


RPC_URL_DEVNET=https://devnet.neonevm.org
CHAIN_ID_DEVNET=245022926
RPC_URL_MAINNET=https://neon-proxy-mainnet.solana.p2p.org
CHAIN_ID_MAINNET=245022934
PRIVATE_KEY=
VERIFIER_URL_BLOCKSCOUT=https://neon-devnet.blockscout.com/api

Replace <YOUR_PRIVATE_KEY> with your private key and run source .env.

To compile the project's contracts, navigate to the src directory and run forge build. The console should return that the compiler run was successful. The contracts can also be tested using the forge test command. To deploy the project’s contract, run the following command:


forge create --rpc-url $RPC_URL_DEVNET --private-key $PRIVATE_KEY src/TestERC20/TestERC20.sol:TestERC20 --constructor-args "Test ERC20 Token" "TERC20" --legacy

You should see output similar to the following:


[⠰] Compiling...
No files changed, compilation skipped
Deployer: 0x4455E84Eaa56a01676365D4f86348B311969a4f4
Deployed to: 0x5537599aa2F97Dd60a66342522a465A7f2e40Ff9
Transaction hash: 0x6de9dab8a526cbac33008056d185b93dff725605efb791bf116b6bece4f0c486

To verify your contract, run the following command:


forge verify-contract --chain-id $CHAIN_ID_DEVNET  src/TestERC20/TestERC20.sol:TestERC20 --verifier-url $VERIFIER_URL_BLOCKSCOUT --verifier blockscout

Replace <contract_address> with your smart contract’s address. You should receive an OK response with a URL pointing to the contract’s address on BlockScout, Neon’s Devnet explorer. You can also configure your .env file to use NeonScan instead of BlockScout, which also supports devnet.

Advantages and Limitations

Developers who want a similar Ethereum developer experience should choose Neon EVM. It is an EVM-compatible environment that sends Ethereum-like transactions and uses familiar tools. Features such as NeonPass, support for most EVM opcodes, and the provision of gasless transactions make the transition to Solana a lot easier. However, the Neon EVM isn’t perfect. It presents certain limitations, such as a change in smart contract logic to adapt to Solana’s infrastructure, operating within the constraints of Solana’s Berkeley Packet Filter (BFP) and account models, and having certain restrictions on opcodes and precompiled contracts. Understanding and navigating these nuances is crucial to deploy to Solana from an EVM-compatible environment successfully.

Past Migrations

Several complex tasks must be achieved for a large Ethereum protocol to deploy on a non-EVM chain. Namely, acquiring non-Solidity engineers to rebuild their codebase from scratch, finding trustable audit partners, re-auditing the new codebase, and adapting governance contracts to enforce DAO decisions. These feats have the potential to cost millions of dollars and take months of dedicated work. This is not feasible for most. However, it has happened before.

Helium is a LoRaWAN network that aims to create a decentralized wireless infrastructure to support Internet of Things (IoT) devices. This is done using hotspots, small, low-power devices akin to miniature cell towers that connect to other hotspots over long distances. Originally on its own Layer 1 (L1) blockchain, the Helium developer team proposed a move to Solana with HIP 70. The move would allow Helium to achieve higher uptime, greater composability, and a faster user experience while maintaining a high level of security and low cost of use. The community voted overwhelmingly in favor of the proposal, and Helium migrated to Solana in April 2023. Helium’s COO Scott Sigel described the migration as a boring event - nothing went wrong with the network or Helium’s infrastructure. It was an engineer’s dream. The team also outlined the entire migration process in a series of guides in their documentation. Helium’s migration was a huge success and greatly benefited the community.

Community Vote for RNP-002
Source: Community Vote for RNP-002

This migration is not an isolated event. The Render Network, the world’s first decentralized GPU rendering platform, successfully upgraded its core infrastructure from Ethereum to Solana in November 2023. The community voted favorably on RNP-002 to migrate to Solana. Render’s founder, Jules Urbach, described the migration as a watershed moment. He stated: “Solana’s incredible transaction speeds, low costs, and commitment to web-scale architecture make it perfect for the Render Network as we continue building a scalable and decentralized metaverse infrastructure.”

Render’s migration did not have an overly negative effect on their users. Users can bridge funds from Ethereum to Solana using Render’s Upgrade Assistant. Users connect their Ethereum wallet, indicate the amount of RNDR they wish to migrate, and wait for their tokens to be delivered to the specified Solana wallet. 

Maker is a quintessential Ethereum project. They aim to unlock the potential of decentralized finance with the Maker Platform, an inclusive platform designed to enhance economic empowerment and equal access to the global financial market. The platform consists of MakerDAO for administering the Maker project and Maker protocol for DAI, “the world’s first unbiased currency and leading decentralized stablecoin.”Rune Christensen, the founder of Maker, tweeted about using a fork of Solana’s codebase to develop a Maker appchain: 

Potential for Maker appchain on Solana tweet
Source: Rune Christensen’s Twitter

Migrations are inherently complex. Despite the monetary, reputational, and temporal costs of migrating a project’s entire infrastructure from Ethereum to Solana, projects are choosing Solana. Migrations do not have to be inherently complex with tools such as Solang and the Neon EVM. Like Helium’s migration, it can be boring and have little to no adverse impact on users’ funds, like the Render Network. Solana is the most performant blockchain on the market, built for web scale. This is the place to build, and more and more projects are realizing this.

Conclusion

Solidity is the lingua franca of smart contract development. The EVM has been the dominant smart contract environment since its inception. However, it has its weaknesses. Web-scale, consumer-grade applications necessitate a high-throughput, low-latency network to support their operations. Ethereum’s single-threaded, volatile gas-based environment cannot support a high-throughput Decentralized Physical Infrastructure Network (DePIN) project like Helium. 

In response to these challenges, Solana emerges as a powerful alternative. The best way to harness the true benefits of Solana is to actually build on Solana. With recent developments, tools such as Solang and the Neon EVM allow interested EVM developers to migrate using familiar tools and languages. This article offers a comprehensive guide on Solana’s architecture, how it compares to Ethereum, and posits how an EVM developer could start developing on Solana. Solana is the most performant blockchain on the market. It is gaining momentum, mind share, and reputable consumer-grade applications. Why wait for Ethereum to scale when you can enjoy the benefits of a fast, scalable blockchain today?

If you’ve read this far, thank you, anon! Be sure to enter your email address below so you’ll never miss an update about what’s new on Solana. Ready to dive deeper? Join our Discord to start building the future on the most performant blockchain, today.

Further Resources