<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Andrew Chiaramonte</title>
        <link>https://paragraph.com/@andrewc</link>
        <description>writer of smart contracts and english</description>
        <lastBuildDate>Fri, 08 May 2026 14:41:47 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Andrew Chiaramonte</title>
            <url>https://storage.googleapis.com/papyrus_images/8cba37809fedffd830184f44a514c05f48cce35bc1f8fbb3c3d8b794e8387ecb.png</url>
            <link>https://paragraph.com/@andrewc</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Arbitrum Precompile Etching & InvalidFEOpcode]]></title>
            <link>https://paragraph.com/@andrewc/arbitrum-precompile-etching-invalidfeopcode</link>
            <guid>qDfTSLzth0A7vd65mTHO</guid>
            <pubDate>Mon, 21 Oct 2024 23:52:23 GMT</pubDate>
            <description><![CDATA[📚 Background: Arbitrum PrecompilesThe 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, Andrew Chiaramonte🚧 Problem: Fork TestingWhen 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 ...]]></description>
            <content:encoded><![CDATA[<h2 id="h-background-arbitrum-precompiles" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">📚 Background: Arbitrum Precompiles</h2><p>The Synthetix v3 instance on Arbitrum relies on the <code>ArbGasInfo</code> precompile to determine gas costs incurred by third parties, such as liquidators, who act autonomously to help maintain system stability.</p><p><strong>Authors:</strong> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://x.com/Jared_Borders">Jared Borders</a>, Andrew Chiaramonte</p><h2 id="h-problem-fork-testing" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">🚧 Problem: Fork Testing</h2><p>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.</p><p>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.</p><blockquote><p><strong>💡 Precompiles 101</strong></p><p>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.</p><p>Precompiles offer functionality such as cryptographic methods, memory copying utilities, and tools for facilitating L1 ↔ L2 interactions.</p></blockquote><h3 id="h-spotting-the-fork-testing-issue" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Spotting The Fork Testing Issue</h3><p>When fork testing encounters a problem with precompiles, it typically manifests as follows:</p><ol><li><p>The test reverts with an <code>InvalidFEOpcode</code></p></li><li><p>This revert occurs in conjunction with a call to a precompile address</p></li></ol><p><strong>Note:</strong> The <code>FE</code> in <code>InvalidFEOpcode</code> refers to the INVALID opcode, which serves as a catch-all for undefined bytecode.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5aa7a8654ffd0bb1facae02c5adbf174a98b8c70d9be5bcba90e4ef214bada46.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-identifying-a-precompile" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Identifying a Precompile:</h3><p>After the test reverts with an <code>InvalidFEOpcode</code>, check if the function being called (e.g., <code>getPricesInWei()</code>) is on a contract deployed at &quot;GENESIS&quot; when viewed on a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://arbiscan.io/address/0x000000000000000000000000000000000000006C">block explorer</a>: A contract deployed at “GENESIS” indicates a precompile.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/80efb7fddd09d2cca5b25c71ff962b978e86ae9bc7299ccaf71dc9fda14c92e0.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-solution-etching-with-foundry" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">⛏️ Solution: Etching with Foundry</h2><p>Leveraging Foundry&apos;s library of <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://book.getfoundry.sh/cheatcodes/">cheatcodes</a>, we can easily inject bytecode into the address where the protocol expects it to exist. This is achieved using Foundry&apos;s <code>etch</code> cheatcode.</p><p>Foundry&apos;s documentation specifically mentions using <code>etch</code> to address precompile-related issues:</p><blockquote><p>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 <code>vm.etch</code> cheatcode to inject mock of the missing precompile to the address it is expected to appear at.</p></blockquote><p>Given this, the following Solidity code demonstrates how to circumvent the dependency issue that arises between Synthetix v3 and Arbitrum&apos;s <code>ArbGasInfo</code> precompile:</p><pre data-type="codeBlock" text="pragma solidity 0.8.27;

import {Test} from &quot;forge-std/Test.sol&quot;;

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;
    }

}
"><code><span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.27;</span>

<span class="hljs-keyword">import</span> {<span class="hljs-title">Test</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"forge-std/Test.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">BootstrapArbitrumFork</span> <span class="hljs-keyword">is</span> <span class="hljs-title">Test</span> </span>{

    <span class="hljs-keyword">address</span> ARB_GAS_INFO_PRECOMPILE <span class="hljs-operator">=</span> <span class="hljs-number">0x000000000000000000000000000000000000006C</span>;
        
        <span class="hljs-comment">/// @dev expand the parameters to fine-tune the mock</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mockArbGasInfo</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> x</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        ArbGasInfoMock arbGasInfoMock <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> 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, <span class="hljs-keyword">address</span>(arbGasInfoMock).<span class="hljs-built_in">code</span>);
    }

}

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ArbGasInfoMock</span> </span>{

    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">immutable</span> L2_TX;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">immutable</span> L1_CALLDATA_BYTE;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">immutable</span> STORAGE_ALLOCATION;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">immutable</span> ARB_GAS_BASE;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">immutable</span> ARB_GAS_CONGESTION;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">immutable</span> ARB_GAS_TOTAL;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">immutable</span> L1_BASE_FEE_ESTIMATE;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> _L2_TX,
        <span class="hljs-keyword">uint256</span> _L1_CALLDATA_BYTE,
        <span class="hljs-keyword">uint256</span> _STORAGE_ALLOCATION,
        <span class="hljs-keyword">uint256</span> _ARB_GAS_BASE,
        <span class="hljs-keyword">uint256</span> _ARB_GAS_CONGESTION,
        <span class="hljs-keyword">uint256</span> _ARB_GAS_TOTAL,
        <span class="hljs-keyword">uint256</span> _L1_BASE_FEE_ESTIMATE
    </span>) </span>{
        L2_TX <span class="hljs-operator">=</span> _L2_TX;
        L1_CALLDATA_BYTE <span class="hljs-operator">=</span> _L1_CALLDATA_BYTE;
        STORAGE_ALLOCATION <span class="hljs-operator">=</span> _STORAGE_ALLOCATION;
        ARB_GAS_BASE <span class="hljs-operator">=</span> _ARB_GAS_BASE;
        ARB_GAS_CONGESTION <span class="hljs-operator">=</span> _ARB_GAS_CONGESTION;
        ARB_GAS_TOTAL <span class="hljs-operator">=</span> _ARB_GAS_TOTAL;
        L1_BASE_FEE_ESTIMATE <span class="hljs-operator">=</span> _L1_BASE_FEE_ESTIMATE;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getL1BaseFeeEstimate</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
        <span class="hljs-keyword">return</span> L1_BASE_FEE_ESTIMATE;
    }

}
</code></pre><h3 id="h-mocking-real-world-scenarios" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Mocking Real World Scenarios</h3><p>When deploying the <code>ArbGasInfoMock</code> in practice, dummy values can be used for all variables, as seen <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Kwenta/smart-margin-v3/blob/arbitrum-release-zap/test/utils/mocks/ArbGasInfoMock.sol">here</a>. But in the case of precompiles that require precise values, you can look at the precompile contract on a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://arbiscan.io/address/0x000000000000000000000000000000000000006C#readContract">block explorer</a> and mock the values you get from the read functions.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b1d2f18b71c753061ff9770abe30128a6004ffb9b47f2c9fd43597452b4a2fb2.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure>]]></content:encoded>
            <author>andrewc@newsletter.paragraph.com (Andrew Chiaramonte)</author>
        </item>
    </channel>
</rss>