Appendix

Address alias

In the OP Stack (Optimism Stack), the address aliasing mechanism is primarily used to differentiate between Layer 1 (L1) and Layer 2 (L2) addresses. This mechanism ensures that when an L1 contract deposits transactions into L2, the address that appears as the "caller" on L2 isn't the original L1 contract address but an aliased version of it.

How Address Aliasing Works:

  • Address Aliasing Mechanism: When an L1 contract interacts with L2 (for example, by sending a transaction to deposit tokens or execute a call), the L1 contract’s address is modified on L2 using a specific rule. In Optimism, the common aliasing approach is to add a value of 0x1111000000000000000000000000000000001111 to the original L1 contract’s address. This modified address is then used on L2 as the "caller." The math is unchecked and done on a Solidity uint160 so the value will overflow.
    • For example:
    • If an L1 contract has an address 0xabc...123 and interacts with an L2 contract, the address that the L2 contract will see as the "caller" is 0xabc...123 + 0x1111000000000000000000000000000000001111.

Why Address Aliasing is Necessary:

This prevents attacks in which a contract on L1 has the same address as a contract on L2 but doesn't have the same code. We can safely ignore this for EOAs because they're guaranteed to have the same "code" (i.e. no code at all).
For example, if developer uses a contract factory to deploy contracts which uses create opcode. create opcode calcualtes contract address based on sender address and sender’s nonce. So it is possible that the factory deploys two different contracts with different code but same address on L1 and L2 by controling the nonce. In this situation, if some L2 contracts trust the logic of the factory-deployed L2 contract, then attacker can use the factory-deployed L1 contract to deposit transaction on L1 to interact with other L2 contracts which is dangerous.

Tying L2 Output root to L1 blockhash

L2 will scan messages deposited from L2, and generate corresponding message on L2 to execute. So L2 block is also based on L1 status. During L2 root proposal, by tying L2 root to hash of L1 blockhash, it ensures that the L2 root is actually generated by current L1 without reorg.
If a user deposits a transaction to bridge soem asset from L1 to L2, and L2 accepts the messages and mints corresponding asset on L2 and generates L2 root. If later the L1 reorgs and discards user’s deposited message, when L2 submits the root, the transaction reverts, which prevents data inconsistency between L1 and L2.
/// --- packages/contracts-bedrock/src/L1/L2OutputOracle.sol --- /// @notice Accepts an outputRoot and the timestamp of the corresponding L2 block. /// The timestamp must be equal to the current value returned by `nextTimestamp()` in /// order to be accepted. This function may only be called by the Proposer. /// @param _outputRoot The L2 output of the checkpoint block. /// @param _l2BlockNumber The L2 block number that resulted in _outputRoot. /// @param _l1BlockHash A block hash which must be included in the current chain. /// @param _l1BlockNumber The block number with the specified block hash. function proposeL2Output( bytes32 _outputRoot, uint256 _l2BlockNumber, bytes32 _l1BlockHash, uint256 _l1BlockNumber ) external payable { require(msg.sender == proposer, "L2OutputOracle: only the proposer address can propose new outputs"); require( _l2BlockNumber == nextBlockNumber(), "L2OutputOracle: block number must be equal to next expected block number" ); require( computeL2Timestamp(_l2BlockNumber) < block.timestamp, "L2OutputOracle: cannot propose L2 output in the future" ); require(_outputRoot != bytes32(0), "L2OutputOracle: L2 output proposal cannot be the zero hash"); if (_l1BlockHash != bytes32(0)) { // This check allows the proposer to propose an output based on a given L1 block, // without fear that it will be reorged out. // It will also revert if the blockheight provided is more than 256 blocks behind the // chain tip (as the hash will return as zero). This does open the door to a griefing // attack in which the proposer's submission is censored until the block is no longer // retrievable, if the proposer is experiencing this attack it can simply leave out the // blockhash value, and delay submission until it is confident that the L1 block is // finalized. require( blockhash(_l1BlockNumber) == _l1BlockHash, "L2OutputOracle: block hash does not match the hash at the expected height" ); } emit OutputProposed(_outputRoot, nextOutputIndex(), _l2BlockNumber, block.timestamp); l2Outputs.push( Types.OutputProposal({ outputRoot: _outputRoot, timestamp: uint128(block.timestamp), l2BlockNumber: uint128(_l2BlockNumber) }) ); }