# Demystifying Arbitrum's Fees General-purpose Ethereum L2s need to charge fees for two things: 1. As an Ethereum-compatible chain, they charge for their own gas just like Ethereum does. 2. The amount of L1 gas they'll need to commit themselves to your transaction on L1. This is mostly about making the calldata of your transaction available on L1. To do this, every L2 transaction effectively consumes an amount of L1 gas and an amount of L2 gas. To clarify, here's what those terms mean: - **L1 gas**: The amount of gas the L2 will need to spend on L1 to support your transaction, charged at the L1 gas price - **L2 gas**: The amount of gas used directly by the L2 chain itself (in accordance with the [ethereum yellowpaper](https://ethereum.github.io/yellowpaper/paper.pdf)), charged at the L2 gas price Or at least, this is how I (Andrew Morris) use these terms, and how I believe they are understood in the Ethereum community. Arbitrum introduces 2 gotchas to these terms that make Arbitrum's fees particularly difficult to understand. ## Disclaimer *Before continuing, I'd like to emphasise that L2s have hard problems to solve. Tradeoffs need to be made. Confusion is understandable and not unique to Arbitrum. I would like to see Arbitrum improve the way they report gas, not because they did a bad job, but because there's always room to improve.* ## Gotcha #1 **The `gasUsed` field reported by Arbitrum is not L2 gas.** Normally, the amount of gas is purely determined by the transaction's full data (not just the data field) and the EVM ops it performs. This makes gas predictable - you can measure your transaction's gas on a different chain as long as the relevant state is the same, including in a dev environment. Normally that relevant state is reasonably controllable, but on Arbitrum it includes a complex system that is unique to Arbitrum. The problem that Arbitrum is solving with this system is that all L2s need to charge an additional fee for the cost of proving to L1 that they executed your transaction correctly. Because this additional fee is about doing work on L1, it is best understood as an amount of L1 gas. Arbitrum's system simply increases `gasUsed` to whatever number is needed such that the fee they charge is equal to `gasUsed` * `gasPrice`. Because the fee is dominated by this additional cost of posting to L1, very little of their `gasUsed` number is L2 gas. This solution has advantages: 1. The fee deducted from your account is `gasUsed` * `gasPrice`, consistent with L1 2. You can restrict the fee you're willing to pay using `gasLimit` and gas pricing fields The disadvantage is the confusion. It's reasonable to expect that L2 gas is the amount of gas reported by the L2. On Arbitrum it's a very different thing. ## Gotcha #2 **Arbiscan provides a breakdown of L1 and L2 gas, but their "L1 gas" is not L1 gas.** [![Arbiscan advanced tx-info annotated example](https://hackmd.io/_uploads/Bykq3vodp.png)](https://arbiscan.io/tx/0x8cfa217845239345cc6559b3efc6e7ea43b19dbcad8b3f7685d0c86fecdd5900#txninfo) This breakdown is useful to get the L2 gas, but the L1 gas reported here is actually just their `gasUsed` field with the L2 gas subtracted. It's neither L1 gas nor L2 gas. It's an amount of extra hypothetical L2 gas that would compensate them for their L1 costs, charged at the L2 gas price. That number is difficult to put an accurate label on, which is kinda the whole reason why this system is confusing. ## Bakery Analogy Imagine a bakery that sells cookies and cakes. Today a cookie costs $2 and a cake costs $50. You decide to buy 5 cookies and 1 cake. This costs $60, but the store's checkout has some unusual software that can only charge for cookies, so you end up with this receipt: ``` Cookies: 30 // gasUsed Price/cookie: $ 2.00 // gasPrice ====================================== Total: $60.00 // total fee ``` You're an engineer, so you give a nod of understanding. You need an itemization for your records though, and the bakery happily adds this note for you: ``` Cakes: 25 // "L1 gas" Cookies: 5 // L2 gas ====================================== Total: 30 // gasUsed ``` This is partly helpful because it accurately records that you bought 5 cookies, but 25 cakes is wrong. Furthermore, even knowing that the thinking behind this note was that 25 was picked to get the total of 30 cookies, there's no way to deduce from the receipt and note that you bought 1 cake, or that cakes were selling for $50 each that day. This is what you want instead: ``` Cakes: 1 // L1 gas Price/cake: $50.00 // L1 gas price Cookies: 5 // L2 gas Price/cookie: $ 2.00 // L2 gas price ====================================== Total: $60.00 // total fee ``` ## Calculating the Actual L1 Gas We need to find the L1 gas price used, then we can just calculate: `Poster Fee` / `l1GasPrice` or equivalently: `"L1 Gas"` * `l2GasPrice` / `l1GasPrice`. For a quick approximation, we can just look up what L1 was charging for gas at about the same time as the Arbitrum transaction. To be more thorough, Arbitrum provides [precompiles](https://docs.arbitrum.io/for-devs/dev-tools-and-resources/precompiles#arbgasinfo) that give on-chain insight about Arbitrum's view of the L1 gas price. As far as I can tell, calling [`ArbGasInfo`](https://docs.arbitrum.io/for-devs/dev-tools-and-resources/precompiles#arbgasinfo) (at address `0x6c`) with `getL1GasPriceEstimate()` is what we want. Here's an example of how to do that with cURL: ```sh curl -X POST --data \ '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0x000000000000000000000000000000000000006c","data":"0x055f362f"},"0xa1040b4"],"id":1}' \ -H 'Content-Type: application/json' \ https://rpc.ankr.com/arbitrum ``` (Replace `0xa1040b4` with the block number of your transaction. You'll need to convert it to hex.) Response: ```json {"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000003ff7f6ec2"} ``` So `0x3ff7f6ec2` is the L1 gas price estimate for that block, which is 17,171,443,394 (17.1 gwei). The block used in this example matches the [transaction from the screenshot](https://arbiscan.io/tx/0x8cfa217845239345cc6559b3efc6e7ea43b19dbcad8b3f7685d0c86fecdd5900), so we can divide 0.0000318701 ETH (=31,870,100,000,000 wei) by 17,171,443,394 to get 1,856 L1 gas. If all goes well in your example, the result should be very close to a whole number of L1 gas like it did here, which I think is pretty good evidence that this really is what Arbitrum is doing internally.