# Arbitrum Precompile Etching & InvalidFEOpcode

By [Andrew Chiaramonte](https://paragraph.com/@andrewc) · 2024-10-21

---

📚 Background: Arbitrum Precompiles
-----------------------------------

The Synthetix v3 instance on Arbitrum relies on the `ArbGasInfo` precompile to determine gas costs incurred by third parties, such as liquidators, who act autonomously to help maintain system stability.

**Authors:** [Jared Borders](https://x.com/Jared_Borders), Andrew Chiaramonte

🚧 Problem: Fork Testing
------------------------

When writing tests for the Synthetix v3 protocol, it is often necessary to fork the Arbitrum network. This approach simulates a realistic system state, enabling more efficient and higher-quality testing with less effort.

However, an issue arises when forking because precompile addresses do not contain bytecode. As a result, any protocol methods relying on these precompiles will revert when called.

> **💡 Precompiles 101**
> 
> Precompiles are smart contracts with special addresses that provide specific functionality, executed natively by the EVM client rather than at the bytecode level. This allows precompiles to perform tasks that would otherwise be costly, difficult, or even impossible to implement with standard smart contract code. As a result, they enable smoother and more efficient execution of specialized tasks within the EVM.
> 
> Precompiles offer functionality such as cryptographic methods, memory copying utilities, and tools for facilitating L1 ↔ L2 interactions.

### Spotting The Fork Testing Issue

When fork testing encounters a problem with precompiles, it typically manifests as follows:

1.  The test reverts with an `InvalidFEOpcode`
    
2.  This revert occurs in conjunction with a call to a precompile address
    

**Note:** The `FE` in `InvalidFEOpcode` refers to the INVALID opcode, which serves as a catch-all for undefined bytecode.

![](https://storage.googleapis.com/papyrus_images/5aa7a8654ffd0bb1facae02c5adbf174a98b8c70d9be5bcba90e4ef214bada46.png)

### Identifying a Precompile:

After the test reverts with an `InvalidFEOpcode`, check if the function being called (e.g., `getPricesInWei()`) is on a contract deployed at "GENESIS" when viewed on a [block explorer](https://arbiscan.io/address/0x000000000000000000000000000000000000006C): A contract deployed at “GENESIS” indicates a precompile.

![](https://storage.googleapis.com/papyrus_images/80efb7fddd09d2cca5b25c71ff962b978e86ae9bc7299ccaf71dc9fda14c92e0.png)

⛏️ Solution: Etching with Foundry
---------------------------------

Leveraging Foundry's library of [cheatcodes](https://book.getfoundry.sh/cheatcodes/), we can easily inject bytecode into the address where the protocol expects it to exist. This is achieved using Foundry's `etch` cheatcode.

Foundry's documentation specifically mentions using `etch` to address precompile-related issues:

> Some chains, like Blast or Arbitrum, run with custom precompiles. Foundry is operating on vanilla EVM and is not aware of those. If you are encountering reverts due to not available precompile, you can use `vm.etch` cheatcode to inject mock of the missing precompile to the address it is expected to appear at.

Given this, the following Solidity code demonstrates how to circumvent the dependency issue that arises between Synthetix v3 and Arbitrum's `ArbGasInfo` precompile:

    pragma solidity 0.8.27;
    
    import {Test} from "forge-std/Test.sol";
    
    contract BootstrapArbitrumFork is Test {
    
        address ARB_GAS_INFO_PRECOMPILE = 0x000000000000000000000000000000000000006C;
            
            /// @dev expand the parameters to fine-tune the mock
        function mockArbGasInfo(uint256 x) public {
            ArbGasInfoMock arbGasInfoMock = new ArbGasInfoMock({
                _L2_TX: x,
                _L1_CALLDATA_BYTE: x,
                _STORAGE_ALLOCATION: x,
                _ARB_GAS_BASE: x,
                _ARB_GAS_CONGESTION: x,
                _ARB_GAS_TOTAL: x,
                _L1_BASE_FEE_ESTIMATE: x
            });
            
            vm.etch(ARB_GAS_INFO_PRECOMPILE, address(arbGasInfoMock).code);
        }
    
    }
    
    contract ArbGasInfoMock {
    
        uint256 immutable L2_TX;
        uint256 immutable L1_CALLDATA_BYTE;
        uint256 immutable STORAGE_ALLOCATION;
        uint256 immutable ARB_GAS_BASE;
        uint256 immutable ARB_GAS_CONGESTION;
        uint256 immutable ARB_GAS_TOTAL;
        uint256 immutable L1_BASE_FEE_ESTIMATE;
    
        constructor(
            uint256 _L2_TX,
            uint256 _L1_CALLDATA_BYTE,
            uint256 _STORAGE_ALLOCATION,
            uint256 _ARB_GAS_BASE,
            uint256 _ARB_GAS_CONGESTION,
            uint256 _ARB_GAS_TOTAL,
            uint256 _L1_BASE_FEE_ESTIMATE
        ) {
            L2_TX = _L2_TX;
            L1_CALLDATA_BYTE = _L1_CALLDATA_BYTE;
            STORAGE_ALLOCATION = _STORAGE_ALLOCATION;
            ARB_GAS_BASE = _ARB_GAS_BASE;
            ARB_GAS_CONGESTION = _ARB_GAS_CONGESTION;
            ARB_GAS_TOTAL = _ARB_GAS_TOTAL;
            L1_BASE_FEE_ESTIMATE = _L1_BASE_FEE_ESTIMATE;
        }
    
        function getL1BaseFeeEstimate() external view returns (uint256) {
            return L1_BASE_FEE_ESTIMATE;
        }
    
    }
    

### Mocking Real World Scenarios

When deploying the `ArbGasInfoMock` in practice, dummy values can be used for all variables, as seen [here](https://github.com/Kwenta/smart-margin-v3/blob/arbitrum-release-zap/test/utils/mocks/ArbGasInfoMock.sol). But in the case of precompiles that require precise values, you can look at the precompile contract on a [block explorer](https://arbiscan.io/address/0x000000000000000000000000000000000000006C#readContract) and mock the values you get from the read functions.

![](https://storage.googleapis.com/papyrus_images/b1d2f18b71c753061ff9770abe30128a6004ffb9b47f2c9fd43597452b4a2fb2.png)

---

*Originally published on [Andrew Chiaramonte](https://paragraph.com/@andrewc/arbitrum-precompile-etching-invalidfeopcode)*
