Celo L2 Specification
Table of Contents
This document describes the differences between the Celo L2 implementation and Optimism's OP Stack, on which it is based. Refer to the OP Stack specs for details on the unmodified OP Stack.
The L1→L2 migration changes page details the differences compared to the Celo L1 blockchain. The Celo L2 is a continuation of the L1 by using its state and providing a high level of compatibility with it.
Background
Since May 2024, the Celo Community has decided to transition from an L1 blockchain to a L2 solution using the OP Stack. This move, detailed and approved in CGP-133, aims to enhance scalability, security, and interoperability with Ethereum. The proposal aligns with Celo's mission to create a more accessible and inclusive financial system. By leveraging the OP Stack, Celo L2 will retain the benefits of the existing ecosystem while integrating with Ethereum's robust infrastructure, fostering greater innovation and user engagement.
New Features
- Token duality, access native tokens via ERC20
- Fee Abstraction, pay gas with ERC20 tokens
Technical Differences
- New transaction types to support Fee Abstraction
- One new precompile to support token duality
- Changes to finality
Deployments
Alfajores
The Alfajores testnet will be migrated to an L2 on September 26, 2024. The migration is scheduled for block 26384000. For this L1 chain will be stopped at a block height of 26383999. At this point the existing state will migrated to work with the L2 nodes. The migration process preserves the full L1 history while updating it to work with the Celo L2 stack. More technical details are available in the migration docs.
The Alfajores L2 testnet will have the following chain properties:
- Block period: 1 second
- Block gas limit: 30,000,000 gas
- Block gas target: 6,000,000 gas (see EIP-1559 elasticity multiplier)
- EIP-1559 elasticity multiplier: 5
- EIP-1559 denominator: 400
- EIP-1559 floor: 25 Gwei
Celo core contracts
Table of Contents
To facilitate Celo features and governance, the Celo chain has a set of core contracts.
Contracts List
Alfajores
See also the config files
L1 Contracts
Contract | Address |
---|---|
AddressManager | 0xF9f47a32F0BB1F92cF200b1CC9a1713F96b65284 |
CustomGasTokenProxy | 0x3E3FEA3F31ff162a460f11Af4c53f39E743fd88c |
DisputeGameFactoryProxy | 0xE28AAdcd9883746c0e5068F58f9ea06027b214cb |
L1CrossDomainMessengerProxy | 0xF1eE12842631A56a860A38C20B588F4Bb872a4F8 |
L1ERC721BridgeProxy | 0x514912297580a20B7a0C2930BC8503d2C13Da642 |
L1StandardBridgeProxy | 0xD1B0E0581973c9eB7f886967A606b9441A897037 |
L2OutputOracleProxy | 0x4a2635e9e4f6e45817b1D402ac4904c1d1752438 |
OptimismMintableERC20FactoryProxy | 0xa950F004F069B0bF9201b17e71549c7711d4a9d5 |
OptimismPortalProxy | 0x82527353927d8D069b3B452904c942dA149BA381 |
PreimageOracle | 0x68967789277F6E831c9Dd73b5B67E598f79C248D |
ProtocolVersionsProxy | 0x5E5FEA4D2A8f632Af05D1E725D7ca865327A080b |
ProxyAdmin | 0x4630583d066520aF0E3fda0de2C628EEd2888683 |
SafeProxyFactory | 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2 |
SafeSingleton | 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552 |
SuperchainConfigProxy | 0xdf4Fb5371B706936527B877F616eAC0e47c9b785 |
SystemConfigProxy | 0x499b0C1F4BDC76d61b1D13b03384eac65FAF50c7 |
SystemOwnerSafe | 0xf05f102e890E713DC9dc0a5e13A8879D5296ee48 |
L2 Contracts
Contract | Address |
---|---|
Registry | 0x000000000000000000000000000000000000ce10 |
Accounts | 0xed7f51A34B4e71fbE69B3091FcF879cD14bD73A9 |
Attestations | 0xAD5E5722427d79DFf28a4Ab30249729d1F8B4cc0 |
BlockchainParameters | 0xE5aCbb07b4Eed078e39D50F66bF0c80cF1b93abe |
CELO (GoldToken) | 0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9 |
CeloUnreleasedTreasury | 0x07bf0b2461A0cb608D5CF9a82ba97dAbA850F79F |
DoubleSigningSlasher | 0x88A4c203C488E8277f583942672E1aF77e2B5040 |
DowntimeSlasher | 0xf2224c1d7b447D9A43a98CBD82FCCC0eF1c11CC5 |
Election | 0x1c3eDf937CFc2F6F51784D20DEB1af1F9a8655fA |
EpochRewards | 0xB10Ee11244526b94879e1956745bA2E35AE2bA20 |
Escrow | 0xb07E10c5837c282209c6B9B3DE0eDBeF16319a37 |
FeeCurrencyDirectory | 0x9212Fb72ae65367A7c887eC4Ad9bE310BAC611BF |
FederatedAttestations | 0x70F9314aF173c246669cFb0EEe79F9Cfd9C34ee3 |
FeeHandler | 0xEAaFf71AB67B5d0eF34ba62Ea06Ac3d3E2dAAA38 |
Freezer | 0xfe0Ada6E9a7b782f55750428CC1d8428Cd83C3F1 |
Governance | 0xAA963FC97281d9632d96700aB62A4D1340F9a28a |
LockedGold | 0x6a4CC5693DC5BFA3799C699F3B941bA2Cb00c341 |
MentoFeeHandlerSeller | 0xae83C63B5FB50C353c8d23397bcC9dBf3a9837Ac |
OdisPayments | 0x645170cdB6B5c1bc80847bb728dBa56C50a20a49 |
Random | 0xdd318EEF001BB0867Cd5c134496D6cF5Aa32311F |
Reserve | 0xa7ed835288Aa4524bB6C73DD23c0bF4315D9Fe3e |
SortedOracles | 0xFdd8bD58115FfBf04e47411c1d228eCC45E93075 |
StableToken | 0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1 |
StableTokenBRL | 0xE4D517785D091D3c54818832dB6094bcc2744545 |
StableTokenEUR | 0x10c892A6EC43a53E45D0B916B4b7D383B1b78C0F |
TransferWhitelist | 0x52449A99e3455acB831C0D580dCDAc8B290d5182 |
UniswapFeeHandlerSeller | 0xc7b6E77C3702666DDa8EB5b7F30234B020788b21 |
Validators | 0x9acF2A99914E083aD0d610672E93d14b0736BBCc |
Dango
Contract | Proxy Address |
---|---|
Accounts | 0xed7f51A34B4e71fbE69B3091FcF879cD14bD73A9 |
Attestations | 0xAD5E5722427d79DFf28a4Ab30249729d1F8B4cc0 |
BlockchainParameters | 0xE5aCbb07b4Eed078e39D50F66bF0c80cF1b93abe |
Celo Token (GoldToken) | 0xF194afDf50B03e69Bd7D057c1Aa9e10c9954E4C9 |
CeloDistributionSchedule | 0x78Af211Ad79bCE6BF636640CE8c2C2b29e02365A |
DoubleSigningSlasher | 0x88A4c203C488E8277f583942672E1aF77e2B5040 |
DowntimeSlasher | 0xf2224c1d7b447D9A43a98CBD82FCCC0eF1c11CC5 |
Election | 0x1c3eDf937CFc2F6F51784D20DEB1af1F9a8655fA |
EpochRewards | 0xB10Ee11244526b94879e1956745bA2E35AE2bA20 |
Escrow | 0xb07E10c5837c282209c6B9B3DE0eDBeF16319a37 |
Exchange | 0x17bc3304F94c85618c46d0888aA937148007bD3C |
ExchangeBRL | 0xf391DcaF77360d39e566b93c8c0ceb7128fa1A08 |
ExchangeEUR | 0x997B494F17D3c49E66Fafb50F37A972d8Db9325B |
FeeCurrencyDirectory | 0x71FFbD48E34bdD5a87c3c683E866dc63b8B2a685 |
FederatedAttestations | 0x70F9314aF173c246669cFb0EEe79F9Cfd9C34ee3 |
FeeHandler | 0xEAaFf71AB67B5d0eF34ba62Ea06Ac3d3E2dAAA38 |
Freezer | 0xfe0Ada6E9a7b782f55750428CC1d8428Cd83C3F1 |
GasPriceMinimum | 0xd0Bf87a5936ee17014a057143a494Dc5C5d51E5e |
Governance | 0xAA963FC97281d9632d96700aB62A4D1340F9a28a |
GrandaMento | 0xEcf09FCD57b0C8b1FD3DE92D59E234b88938485B |
LockedGold | 0x6a4CC5693DC5BFA3799C699F3B941bA2Cb00c341 |
MentoFeeHandlerSeller | 0xae83C63B5FB50C353c8d23397bcC9dBf3a9837Ac |
OdisPayments | 0x645170cdB6B5c1bc80847bb728dBa56C50a20a49 |
Random | 0xdd318EEF001BB0867Cd5c134496D6cF5Aa32311F |
Registry | 0x000000000000000000000000000000000000ce10 |
Reserve | 0xa7ed835288Aa4524bB6C73DD23c0bF4315D9Fe3e |
SortedOracles | 0xFdd8bD58115FfBf04e47411c1d228eCC45E93075 |
StableToken | 0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1 |
StableTokenBRL | 0xE4D517785D091D3c54818832dB6094bcc2744545 |
StableTokenEUR | 0x10c892A6EC43a53E45D0B916B4b7D383B1b78C0F |
TransferWhitelist | 0x52449A99e3455acB831C0D580dCDAc8B290d5182 |
UniswapFeeHandlerSeller | 0xc7b6E77C3702666DDa8EB5b7F30234B020788b21 |
Validators | 0x9acF2A99914E083aD0d610672E93d14b0736BBCc |
ABIs
JSON ABI descriptions for all core contracts are available in the @celo/abi npm package.
Table of Contents
- Epochs And rewards
- Celo minting
- Governance Hotfix
- FeeCurrencyDirectory
- FeeHandler
- Slashing
- Deprecated contracts
- Precompiles deprecation
- Transition
Smart contract changes that will be deployed to the L1 before the transition can be seen in this diff: https://github.com/celo-org/celo-monorepo/pull/11035/files
Epochs And rewards
Overview of rewards and epochs in L1:
The Celo L1 produces a special block ("epoch block”) every 17280 blocks (approximately one day). This blocks includes "epoch transactions” that are triggered by the blockchain itself.
During these epoch transactions, the protocol does:
- Updates target voting yield
- Calculates validator rewards
- Update validators scores.
- Mints Celo:
- For validator rewards (then exchanged for cUSD using Mento)
- For voter rewards
- For CarbonOfsetting fund
- For Community Fund (Celo Governance)
- Distributing validator rewards to the validators, groups and delegator (in cUSD)
- Distributing voter rewards
- Running validator elections (the result is the addresses of the signers, then stored in the blockchain storage and accessed via a precompile)
Overview of rewards and epochs in L2:
In the L2 "epoch blocks” no longer exist. There are no transactions triggered by the blockchain itself. Precompiles that were used to query epoch state are also not longer available.
The concept for epochs still remains, but they are determined to be at least as long as "epoch duration” (targeted to be set as one day on mainnet), but there's no guaranteed limit of the maximal duration. The size of an epoch can no longer be deterministically calculated based on block numbers alone
The logic for processing epochs is now fully implemented in Solidity in the EpochManager contract introduced in Contract Release 12. cLabs is intended to run a bot to call the functions to trigger the epoch change and rewards distributions as soon as they are ready to do so, in a best-effort way.
Please note that the deployed version in a network can vary from the smart contract linked, as the code is still ongoing audit.
Epochs are now processed using multiple calls, as the gas consumption of the process involved uses relatively high amounts of gas.
Celo is no longer minted when processing the epoch, it is now transferred from the CeloUnreleasedTreasury
. The contract CeloUnreleasedTreasury
is allocated the full amount of unminted Celo at the time of the transition.
When epoch duration has elapsed since the current epoch started, the function startNextEpochProcess
can be called. This function:
- Is permissionless, every EOA or contract can call it given that certain conditions are met:
- Enough time has elapsed after the beginning of the epoch.
- The epoch is not currently processing.
- Updates target voting yield
- Calculates epoch rewards (
EpochRewards.calculateTargetEpochRewards()
) - Allocates validator rewards:
- mints CELO and exchanges it to cUSD
- Sets an internal mapping with the allocation for each validator. Validators can later claim it calling
sendValidatorPayment
- Starts a block that prevents certain actions to be performed, notable lock Celo, unlock Celo and change validator locks.
- Emits events:
- EpochRewards:
TargetVotingYieldUpdated(uint256 fraction)
- CeloUnreleasedTreasury:
Released(address indexed to, uint256 amount)
- cUSD:
Transfer(address indexed from, address indexed to, uint256 value)
- EpochManager
EpochProcessingStarted(uint256 indexed epochNumber)
- EpochRewards:
After startNextEpochProcess
is called, the epoch can be fully finished by calling finishNextEpochProcess
. This function:
- Is permissionless, every EOA or contract can call it given that certain conditions are met:
startNextEpochProcess
has been called before.
- Distributes rewards to voters (Celo)
- Elects validators (the result is stored as an array of accounts of the elected validators, signers are also stored for backwards compatibility purposes)
- Unblocks all actions blocked in
startNextEpochProcess
. - Updates the Epoch state.
- Emits events:
- Election:
EpochRewardsDistributedToVoters(address indexed group, uint256 value)
- CeloUnreleasedTreasury:
Released(address indexed to, uint256 amount)
- EpochManager:
EpochProcessingEnded(uint256 indexed epochNumber)
- Election:
In the unlikely case that
startNextEpochProcess
needs to use more gas than available in a block, the same result can be achieved using multiple calls toprocessGroup
.
Scoring
The rewards for a validator, its group and it's voters were previously based on the score of the validator, and the downtime the validator. The score was defined by the downtime the validator had over multiple epochs.
The Scoring is now managed by a contract called ScoreManager. This contract is a placeholder for a more complex implementation, meanwhile Governance and a multisig has the power to change the score of a validator. The score is now fully proportional to the rewards the validator and its voters will get at the end of the epoch.
Celo minting
In the L1 Celo used to be minted using the well known mint()
function, that was using the transfer
precompile under the hood.
In the L2, the Celo in the L2 is a bridged token from the L1. At the moment of the transition the whole total supply of Celo (1 billion tokens) is allocated to the bridge contract in the L1. In the L2, all holders (contracts and EOAs) will remain with their balance in their account, but was previously unallocated in Celo as a L1 will now be allocated to a new contract called CeloUnreleasedTreasury
.
This comes with the implication that the totalSuply()
function of the Celo Token (contract name GoldToken
) will return 1 billion celo after the L2 transition.
This means that the Celo token is now a ERC20 on the Ethereum network, and the tokens available on the Celo chain are a bridged representation of the tokens in the L1.
This is achieved by using the token duality of the OP stack: https://docs.optimism.io/builders/chain-operators/features/custom-gas-token
Governance Hotfix
The hot fix mechanism has been changed from a consensus of validators to a security council multisig. The current setup will be available until the transition.
FeeCurrencyDirectory
The contract FeeCurrencyWhitelist
is now deprecated. Fee currencies are now stored in a new contract called FeeCurrencyDirectory
. To add a new token to the directory, an address for an oracle needs to be provided, as well as the intrinsic gas for transactions paid with this token as fee. Changes can only be made by Celo Governance.
The intrinsic gas is the amount of gas that it will be added to all transactions paying for feed with this gas. It is meant to accurately represent the cost of the functions debitGasFees
and creditGasFees
used to collect the fees involved while validating the transaction.
FeeHandler
The FeeHandler was extended to support multiple beneficiaries. As it works in the L1 right now, only a carbon fund beneficiary and the burn fraction can be set.
After Contract Release 12 is deployed, many beneficiaries can be set, and the burn fraction would be the reminding of all the allocations of the beneficiaries.
Slashing
DowntimeSlasher and DoubleSigningSlasher will be deprecated after L2 transition, as the validators get replaced for sequencers. GovernanceSlasher will still be available and now supports removing members of a validator group and changing the slash multiplier. Governance slasher now supports to enable a multisig with the slasher role.
Deprecated contracts
The following contracts have been deprecated:
- Attestations (withdraws still enabled).
- FeeCurrencyWhitelist
- GasPriceMinimum
- BlockChainParameters
- DowntimeSlasher
- DoubleSigningSlasher
- Random
- UsingPrecompiles
Deprecation means that they do not fulfill any purpose as a require primitive to run the chain. Deprecated contracts still remain on-chain, although their functions are supposed to revert with a L2 check.
They still remain in the Celo Registry, but are scheduled for deletion after the L2 fully activates.
Some functionality that was provided by the precompiles (notably epoch number and elected validators) can be found in the EpochManager contract.
Some contracts had function that exposed precompiles will begin to revert in L2, at least this contract still internally uses those functions. For more details, please refer to this forum post.
Precompiles deprecation
All precompiles implemented in Celo as an L1 will not be available after the L2 transition but the transfer
precompile. However, the transfer
precompile can no longer mint Celo. There's an ongoing effort to push the transfer precompile upstream to the OP stack to guarantee full Superchain compatibility.
Transition
Contracts for the L2 will be deployed before the transition, with Celo as L1. The contracts change behaviour automatically at the time of the transition.
Token Duality
Table of Contents
What is token duality?
Token duality means that the CELO token is both the native currency of the Celo blockchain as well as an ERC20 compatible token. This means CELO tokens can be moved both by doing a native transfer as well as ERC20 transfers and will show up in both in the native account balance and the ERC20 balance, no matter how they were transferred. In contrast to ETH/WETH, no token wrapping or unwrapping is necessary.
Implementation
The native transfers and balances behave exactly as on Ethereum and are stored in the same way. The CELO contract reads native balances and triggers native transfers via its ERC20 interface.
Reading balances via ERC20
The ERC20 token does not store the balances in the contract storage, it uses the native balance as the source of truth. The balanceOf
function just passes through the native balance.
Transfers via ERC20
Similarly, the ERC20 transfer
and transferFrom
functions do not change the contract storage, but initiate a native transfer instead. Since there is no way to trigger native transfers from within a contract in Ethereum, Celo adds a transfer
precompile for this purpose. This precompile can only be called by the CELO token.
The transfer
precompile
The precompile directly manipulates the account balances in the EVM’s statedb. It checks the caller address to verify that it has been called by the CELO token.
Precompile address: 0xff - 2
== 253
Parameters (abi-encoded): address from, address to, uint256 value
Gas costs: 9000
No return value
Table of Contents
Transaction Fees
Overview
While you can send transactions on Celo as you do on any Ethereum or OP-Stack chain, there are some key differences that are relevant if you want a deeper understanding:
- Fee Abstraction, which allows users to pay for transaction fees in ERC20 tokens instead of CELO and is covered in its own chapter
- Zero L1 fees, meaning that the OP-Stack L1 fees are configured to always be zero, so that Celo chains don't incur any fees on top of the normal Ethereum transaction fees
- The base fee foor, which sets a lower limit for a block's base fee
- The FeeHandler contract that decides what to do with the collected base fees
Zero L1 Fees
OP-Stack supports charging transaction senders an L1 data fee, which is added on top of the normal transaction fees and can't be directly influenced or limited by the tx sender. The fee is meant to cover the cost of L1 transactions, especially for data availability. Since CELO uses EigenDA, the data availability costs are low and predictable, so that this mechanism is not needed.
The L1 fees are configured to zero by setting the gasPriceOracleBaseFeeScalar
and gasPriceOracleBlobBaseFeeScalar
to zero, so that the L1 fee formula always returns zero. The GasPriceOracle
will also correctly return zero as a result, so that you don't have to change your code if you are already relying on the GasPriceOracle
due to supporting other OP-Stack chains.
If you are coming from Ethereum, not having L1 data fees is what you are used to, and you will feel right at home on Celo.
Base Fee Floor
Celo follows the usual EIP-1559 rules to determine a block's base fee with on modification: The base fee can't fall below a certain limit, the base fee floor. This prevents the chain to be spammed with unimportant transactions that will make the chain state grow rapidly and make future scaling harder. The floor is set sufficiently low to still keep average transactions below 0.01 USD.
The base fee floor is set in the eip1559BaseFeeFloor
rollup configuration variable and is denominated in native CELO token. If Fee Abstraction is used to pay in other tokens, the base fee is converted to the token's value at the current exchange rate.
FeeHandler
All base fees are sent to the FeeHandler contract, which is responsible for deciding what to eventually do with the fees. The final behavior for Celo L2 mainnet has not been confirmed yet (as of 2024-11-15). On Celo L1, the FeeHandler:
- burns 80% of the fees (after converting non-CELO fees to CELO)
- sends 20% of the fees to carbon offsetting projects as part of the Ultragreen Money initiative.
Fee Abstraction: Paying Gas With ERC20 Tokens
Table of Contents
Fee Abstraction is a Celo feature that allows users to send transactions without spending any native CELO tokens. Instead, ERC20 tokens are used to pay for the transaction's gas cost.
As a User
Sending Fee Abstraction Transactions
To pay gas via Fee Abstraction, the gas token must be specified in a CIP-64 transaction. This tx type has an additional feeCurrency
field that specifies which token is used for the maxFeePerGas
and maxPriorityFeePerGas
fields. See CIP-64 for details.
Celo will also soon support the CIP-66 tx type in addition to CIP-64.
Client library support is described in docs.celo.org.
JSON-RPC Changes
To make it easier to reliably send Fee Abstraction transactions, the JSON-RPC interface has been extended in two ways:
eth_estimateGas
takes an optionalfeeCurrency
parameter to get Fee Abstraction specific gas estimates (the gas price for the fee token transfers depends on the token)eth_gasPrice
andeth_maxPriorityFeePerGas
take an optionalfeeCurrency
parameter for getting CIP-64 gas prices.
The Fee Currency Directory
Only tokens that have been registered in the FeeCurrencyDirectory
contract using Celo's governance process can be used for Fee Abstraction. The directory entry shows that the token is trustworthy and contains additional information about the token:
- The token address
- An oracle address, conforming to the
IOracle
interface, to get the current exchange rate - The amount of intrinsic gas for Fee Abstraction gas calculation (see "Gas Calculation for Fee Abstraction Transactions")
You can run the getCurrencies()
method on the FeeCurrencyDirectory
contract to get a list of all registered tokens and use getExchangeRate(token address)
to fetch the current exchange rate for a token. The IOracle
interface is only intended for parties providing Fee Abstraction tokens, not for users or client libraries.
Tokens using FeeCurrencyAdapter
Tokens with a low number of decimals
(USDC and USDT) would cause problems during the gas calculation, because the cost per gas can only be accurately given in fractions and not integers. To avoid this issue, the tokens are not directly added to the FeeCurrencyDirectory
. Instead, a FeeCurrencyAdapter
contracts wraps these tokens, calculates the costs per gas in a higher accuracy and only converts it back to the lower accuracy once the total gas costs for a transaction is determined.
This only really impacts the user in a single way: Instead of using the USDC and USDT token addresses, you have to pass their FeeCurrencyAdapter
addresses in the feeCurrency
field for CIP-64 txs.
For Token Authors
Requirements for Registering a Fee Abstraction Token
To become registered as a Fee Abstraction currency, the following requirements have to be met:
- Implement the IFeeCurrency interface
- Ensure exchange rates are kept up to date in the oracle with the
IOracle
interface - Pass a Celo governance vote for the
FeeCurrencyDirectory
addition
The fee-currency-example repository contains the interface description and an example Fee Abstraction token implementation with tests.
Inside the Celo Blockchain
Exchange Rate Handling Inside the Blockchain Client
Before each block, the blockchain client reads the list of all registered Fee Abstraction tokens from the FeeCurrencyDirectory
and fetches the currency exchange rate for each of them. If the Oracle for a token reverts or returns invalid exchange rates (numerator or denominator are zero), the token is treated as if it was not registered. Token registrations and exchange rates stay the same within a block and are only updated before the next block.
Transactions with unregistered feeCurrency
values are not accepted into the tx pool and dropped from the pool if they have been previously accepted. The same is true if the maxFeePerGas
falls below the current base fee.
Gas Calculation for Fee Abstraction Transactions
Ethereum transactions pay a fixed fee of 21000 gas ("intrinsic gas") in addition to the cost for executing the transaction. This is meant to cover the operating expenses during tx processing outside the actual tx execution, like debiting the gas cost from the tx sender's account, refunding the unused gas or transferring the base fee and tip to the respective receivers.
When using Fee Abstraction, the fees are transferred by executing functions on the respective token contract, which is computationally more expensive than native token transfers. The exact costs will vary by token and can change over time. Therefore, a token-specific intrinsic gas value is used for Fee Abstraction txs in addition to Ethereum's intrinsic gas. This value is stored in the FeeCurrencyDirectory
contract and can be changed by Celo governance. It should be set to at least the maximum gas cost of executing all function calls for debiting and crediting fees for a single transaction.
Block Space Limits Per Fee Abstraction Token
As described above, Celo allows users to pay for gas using ERC20 tokens. There is a governable list of accepted tokens. However, the Celo blockchain client starting with version 1.8.1 implements a protective mechanism that allows validators to control the percentage of available block space used by transactions paid with an alternative fee currency (other than CELO) more precisely.
There are two new flags that control this behavior:
-
celo.feecurrency.limits
with a comma-separatedcurrency_address_hash=limit
mappings for currencies listed in theFeeCurrencyWhitelist
contract, wherelimit
represents the maximal fraction of the block gas limit as a float point number available for the given fee currency. The addresses are not expected to be checksummed.For example,
0x765DE816845861e75A25fCA122bb6898B8B1282a=0.1,0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73=0.05,0xEd6961928066D3238134933ee9cDD510Ff157a6e=0
. -
celo.feecurrency.default
- an overridable default value (initially set to0.5
) for currencies not listed in the limits map, meaning that if not specified otherwise, a transaction with a given fee currency can take up to50%
of the block space. CELO token doesn't have a limit.
Based on historical data, the following default configuration is proposed:
--celo.feecurrency.limits.default=0.5
--celo.feecurrency.limits="0x765DE816845861e75A25fCA122bb6898B8B1282a=0.9,0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73=0.5,0xe8537a3d056DA446677B9E9d6c5dB704EaAb4787=0.5"
It imposes the following limits:
- cUSD up to 90%
- cEUR up to 50%
- cREAL up to 50%
- any other token except CELO - 50%
- CELO doesn't have a limit
Transaction Types On Celo L2
Table of Contents
Different categories of transaction types are relevant to Celo. Some are inherited from Ethereum, others were added to support Celo's Fee Abstraction feature, and some older ones have been superseded and are no longer supported. When developing new applications, please use the tx types marked as "recommended" below.
The docs.celo.org page about tx types contains additional information on how to handle different tx types, their differences and tooling support.
Ethereum Compatible Tx Types
To achieve its high level of Ethereum compatibility, Celo supports all Ethereum tx types relevant for an L2. The following transaction types can be used in exactly the same way as on Ethereum and don't require any changes to client libraries or other tooling.
- EIP-1559 (recommended), type 2
- EIP-2930, type 1
- Legacy Ethereum transaction as described in the Ethereum Yellow Paper, type 0
The EIP-4844 tx type 3, which provides blobs for data availability on Ethereum, is not supported.
Celo-Specific Tx Types
The following tx types are an essential part of Celo's Fee Abstraction feature. For more details, read the respective CIPs, linked below, and the Fee Abstraction section.
Older, Unsupported Tx Types
These tx types won't be accepted anymore, but transactions in blocks before the L2 migration can still contain transactions of these types.
- CIP-42, type 124
- Legacy Celo transaction, type 0, but with different fields than the Ethereum legacy tx
Details on Celo Legacy Transactions
For historic reasons, the Celo legacy txs are not prefixed with a tx type number, just like Ethereum legacy txs. To tell these two legacy tx types apart, you have to look at the tx content, which contains additional feeCurrency
, gatewayFeeRecipient
, and gatewayFee
fields for Celo legacy txs:
RLP([nonce, gasPrice, gasLimit, feeCurrency, gatewayFeeRecipient, gatewayFee, recipient, amount, data, v, r, s])
There is no CIP number for this tx type because it was included in version 1 of the Celo blockchain and CIPs only describe changes introduced after that point in time.
Native Bridge
Table of Contents
- Bridging CELO from L1 to L2
- Bridging CELO from L2 to L1
- Bridging ETH
- Bridged ERC20 Tokens
- Using Bridged Tokens as Fee Abstraction
With the L2 migration, the Celo blockchain gained a native bridge to Ethereum based on OP Stack's Standard Bridge. Specifically we use Custom Gas Token feature. This page describes the process of bridging assets between L1 and L2.
The Celo token now exists in both L1 and L2 versions. The L1 version is a standard ERC20 token with a total supply of 1 billion, fully minted to the OptimismPortal
smart contract, which is part of the bridge (this setup allows any Celo token holder on L2 to bridge their tokens to L1). The L2 version is the native token on the L2 Celo chain, preserving the balances from the Celo L1 chain. Tokens that have not yet been minted on the Celo L1 chain, such as tokens for Community Fund, are now minted to the CeloDistributionSchedule
, which manages further distribution.
Bridging CELO from L1 to L2
To deposit ERC20 Celo tokens onto the chain, users should use the OptimismPortalProxy.depositERC20Transaction
method. Before depositing tokens with depositERC20Transaction
, users must first call approve()
on the OptimismPortal
. After the deposit is made, L1 tokens are bridged, and an equivalent amount of tokens is minted as native Celo tokens in the user's account on Layer 2 (L2).
Bridging CELO from L2 to L1
To withdraw Celo from the L2 chain, users should use the L2ToL1MessagePasser.initiateWithdrawal
method. The process for proving and finalizing withdrawals is the same as it is on OP chains that use ETH as the native token.
Bridging ETH
Native ETH bridging is not supported for now since L1 bridge considers L1 Celo ERC20 as native token for Celo L2 and actively rejects any native ETH sent to the bridge. It is possible to bridge WETH (wrapped ETH) which behaves as standard ERC20 token both on L1 and L2.
Bridged ERC20 Tokens
ERC20 tokens can be bridged the same way as in the unmodified OP Stack, see Bridging ERC-20 Tokens to OP Mainnet With the Optimism SDK for a tutorial on this.
Using Bridged Tokens as Fee Abstraction
The OptimismMintableERC20
(used to represent ERC20 tokens from L1 on L2) supports the IFeeCurrency
interface, which is a requirement to use them as a Fee Abstraction token. Before a new OptimismMintableERC20
instance can actually be used as Fee Abstraction, it still has to be added to the FeeCurrencyDirectory
(0x71FFbD48E34bdD5a87c3c683E866dc63b8B2a685
) by Celo governance. This is currently only the case for WETH
.
EigenDA
Table of Contents
In contrast to OP Mainnet sequencer which writes TX batches to Ethereum in the form of calldata or, more recently, EIP-4844 blobs to commit to the transactions included in the canonical L2 chain, Celo uses EigenDA as an alternative data availability layer in order to minimize the TX fees. EigenDA is a data availability store made by EigenLabs and built on top of EigenLayer. With EigenDA, TX data is stored by EigenDA operators off-chain, only DA commitments used for verficiation and subsequent data retrieval are stored On Ethereum which significantly reduces DA costs and L2 TX fees.
The integration is done in accordance with the Optimism's Alt-DA spec which contains a more in-depth description of this interface.
Testnet and Contract Addresses
The Celo Dango testnet uses the EigenDA Holesky testnet. For the current list of deployed contract addresses, please refer to the EigenLayer's Current Testnet Deployment page.
Finality
Table of Contents
The Celo L2 provides two layers of economic security - the Celo-economic security and Ethereum-economic security. Celo-economic security is the security the L2 blockchain provides until data has been written to a L1 block and that block has been finalized. In order to provide reorg resistance at Celo-economic security in the L2 design, we need to address two sources of possible reorgs:
- Avoid discrepancies in what the sequencer shares over the p2p network and posts to the L1.
- Avoid those caused by Ethereum reorgs.
Finality and Reorgs in Optimism
Optimism L2 blocks have three levels of finality:
- Unsafe: Blocks are shared by the sequencer over the p2p network and can be reorged with no penalty.
- Safe: Blocks are deterministically derived from inputs and L2 data posted to not-yet-finalized Ethereum blocks. These blocks are susceptible to Ethereum reorgs.
- Finalized: Blocks are derived from finalized blocks on Ethereum and can practically not be reorged without massive economic costs. This is Ethereum-economic security.
Once a L2 block is finalized, it has the same security guarantees as the underlying L1 and are highly unlikely to be reorged. Therefore, our focus lies on avoiding reorgs at the unsafe and safe levels.
Unsafe head reorgs could happen in the following scenarios:
- The sequencer shares an unsafe block on the p2p network but misses the sequencing window to post the corresponding transaction data to Ethereum. As a result, there won’t be a valid block data for that height, and the safe head will reorg to use a generated empty block.
- The sequencer distributes an unsafe block on the p2p network but posts a different transaction data for the same block height to Ethereum, the safe head will reorg to use the data on Ethereum.
These cases are controlled by the sequencer. So if the user trust the sequencer and it's actions it can follow unsafe blocks. If not, it can completely avoid those blocks by only following safe blocks.
Safe head reorgs can occur in the following situations:
- An L1 block is reorged, then the corresponding sequencing epoch’s blocks will need to be updated to account for the changes in the L1 origin (specifically, deposit transactions). This situation can also cause an unsafe head reorg.
- If Ethereum reorgs such that transaction data posted either no longer exists or now falls outside of the sequencing window, then the corresponding (previously safe) L2 block will become an empty block.
Mitigating Ethereum Reorgs
Essentially, we need to mitigate Ethereum reorgs that affect L1 deposit transactions to prevent reorgs on Celo L2.
To do this, we modify the L1 origin used in the sequencer to follow only finalized blocks on Ethereum. This behavior can be opted into by setting the --sequencer.use-finalized
flag on the op-node instance that sequences new blocks.
In the sequencer, the L1 origin will still be incremented in steps of one, so that all invariants relying on this behavior in the Optimism codebase are kept.
Only using finalized blocks has the downside of increasing the time for user-deposited and (native) bridging transactions to be included in the L2 from currently 4 blocks (48 seconds) to at least 2 Ethereum epochs (64 slots, 12.8 mins). This doesn’t apply to optimistic bridges, so it most likely won’t hurt user experience in general.
There is also a possible problem if the Ethereum L1 fails to finalize blocks. In that case the sequencer would continue to create unsafe L2 blocks. Therefore, in that case the sequencer must stall and avoid producing blocks until L1 finalization catches up to below some threshold.
L1→L2 Migration Changes
Table of Contents
- Changes for Contracts Developers
- Changes for JSON-RPC Users
- State Changes during the Migration
- Other Changes
The switch from the Celo L1 blockchain to the Celo L2 introduces a variety of changes, most of which are not visible to the majority of developers and even less to the end users. However, tool developers, infrastructure operators and some developers will have to take a few of these changes into account. This page contains all information to check if this is the case for you or not.
Changes for Contracts Developers
- Removed precompiles (all except the
transfer
precompile) - During the migration the following hardforks are enabled:
- The following Optimism specific hardforks are enabled:
Precompile Deprecation and Epoch Management
As part of Celo's transition to a L2 network, the following Celo precompiles, except the Transfer
precompile, will be deprecated.
FRACTION_MUL
PROOF_OF_POSSESSION
GET_VALIDATOR
NUMBER_VALIDATORS
EPOCH_SIZE
BLOCK_NUMBER_FROM_HEADER
HASH_HEADER
GET_PARENT_SEAL_BITMAP
GET_VERIFIED_SEAL_BITMAP
This means that the geth client is no longer responsible for processing epochs. Instead, processing of epochs, rewards distribution and storage of currently elected validators is now handled by the EpochManager
contract.
Any contract supporting the use of precompiles will now revert on Celo as a L2. This includes the UsingPrecompiles
contract. More details here
To keep costs of processing epochs low, only the following key functions (used for querying the current epoch or elected validators) have been ported over to the [EpochManager] contract.
getEpochNumberOfBlock(uint256)
getEpochNumber()
validatorSignerAddressFromCurrentSet()
numberValidatorsInCurrentSet()
Read more on new epoch management and reward distribution or deprecated precompiles.
FeeCurrencyDirectory
We introduced a new contract, FeeCurrencyDirectory
, which is responsible for managing the fee currencies used in the Celo network. This contract is replacement for FeeCurrencyWhitelist
and keeps track of ERC-20 tokens that can be used as gas currencies on Celo network with additional setup of intrinsic gas cost of transactions for these fee currencies.
FeeCurrencyWhitelist
The FeeCurrencyWhitelist
contract has been replaced by the FeeCurrencyDirectory
contract.
Deactivated Random Contract
The Random
core contract has been deactivated. The EIP-4399 PREVRANDAO
opcode provide some pseudo-randomness now. Please be aware of the limitations mentioned in the EIP, as well as the following OP Stack specific limitations:
- The randao value is read from the L1, it is known a longer time in advance.
- Since multiple L2 blocks are derived from the same L1 block, the
PREVRANDAO
value will not change with every L2 block, but only with the L1 block.
Deactivated BlockchainParameters Contract
The BlockchainParameters
core contract has been deactivated.
The blockGasLimit
can now be found by querying the Optimism L1 SystemConfig
contract deployed at 0x43cb8878b4B62D9853452140eFB42CF30672e23a and calling the gasLimit()
getter.
The intrinsicGasForAlternativeFeeCurrency
can now be found by querying the FeeCurrencyDirectory contract function getCurrencyConfig(token).intrinsicGas
.
Updated Governance Hotfix
The Governance Hotfix process has undergone several changes due to the absence of validators on L2, now using a multisig approach. Here’s a detailed explanation of these changes:
Original Hotfix Process
Previously, the hotfix process relied heavily on a set of validators:
- Validator Approval: A byzantine quorum of validators was needed to whitelist a hotfix. Validators had financial incentives to act in the network's best interest, ensuring that any approved hotfix had been vetted by a trustworthy group.
- Dynamic Validator Set: The list of validators who approved the hotfix changed with each epoch. This dynamic nature made it difficult for validators to collude and approve a malicious hotfix.
- Epoch-Dependent: If a hotfix was not executed within the same epoch it was approved, it needed to be reapproved by the new set of validators in the next epoch.
- Prepare Step: The hotfix required a "prepare" step, ensuring that the current set of validators had approved the hotfix before it could be executed.
Updated Hotfix Process
Due to the absence of validators on L2, the process now incorporates a multisig approach:
- Multisig Approval: The new process requires the approval of an approver multisig, but now also includes the Security Council multisig.
- Fixed Signers: Unlike the previous dynamic set of validators, the list of Security Council signers remains fixed. This change simplifies the approval process but also increases the risk of collusion among the fixed set of signers.
- Execution Time Limit: If a hotfix is not executed within the specified
executionTimeLimit
, it must be reset and re-approved. This keeps the time constraint but no longer depends on epoch changes. - Collusion Risk: The fixed list of Security Council signers introduces a new risk factor. Without clear incentives for the signers to act in the network's best interest, there is a higher risk of collusion and the potential for malicious hotfixes being approved.
Changes for JSON-RPC Users
- Removed tx types, see also the tx types page:
- Celo legacy tx
- CIP-42
State Changes during the Migration
The migration is the process of converting the Celo L1 chain into an L2 based on Ethereum. This migration involves different steps and requires the blockchain to shortly pause block production.
During the migration the following things are done:
- Historic blockchain data such as blocks, headers and transactions are transformed into a version readable by the updated execution client. During this process some data, such as data required for the Istanbul consensus algorithm, is removed as it is no longer required.
- OP Stack L2 contracts are deployed.
- The new Celo unreleased treasury core contract is initialized.
Historical data migration
Celo started as a fork of go-ethereum
but initially some significant changes to the structure of headers and blocks were made because it was operating with a Proof of Stake consensus mechanism.
At the outset Celo blocks lacked the following fields that Ethereum blocks had:
sha3Uncles
uncles
difficulty
gasLimit
(thegasLimit
was defined by a contract and so had to be retrieved from state)mixHash
nonce
Later in the Espresso hardfork, dynamic fee transactions were introduced. Instead of relying on the baseFeePerGas
field on the block header, the baseFeePerGas
was also retrieved from state via a contract call similar to gasLimit
.
In the Gingerbread hardfork (block 21616000
) all the fields listed above plus baseFeePerGas
were added to the internal block representation to bring future blocks into alignment with Ethereum.
A constant difficulty field of 0x0
was added to all pre-Gingerbread RPC API block responses.
Celo added the following fields to block bodies:
randomness
epochSnarkData
As additions they do not damage compatibility at the RPC API level as API clients would generally ignore them. However, in the transition to L2 these fields are planned for removal because they will no longer be needed.
In the transition to L2 as an attempt to improve the situation with RPC API compatibility for historical blocks, all post-Gingerbread fields will be returned for all blocks.
Both pre-Gingerbread and post-Gingerbread blocks retrieved from the L2 RPC API would look the same with the exception of baseFeePerGas
which for now will not be returned for pre-Gingerbread blocks.
L2 Block Structure
Going forward, blocks occurring after the transition point will gain an extra field, parentBeaconBlockRoot
, which will bring our block structure fully up to date with Ethereum’s block structure.
As Ethereum evolves in the time before we make the transition to L2 we could end up with additional fields being added to the L2 Block Structure, and this document will be updated accordingly.
State migration
The OP stack requires a number of contracts to be available on the L2. Those contracts have predefined addresses and cannot be deployed like normal contracts. Instead they are written to the state during the migration.
As this process touches the blockchain state, it is important that it is transparent and can be verified by every node operator and user. Therefore, every node operator can do the migration locally and check the resulting state against Celo L2 state.
OP predeploys
The predeploys to be added to the state are supplied in form of an allocation file which contains a mapping of account addresses to their state. In the state migration tool this file is read and every account copied into the Celo L1 state.
There's a number of checks to make sure this doesn't end up causing problems.
- If an account to be written to the state already has a balance, this balance is added to the copied account balance. This makes sure the total amount of Celo doesn't change.
- If an account already contains code, it is checked that the code is the same.
CeloUnreleasedTreasury
set up
The CeloUnreleasedTreasury
is a new contract available on the migrated Celo L2. See the spec for more information.
During the migration it needs to be setup with the remaining unminted Celo.
This is done by first reading the total supply of Celo tokens at the time of the migration. This value is then subtracted from the max supply of Celo tokens, which is 1,000,000,000. This difference is the remaining amount of tokens that gets set as the balance of the distribution schedule contract.
Other Changes
- No need for validators, since a centralized sequencer is used
CIP-64 Receipts Now Contain the baseFee
For the Celo L1, the effectiveGasPrice
for CIP-64 txs is only available until the block state is pruned. Afterwards, the blockchain client is unable to get the baseFee
for the relevant feeCurrency
, which is required to calculate the effectiveGasPrice
. To avoid this and make the effectiveGasPrice
available permanently, the baseFee
is included as the last field in the RLP-encoded CIP-64 receipt for CIP-64 txs submitted after the L2 migration. The EIP-2718 ReceiptPayload
for this transaction type is now rlp([status, cumulativeGasUsed, logsBloom, logs, baseFee])
.