Post-mortem: How an internal audit led to Etherlink 6.2

This is a joint post from Nomadic Labs, TriliTech & Functori.

On March 25 2026, at level #41,222,566, the Etherlink 6.2 kernel upgrade activated successfully on Etherlink Mainnet, following a fast governance vote earlier this week. The new kernel, a security bugfix from commit 7af992cf274a0902c172b5e9829397107e817178, addresses four vulnerabilities uncovered during an internal audit leveraging agentic AI improvements to our development stack. One of these vulnerabilities was independently reported by a security researcher at the same time via the Tezos Foundation bug bounty platform.

In this blogpost, we provide a comprehensive, technical description of each vulnerability and its associated patch. We also reflect on how these issues were discovered, the lessons learnt from this journey and our plans to ensure the highest level of security for Etherlink Mainnet moving forward.

:white_check_mark: No user funds or assets were at risk at any point before the activation of Etherlink 6.2. The vulnerabilities described below threatened the chain’s liveness (its ability to process transactions), not the safety of funds already onchain.

:warning: Breaking change: As a consequence of the fixes in Etherlink 6.2, type 4 transactions require a larger amount of gas in Etherlink 6.2 than before. Operators are strongly advised to upgrade to v0.56 of the Octez EVM node, which computes the required eth_estimateGas envelope correctly.

Etherlink 6.2 Changelog

Etherlink 6.2 is a security upgrade addressing 4 vulnerabilities that were affecting Etherlink Mainnet. These vulnerabilities were first and foremost threatening the chain’s liveness—no user funds and assets were at risk before the activation of the security upgrade.

  • Etherlink’s smart rollup Denial of Service (DoS) vulnerability: an attacker could perform an ill-formed deposit aborting the execution of the smart rollup powering Etherlink Mainnet, preventing the L2 chain to progress and funds to be deposited.
  • Draining the sequencer’s balance: an attacker could craft large Ethereum transactions with underapproximated DA fees. As a result, the sequencer would not be compensated for posting these transactions up to the cost they would have had to cover on Tezos L1.
  • Etherlink sequencer Denial of Service (DoS) vulnerability: an attacker could craft a transaction leading the sequencer to produce an invalid sequence of transactions, effectively preventing the inclusion of other, legitimate transactions and the production of new blocks.
  • Theoretical theft of FA tokens bridged from Tezos L1 (impractical in production): an attacker could steal FA tokens deposited from Tezos L1 and owned by Etherlink’s externally owned accounts (EOA). No tokens deployed on Etherlink Mainnet are owned directly by EOAs, they are all managed by L2 smart contracts.

Etherlink’s smart rollup DoS vulnerability – Critical severity, not exploited

TL;DR An attacker could have halted the chain entirely by sending a deposit to an empty L2 address, preventing any transactions or bridge deposits from being processed.

Etherlink is powered by a Tezos L1 smart rollup (sr1Ghq66tYK9y3r8CC1Tf8i8m5nxh8nTvZEf). Being a first-class citizen of the Tezos L1 protocol, it can notably own tickets. Depositing tez on Etherlink directly relies on this feature under the hood:

There is a smart contract deployed on Tezos L1 which wraps tez as tickets. Then, depositing tez on Etherlink entails calling the default() entrypoint of the smart rollup with the tickets holding the wrapped tez and a L2 address receiver (20 bytes) as argument. The resulting call is turned into a marshaled message, which is added to the smart rollup’s shared inbox.

The Etherlink kernel consumes messages from the shared inbox, knowing it can safely credit the balance of the receiver’s L2 address based on the ticketer of the tickets it has received. This requires decoding the full message. Notably, the part of the business logic which decodes the receiver’s L2 address is implemented in the Etherlink 6.1 kernel by the following snippet:

pub struct DepositInfo {
    pub receiver: DepositReceiver,
    pub chain_id: Option<U256>,
}

impl DepositInfo {
    fn decode(bytes: &[u8]) -> Result<Self, DecoderError> {
        let version = bytes[0];
        // ...

Readers familiar with Rust are likely to spot the danger: bytes[0] is unchecked, meaning that if the bytes array is empty, the Etherlink kernel panics: execution stops, and any remaining message in the shared inbox is not processed.

Critically, there were no guards in Etherlink 6.1 preventing a user from depositing tez to 0x (the empty bytes address). It was therefore possible for an attacker to exploit this vulnerability as a Denial of Service attack.

The patch is trivial (as often is the case for such flaws): always guard against empty bytes, and return an error instead of panicking.

if bytes.is_empty() {
    return Err(DecoderError::Custom("Unexpected empty deposit info"));
}

This guard was added in commit adb09943f3400e16fc7561ab0cd719495d216b47.

Draining the sequencer’s balance – Mid severity, not exploited

TL;DR an attacker could craft large transactions that cost more to post on Tezos L1 than the fees they paid, slowly draining the sequencer’s funds.

The sequencer is responsible for bundling transactions together (up to every 500ms), and is entrusted with publishing these bundles on Tezos L1. It is incentivized to do so by being the recipient of the DA fees paid by users (see Etherlink’s fee structure). DA fees are computed based on how large a given transaction is by the da_fee function in the fees module:

pub(crate) fn da_fee(
    da_fee_per_byte: U256,
    tx_data: &[u8],
    access_list: &[AccessListItem],
) -> U256 {
    let access_data_size: usize = access_list
        .iter()
        .map(|ali| size_of::<H160>() + size_of::<H256>() * ali.storage_keys.len())
        .sum();

    U256::from(tx_data.len())
        .saturating_add(ASSUMED_TX_ENCODED_SIZE.into())
        .saturating_add(access_data_size.into())
        .saturating_mul(da_fee_per_byte)
}

The issue here is that this function was not updated to account for the changes brought up by the Ebisu (Etherlink 5.0) upgrade, which implemented compatibility with EIP-7702. This EIP allows externally owned accounts (EOAs) to temporarily delegate execution to a smart contract and introduces authorization lists as a managing mechanism.

Crucially, the business logic was not updated to consider authorization lists, which can be arbitrarily long. This can lead to the sequencer receiving less tez than the actual cost of posting such a transaction on Tezos L1. As a consequence, an attacker could decide to effectively drain the sequencer funds. The patch simply consists in addressing this omission:

// Size of a single EIP-7702 authorization entry:
// chain_id (U256=32) + address (H160=20) + nonce (u64=8)
// + y_parity (u8=1) + r (U256=32) + s (U256=32) = 125 bytes
const AUTHORIZATION_ENTRY_SIZE: usize = size_of::<U256>()
    + size_of::<H160>()
    + size_of::<u64>()
    + size_of::<u8>()
    + 2 * size_of::<U256>();

/// Data availability fee for a transaction with given data size.
pub(crate) fn da_fee(
    da_fee_per_byte: U256,
    tx_data: &[u8],
    access_list: &[AccessListItem],
    authorization_list_len: usize,
) -> U256 {
    let access_data_size: usize = access_list
        .iter()
        .map(|ali| size_of::<H160>() + size_of::<H256>() * ali.storage_keys.len())
        .sum();

    let authorization_data_size = authorization_list_len * AUTHORIZATION_ENTRY_SIZE;

    U256::from(tx_data.len())
        .saturating_add(ASSUMED_TX_ENCODED_SIZE.into())
        .saturating_add(access_data_size.into())
        .saturating_add(authorization_data_size.into())
        .saturating_mul(da_fee_per_byte)
}

:warning: Breaking change: As a consequence of this fix, type 4 transactions require a larger amount of gas in Etherlink 6.2 than before. Starting from v0.56, the EVM node estimates the required envelope in eth_estimateGas correctly. Operators are strongly advised to upgrade their setup accordingly.

This additional cost was added in commit 087a8314a625b78a499329fae21a9f439ae6428e.

Sequencer DoS vulnerability – Critical severity, not exploited

TL;DR An attacker could send a transaction that emptied their own account mid-execution, causing the sequencer to produce invalid blocks and stall the chain.

The third vulnerability addressed by Etherlink 6.2 is also related to how DA fees were processed on Etherlink before 6.2. More specifically, it concerns transaction validity and how the presence of an invalid transaction is handled by the system.

A transaction in Etherlink is valid—and can therefore be included in a transaction bundle—if the signer of that transaction can pay its fees. Fees are over-approximated by multiplying the gas limit of the transaction with the current gas price, and the rule in place since Etherlink Mainnet beta was simply that a transaction was valid if and only if the overapproximated fees plus the amount of tez transferred by the transaction were lower or equal to the balance of the sender. The sequencer operator drops invalid transactions instead of including them, which ensures that it always perceives DA fees and that no unpaid computation can happen on the chain.

Once a valid transaction is inserted in a bundle, it is first executed, and then DA fees are debited from its balance. This procedure was safe as long as explicit transfers of tez (i.e., the value of a transaction) were the only way to reduce the balance of the signer—which is no longer the case since adopting EIP-7702.

Indeed, it is possible to have an EOA send 0 tez to itself, triggering the execution of EVM code that can perform arbitrary actions like moving the full balance to another account. This would prevent the debit of the DA fees, highlighting that the transaction was indeed invalid, and resulting in the rejection of the bundle altogether. If transactions like this were to be submitted in quick succession, it would have effectively prevented the sequencer from producing new blocks, disrupting the chain as a result.

The solution was to debit DEA fees before starting the execution of the transaction, preventing an attacker from moving the tez expected to be used to pay DA fees out of the account using EIP-7702.

This new behavior was implemented in commit 1a6edb457e228e49c0bdfd40bccfcf784340f67f.

Theoretical theft of FA tokens bridged from Tezos L1 – High severity, not exploitable

TL;DR in theory, an attacker could have withdrawn FA tokens owned by another account — but only tokens held directly by an EOA, which never happens in typical Mainnet usage.

The FA bridge is a critical component of Etherlink Mainnet. It enables Tezos L1 FA compliant assets to be moved to Etherlink. In practice, these assets are managed by EVM smart contracts, exposing them as ERC20 tokens to the Etherlink ecosystem. The FA bridge also supports transferring ownership of FA tokens deposited from Tezos L1 directly to an arbitrary L2 address. It is used as a failsafe mechanism in case the smart contract minting the ERC20 tokens fails to do so. However, the smart contracts used by the FA bridge never fail to mint new ERC-20 tokens.

:green_circle: This vulnerability was never exploitable on Etherlink Mainnet in practice. FA tokens deposited via the bridge are always managed by EVM smart contracts, never held directly by EOAs. The scenario required to exploit this flaw does not occur under typical usage.

An EOA owning FA tokens directly can only do one action with them: withdrawing them to a Tezos L1 address of their choice. However, the withdrawal authorization logic for this particular case was flawed, effectively allowing any Etherlink account to withdraw any FA tokens directly owned by any EOA. This is because the withdrawal logic was tailored to the smart contract-owned tokens use case.

The withdrawal function of the FA bridge contract deployed on Etherlink Mainnet works as follows:

  • The user requests the withdrawal of a specific ticket, notably specifying its current owner.
  • The FA bridge smart contract calls the withdraw method of the ticket owner, which decides whether to authorize the withdrawal or not.
  • If the ticket owner authorizes the withdrawal, the procedure proceeds as expected.

Critically, calling the withdraw method of an EOA would succeed as per EVM semantics, effectively authorizing the withdrawal issued by an attacker.

The fix consists in distinguishing between smart contracts and EOAs (with or without a set authorization list):

    function isWallet(address addr) internal view returns (bool) {
        bytes memory code = addr.code;

        // Plain EOA
        if (code.length == 0) return true;

        // EIP-7702 delegation designator: exactly 23 bytes starting with 0xef0100
        if (code.length == 23 && code[0] == 0xef && code[1] == 0x01 && code[2] == 0x00) return true;

        return false;
    }

On Etherlink 6.2, we use this function in the withdrawal methods to refine the authorization logic, forcing the caller to be the ticket owner of the ticket in case that ticket owner is not a smart contract.

This guard was added in commit 2df9ded61613dc64f511b074a442a62aa1f54daa.

Keeping Etherlink Mainnet secure

As we mentioned in the introduction, these vulnerabilities were detected in an internal audit of the Etherlink kernel, ahead of a planned minor upgrade. Keeping up with latest advancements in agentic software development, we have been incorporating these tools to our development, testing and quality assurance stack. In particular, the issues addressed by Etherlink 6.2 were the result of two agent-powered audits, using Claude Code and Codex Security.

Even when the Etherlink codebase is thoroughly peer-reviewed and tested, 3 of the issues discussed today originate from oversights and corner cases when implementing support for EIP-7702 in the Ebisu kernel upgrade.

We are living through a scientific revolution, and the tools we have available today to improve the safety and quality of our products are orders of magnitude better than those available a year, even months ago. Alas, they are also available to a would-be attacker.

This experience reinforces our intuition into why onboarding and adopting these tools is a must. Their adoption will pay dividends quickly by enabling us to evolve Etherlink faster while elevating our quality and security standards.

:folded_hands: We also want to thank the security researcher who independently reported one of these vulnerabilities via the Tezos Foundation bug bounty platform as well.

6 Likes