Guide for Solidity Developers
A Solana development guide for developers with Ethereum and Solidity experience
Welcome Solidity Developers
Solidity devs, it’s time we’ve had a heart to heart or shall we say “Sol to SOL”
conversation about Solana development. First, this is not meant to convince
anyone that one blockchain is better than another, this isn’t about maximalism.
This is about learning from each other and getting into the finer technical
details about how the two platforms are different. This guide aims to be a
resource for experienced Solidity developers who decide to build an application
on Solana.
Does Solana support Solidity?
Not right now. Solidity was built to work really well for the Ethereum Virtual
Machine (EVM). However, Solana’s smart contract VM was designed with
fundamental differences from the EVM and so it currently does not support
Solidity. There are two ongoing efforts to support Solidity on Solana. Read more
here.
How can I do XXX on Solana?
You’ve come to the right place, this wiki is designed to answer everything a
Solidity developer would need to know about building on Solana. If the wiki
doesn’t cover your question, please create an issue on the wiki Github. Thanks
for your contribution!
1 - Introduction
An overview of Solana and how it compares and contrasts with Ethereum, Solidity, and the EVM
Why is Solana so different from Ethereum?
By far the biggest reason the development experience between Solana and Ethereum
is so different is due to their account model designs. Before we dig into those,
it’s helpful to understand why Solana’s account model was designed so
differently from Ethereum. Unlike Ethereum, which is designed to run on consumer
grade hardware, Solana was designed to optimize transaction throughput on
high-end multi-core machines. The Solana team noticed a trend that the number of
cores in computers is growing exponentially. In order to take advantage of all
these cores and future-proof the protocol, the Solana team designed transactions
to be easily parallelized with each other.
Account model design
So what is actually meant by the “account model” of a blockchain? Well, when an
on-chain program is called on a blockchain like Solana or Ethereum, the smart
contract needs a way to track certain state like token balances, who owns an
NFT, or who the current highest bidder in auction is. All of this state is
stored inside accounts on the blockchain and is replicated perfectly across all
the nodes in a cluster.
On Ethereum, each smart contract is an account which has its own storage. The
smart contract’s code specifies how to make sense of that storage.
On Solana, an on-chain program is a completely immutable account and its storage is
only used to store executable byte code.
So where do Solana programs store state? In other non-executable accounts! In
fact, each Solana account specifies a program owner which is the only program
allowed to make modifications to the account.
By forcing programs to store data inside other accounts, Solana allows developers to
design their smart contracts to be parallelizable. Developers are encouraged to split
their smart contract state across many different accounts that can be used in parallel
transactions without data conflicts.
Transaction Parallelization
Each Solana transaction must list all of the accounts that it will read from,
write to, and invoke as a program. With all this information listed up front,
Solana validators know which transactions can be processed at the same time
without conflicting with each other before those transactions are run. To fully
take advantage of this parallelization, on-chain programs themselves can split
their state across many accounts so that they can be parallelized too. For
example, the Serum Dex program has separate storage accounts for each new market
pair and so transactions on the USDC/ETH market won’t slow down transactions on
the USDC/SOL market because the state is totally separate and they can all be
run in parallel.
Design Constraints
Solana’s design and constraints force developers to carefully consider the
design of their own on-chain programs. Learning how to write Solana programs
has an arguably steeper learning curve than Solidity smart contracts. But don’t
forget the upside! By doing a bit more work upfront, Solana transactions can be
processed very efficiently. This results in both higher throughput and lower
fees. But this isn’t to say Solana is always the solution. As developers, we
make tradeoffs all the time when choosing our tools. The development experience
with Solidity on the EVM is much more flexible than on Solana without all the
overhead of figuring out which accounts will be accessed and which contracts
will be called.
2 - Development Environment
Introduction to the Solana development environment.
2.1 - Development Tools
Recommended tools for Solana program development.
Anchor is the Solidity of Solana
The Solana program SDK provides low-level interfaces for writing
smart contracts. It doesn’t help you define methods for you contract
or how data is stored, you can decide for yourself what tools you want to use.
The Anchor framework makes Solana program development much simpler.
Developing a contract with Anchor will a little closer to the experience of
building with Solidity. It allows programs to define human readable
method names and provides helpers for serializing contract state in many
different programming languages.
solana-test-validator
is the Ganache of Solana
Solana provides a tool called solana-test-validator
which starts up
a local node on your machine for development purposes. It is installed as
part of the Solana Tool Suite.
Useful solana-test-validator
Options
--reset
Running solana-test-validator
to setup a new node with a local blockhain
ledger. Restarting from the same directory will reuse the same ledger state.
Pass the --reset
flag to start fresh.
--bpf-program
Add your own locally built programs (smart contracts) to your local blockchain’s
genesis block by using the --bpf-program
option for as many programs as you
need. This is useful for speeding up development iterations and setting up other
programs that your program will call.
--clone
Copy the account state from another cluster by using the --clone
option. This must
be used along with the --url
option to specify which cluster to fetch accounts from.
solana-program-test
is the Truffle of Solana
Solana provides Rust package called
solana-program-test
which includes a test framework
which can be used to test programs in a local Solana VM instance. Check out the
Solana Program Library to learn how to use it.
2.2 - Program Languages
Supported languages for Solana program development.
Rust
Solana has first class support for the Rust programming language.
New to Rust?
It may be difficult to learn at first, but once you master it, there’s a good
chance you’ll never look back. Rust has consistently ranked as the moved loved
programming language in Stack Overflow’s developer surveys from many years.
Most Rust developers have learned through the Rust “book”. The Rust book explains
Rust’s unique and tricky features in an easy to understand way. Give it a try!
Anchor
The easiest way to write Rust programs for Solana is by using the Anchor framework.
Solidity?
Solidity is not yet supported on Solana. Solidity was built to work really well
for the Ethereum Virtual Machine (EVM). However, Solana’s smart contract VM was
designed with fundamental differences from the EVM and so it currently does
not support Solidity. There are two ongoing efforts to support Solidity on
Solana:
-
Porting the EVM to run on Solana
-
Support Solidity for writing Solana programs
- This is a cross-chain effort to allow compiling Solidity into byte code for
many different blockchains including Solana.
- Track development progress in the
#solang-solidity-compiler
channel on the
Solana Discord server.
- Learn more on the Solang documentation site.
C
Solana has support for C as well but the vast majority of resources are written
for Rust. C developers are recommended to use Rust for a better development
experience.
Move
Solana has previously experimented with adding support for the Move language
but has set that project aside to prioritize other projects.
2.3 - Client Languages
Supported languages for Solana application client development.
Rust
Solana provides a Rust SDK for creating bots and tools.
TypeScript / JavaScript
Solana provides a web3 SDK for writing browser applications in TypeScript
and JavaScript. Check out the documentation for how to use it.
Golang
Community created Go SDK: https://github.com/portto/solana-go-sdk
Swift
Community created Swift SDK: https://github.com/crewshin/solana-swift
Python
Community created Python SDK: https://michaelhly.github.io/solana-py/
3 - Accounts
A high-level overview of the Solana account model
In account based chains like Ethereum and Solana, arbitrary state can be stored on-chain
to create complex and powerful decentralized applications. However, one of the major
differences between the EVM and Solana is how that state is actually stored. On Ethereum,
only smart contracts have storage and naturally they have full control over that storage.
On Solana, any account can store state but the storage for smart contracts is
only used to store the immutable byte code. On Solana, the state of a smart contract is
actually completely stored in other accounts. To ensure that contracts can’t
modify another contract’s state, each account assigns an owner contract which has exclusive
control over state mutations.
To visualize this difference here’s what the storage a token contract would look like on
either platform.
On Ethereum, token contracts typically have a mapping
which defines the balance for each
owner address:
mapping (address => uint256) private _balances;
On Solana, token balances are typically stored in unique accounts where the storage account
address is derived from the address of the owner account.
On Ethereum there are two types of accounts. “Basic accounts” which simply
store a balance of wei, and “Code accounts” which are used for smart
contracts. Each code account, in addition to storing EVM code, all have an
associated storage map which can be used to read and write arbitrary data. The
EVM provides instructions for each contract to read and write to its own storage
but it’s impossible to read from other contract’s storage.
On Solana there are also two types of accounts: executable and non-executable
accounts. Unlike the EVM, both of these accounts can store data. Executable accounts
are immutable and can either store their own executable byte code or a fixed
proxy address to an account which stores mutable executable byte code.
Since executable accounts are immutable, their application state must be stored in non-executable accounts.
In the EVM, contracts can only read and write their own storage. In Solana, any account’s
data can be read or written to by a contract. However, the runtime enforces that only
an account’s owner is allowed to modify it. Changes by any other programs will be reverted
and cause the transaction to fail.
Let’s take a closer look at what is actually stored in an account on each platform.
In the EVM, a “basic” account is very simple. It holds a nonce which is incremented each time
this account sends a transaction as well as a balance field which tracks the remaining wei
held by the account. The nonce field has a very important purpose. It is what prevents
any transaction from being processed twice by the EVM. This is because each transaction
specifies the nonce of the sender and this nonce must match the sender’s nonce in the
account store to be executed. Since nonce’s are incremented after each transaction,
it’s impossible to run the same transaction twice. If it weren’t for this nonce
concept,
transactions could be “replayed” by processing them more than once which is often a very
undesirable outcome for users.
Field |
Description |
nonce |
The number of transactions sent from this account. |
balance |
The number of Wei owned by this account. |
In the EVM, “code accounts” are where all the action is. Since code accounts cannot
be used for sending transactions, the nonce
field represents the number of contracts
this account has created. Similar to “basic accounts”, code accounts can hold wei and use
an EVM instruction to send that wei to other accounts. Code accounts also store an
immutable hash of the associated EVM byte code as well as a hash which tracks changes
to all data in storage. The actual EVM byte code and storage data is stored separately
from the account store but often cached locally for quick access.
Field |
Description |
nonce |
The number of contract-creations made by this account. |
balance |
The amount of Wei owned by this account. |
codeHash |
The hash of the immutable EVM code of this account. |
storageRoot |
The 256-bit hash of the root node of a merkle tree that encodes the storage contents of this account. |
In Solana, the main similarity to EVM is the lamports
field which tracks the balance
of each account. There is a notable lack of anything like EVM’s nonce
field. This is because
nonces are handled differently on Solana. The key field to take
note of in Solana accounts is the owner
field. This field stores the address of an
on-chain program and represents which on-chain program is allowed to write to the
account’s data and subtract from its lamport balance. The concept of program-owned accounts
roughly maps to account-specific storage maps in the EVM. However, it comes with added
flexibility of allowing any on-chain program to read the data from accounts it doesn’t own.
Field |
Description |
lamports |
The number of lamports owned by this account. |
owner |
The program owner of this account. |
executable |
Whether this account can process instructions. |
data |
The raw data byte array stored by this account. |
rent_epoch |
The next epoch that this account will owe rent. |
Account Storage
In the EVM, only “code accounts” have storage. This storage is implemented as a map with a
256 bit key space where each key maps to a 256 bit value. For non-code accounts,
the storageRoot
is set to a special “null” hash which indicates the account has no storage.
In the Solana VM, all accounts can store data. However, executable account data is
used exclusively for immutable byte code which is used to process transactions. So where can
smart contract developers store their data? They can store the data in non-executable accounts
which are owned by the executable account. Developers can create new accounts with an
assigned owner equal to the address of their executable account to store data.
Account Signing Authority
Question: Who actually is allowed to create the accounts needed for storing program state?
- Solana accounts can only be assigned to a program if the account’s signing authority approves
the change. Typically, the signing authority just means that the corresponding private key
must sign the transaction.
Question: What happens when a program wishes to create an account?
- Since program execution state is entirely public and known to every validator, there’s no
way for it to secretly sign a message to create an account. To allow account creation by programs,
the Solana runtime provides a syscall which allows a program to derive an address from its own
address which the program can freely claim to sign.
Question: How to create multiple accounts in the same transaction if each one requires a signature?
Answer: Solana transactions specify a list of signatures and contain as many signatures as can
fit in a 1232 byte blob. Each of these signatures must pass verification or the transaction will be
rejected. Each signature increases the fee as well.
Question: What are signatures used for?
Answer: System-owned account must sign most system instructions. This includes assigning the account
to a new program, allocating storage, and transferring lamports.
Question: What’s the equivalent in the EVM?
Ethereum transactions have a field for exactly one signature which must be verified against the address
which sent the message. Any additional signatures must be passed in transaction binary data and verified
using the ecrecover
crypto function which executes natively using EVM precompiles.
Question: Can the Solana VM verify secpk2561 instructions from Ethereum?
Answer: Yes, it was added to support the Wormhole Ethereum / Solana bridge
Account Owner
Every account in Solana has a specified owner. Since accounts can be created by simply
receiving lamports, each account must be assigned a default owner when created. The
default owner in Solana is called the “System Program”. The System Program is
primarily responsible for account creation and lamport transfers.
Account types
Name |
Owner |
Description |
Sysvar |
Sysvar |
An account used for loading blockchain state like the latest block and current rent fees |
Native Program |
Native Loader |
An account used to indicate native programs like the System, Stake, and Vote programs which do not use BPF byte code |
BPF Program |
BPF Loader |
An account used for processing BPF byte code |
Solana Runtime Account Rules
Immutability
- Executable accounts are fully immutable.
The above rule takes precedence over all following rules. Meaning that programs
cannot add lamports to executable accounts and their data can never be modified
or deleted.
Data Allocation
- Only the System Program is allowed to change the size of account data.
- Newly allocated account data is always zeroed out.
- Account data size cannot be decreased.
At the time of writing, programs cannot increase the data size of accounts they own.
They must copy data from one account to a larger account if they need more data. For this
reason, most programs do not store dynamically sized maps and arrays in account data.
Instead, they store this data in many accounts. For example, each key-value pair of an
EVM mapping can be stored in a new account.
Data
- Only the owner of an account may modify its data.
- Accounts may only be assigned a new owner if their data is zeroed out.
The rules above guarantee that a program can always fully trust the data
stored in an account that it owns. The data is either zeroed out or previously
modified by the program. These guarantees work together to form the same
trust guarantees as the EVM’s account storage mechanism.
In Solana, executable byte code is stored in account data unlike the EVM which
stores code in a separate data store.
Balance
- Only the owner of an account may subtract its lamports
- Any program account may add lamports to an account
This means that once an account is owned by a program, the private key
cannot be used to transfer lamports with the System Program since the
System Program no longer has permission to send lamports from the account.
Ownership
- Only the owner of an account may assign a new owner
Since all accounts are owned by the System Program by default, the System Program
is most often used to assign accounts to other programs.
Rent
- Rent fees are charged every epoch (~2 days) and are determined by account size
- Accounts with sufficient balance to cover 2 years of rent are exempt from fees.
Because rent fees can slowly drain an account’s balance, programs must consider whether
to enforce that accounts they use for storage should be required to be rent-exempt.
If accounts are not required to be rent-exempt, they may eventually run out of lamports
and be deleted by the runtime. Once deleted, accounts can be recreated. For this reason,
accounts which rely on certain data storage to be present should enforce that accounts
are exempt from rent before writing data.
Zero Balance
- Accounts with zero balance will be deleted at the end of transaction processing.
- Temporary accounts with zero balance may be created during a transaction.
Programs which close accounts should consider that account data is not deleted until
a transaction has been fully processed. Simply subtracting lamports to a zero balance
is not sufficient to delete an account. This means that if a program is called from
another program, it may be called again with the same accounts which have not yet
been deleted.
New Executable Accounts
- Only designated loader programs may change account executable status
In Solana, executable accounts are created just like normal accounts but
their owner must be set to a loader program. The loader program processes
transactions to write byte code into account data and only once the program
passes the loader’s verification process, will it be marked as executable.
Since executable accounts are immutable, any lamports in the account will be
frozen when the account is marked executable. The lamport balance should
therefore be no more than the minimum balance required for rent-free storage.
4 - Transactions
Major differences between Ethereum and Solana transactions
Ethereum supports a few different types of transactions which can be created using a common set of parameters. These
include ETH transfers, smart contract calls, and new contract deploys. On Solana, all transactions are treated the
same and so all call on-chain programs (Solana has special programs for deploying contracts and transferring SOL).
Let’s dig into the differences by looking at the structure of a transaction from each chain…
Ethereum Transaction Structure
Field |
Description |
nonce |
Number equal to the count of sender’s processed transactions. |
gasPrice |
The amount of Wei to be paid per unit of gas. |
gasLimit |
Max gas that can be consumed while processing the transaction. |
to |
The recipient of ether and smart contract to be called. |
value |
The amount of ether to transfer to the to address. |
v, r, s |
Represents the signature and used to recover the sender |
Solana Transaction Structure
Field |
Description |
signatures |
List of signatures. |
accounts |
List of accounts (read-only / read-write) |
recentBlockhash |
Blockhash of recently produced block used as nonce. |
instructions |
List of instructions which each call an on-chain program. |
Despite their structural differences, these transactions have a very similar goal: calling a smart contract. Let’s
walk through the fields to understand each approach.
Mapping Ethereum transaction fields to Solana
sender
On Ethereum, the sender
is the address of the keypair which signed this transaction. Inside a smart contract, we
know the msg.sender
is an address that approved of the smart contract call because we trust Ethereum nodes to first
verify the transaction signature. Note: in Ethereum, the sender
is actually recovered from the signature itself.
Also, the sender
of a transaction is the account which will pay gas fees for the smart contract. By signing a
transaction, the sender
authorizes payment of gas fees.
On Solana, the first account
in the transaction accounts
list is roughly the same thing as the sender
in an
Ethereum transaction. It is the account that will be used to pay transaction fees and Solana will verify that the
first signature
in the transaction signatures
list was produced by that account.
signature
On Ethereum, each transaction contains a single signature. This signature is roughly the same as the first signature
in a Solana transaction’s list of signatures. So why does Solana allow multiple signatures? Well, imagine you are
using a multisig wallet and need to create a transaction which shows that multiple keypairs have signed and approve
the transaction. On Ethereum, you would need to pass signatures inside transaction data and verify them inside a
smart contract. On Solana, signatures can be appended to the transaction signatures list and, since Solana nodes use
a GPU to verify signatures, will be verified much more efficiently than they would inside a program.
nonce
On Ethereum, each transaction includes a nonce which is used to prevent a single transaction from being processed
multiple times. Every time Ethereum processes a transaction, it requires that the transaction nonce value is equal to
the sender’s total transaction count. So if you have sent 10 transactions, your next transaction will have a nonce
equal to 10 and after Ethereum checks the nonce and processes the transaction, it will increment the your transaction
count to 11 and wait for a transaction with that nonce.
Solana solves this problem in another way. Relatively old transactions cannot be processed again because each
transaction must specify a “recent” blockhash to be processed. Re-processing recent transactions is avoided by
requiring each node to keep a record of all the transactions for recent blocks. So transactions with an old
recentBlockhash
are easily ignored and other transactions are ignored if they are already included in the recently
processed transaction list.
to
Ethereum transactions use to
to specify an address to send ETH to or a smart contract to call.
Solana transactions can actually list multiple smart contracts to call and so they don’t have a single to
field.
Instead, they may list one or more “instructions” which each represent a smart contract call. Each instruction
specifies its own smart contract address and the input parameters for the call.
value
Ethereum transactions are always explicit about how much ether may be sent from a user’s account when making a
transfer or invoking a smart contract. This amount is specified in the value
field of a transaction and does not
include the gas cost of the transaction.
Solana transactions don’t have an equivalent property which specifies how much SOL can be transferred. Instead, each
on-chain program has authority to withdraw lamports from any account it owns. By default, each account is owned by
the system program which requires an account to sign the transaction to perform a withdraw. Other programs may define
their own rules and typically support a withdraw or close account instruction which requires the account to sign.
gasPrice
and gasLimit
Every operation in the EVM has an associated gas cost which must be paid by the transaction sender. Since transaction
throughput is limited by the amount of gas allowed in each block, gas price provides a way for transaction senders to
bid a higher price in order to be included in a block more quickly. Any transaction with a gas price that’s too low
will get ignored by miners because they want to maximize their earnings in each block they mine. Gas limit is
specified to prevent a buggy smart contract from using way more gas than you intended and causing lost funds.
The Solana VM doesn’t have the dynamic gas model for transactions. Instead, it has a fixed maximum compute cost which
currently cannot be adjusted. This means that each transaction roughly has the fixed cost and it naturally puts
pressure on developers to optimize on-chain code to fit within the system limits. Transactions do have fees on
Solana, though. Currently transaction fee calculation is very simple, each signature in a transaction costs an
additional 5000 lamports (there are 10^9 lamports in one SOL).
data
Ethereum transactions include a single data
field for an unlimited size byte array. This data is passed directly to
a smart contract which if written with Solidity, will be decoded into a function and its parameters.
Solana transactions may include many “sub-transactions” called instructions. Each instruction has a data
field
which is used in the same way as an Ethereum transaction’s data
field. However, note that an Solana instruction’s
data is limited in size. The entire encoded size of a Solana transaction cannot exceed 1232 bytes. This constraint
allows Solana to optimize its networking layer for quickly passing transactions between nodes (smaller packets = less
delay).
Understanding Solana transactions
signatures
Each Solana transaction allows for one or more signatures so that they can be efficiently verified by Solana
validator GPU’s. This means multiple accounts can easily authorize operations in on-chain programs in the same
transaction. This contracts with Ethereum where any additional signatures beyond the sender must be verified inside a
smart contract.
recentBlockhash
Solana transactions must include the blockhash of a recently produced block. Blockhashes are considered recent if
they were produced in about the past 60 seconds. This field is used a nonce to ensure that no transaction can be
processed more than once by the blockchain.
accounts
Solana transactions must explicitly list each account that on-chain programs may read or write to. By specifying all
of the accounts up front, Solana validators can process transactions in parallel without fear of two transactions
modifying the same account. It is important that high-throughput applications split up state into multiple accounts
because if each transaction modifies the same account, transactions will have to be processed serially.
Accounts may be annotated as read-write or read-only accounts. If an on-chain program modifies a read-only account,
the transaction will be reverted. The first account will always be read-write since it is used to cover transaction
fees.
Solana’s account access list is similar to the optional access list in
EIP-2930.
instructions
Solana transactions can be thought of as a bundle of Ethereum transactions. Each Solana transaction can include one
or more instructions which each specify an on-chain program address and inputs. There is no explicit limit on the
size of an instruction but note that the total serialized size of a transaction cannot exceed 1232 bytes. The compute
limit is fixed per instruction so each on-chain program should be optimized to use a small amount of compute units or
be split across multiple instructions for expensive operations.
Each instruction specifies the address of the on-chain program, a list of account inputs, and a byte array. Since
Solana on-chain programs don’t have their own mutable storage, they must read and store data in separate accounts
which are loaded for the on-chain program when invoked.
4.1 -
5 - Dictionary
A list of commonly used terms on Solana and how they map to Ethereum
Term |
Description |
Associated token account |
Wallet’s default address for the holdings for a certain token. |
ATA |
Associated token account. |
Confirmed |
Commitment level which indicates that 2/3 of the active stake has voted on a block. |
Epoch |
Length of time equal to 432,00 slots (~2 days) used for scheduling leaders, warming up stake, etc. |
Finalized |
Commitment level which indicates that 2/3 of the active stake has finalized a block. |
Fee payer |
The first account which signed a Solana transaction, similar to msg.sender . |
Instruction |
Represents a call to an on-chain program. Solana transactions can contain one or more instructions. |
Lamport |
The smallest unit of SOL. Similar to Ethereum’s “wei” unit. |
Leader |
Validator whose turn it is to produce a block. |
Native program |
Solana program which is pre-compiled, examples include the System, Stake, and Vote programs. |
On-chain program |
Solana’s name for smart contracts. |
Processed |
Commitment level which can be used to interact with the most recently produced block. |
PDA |
Program derived account. |
Program derived account |
Special account who’s address is derived from a program id. |
Program ID |
Address of a deployed on-chain program. |
Slot |
Length of time (~500ms) in which a designated leader may optionally produce a block. |
SOL |
The native token of Solana’s blockchain. Similar to ETH on Ethereum. |
SPL |
Solana program library, a collection of reference programs similar to Open Zeppelin. |
SPL token |
The ERC20 equivalent on Solana. Each SPL token uses the same deployed SPL Token program. |
Syscall |
Similar to Ethereum’s precompiles. |
System program |
Native program which is responsible for SOL transfers and account allocation. |
Sysvar |
Special Solana account which stores blockchain context like current slot or epoch. |
Transaction |
Signed list of instructions that are executed atomically on Solana. |