truffle icon indicating copy to clipboard operation
truffle copied to clipboard

Truffle displays an incorrect cost deploying to Arbitrum Rinkeby

Open kevinweaver opened this issue 2 years ago • 10 comments


Issue

When deploying a contract to Arbitrum, truffle displays a total cost that is different from the actual cost.

Steps to Reproduce

truffle unbox arbitrum in .env, set an infura key and a mnemonic with Arbitrum Rinkeby ETH accessible here npm run compile:arbitrum npm run migrate:arbitrum --network=arbitrum_testnet

You will see something like:

   Deploying 'SimpleStorage'
   -------------------------
   > transaction hash:    0xb3142a02eb78cc5f6f682100bc9a13cdc94c9cb3b45ddcae85739d3416266caf
      ...
   > total cost:          0.000013137251486502 ETH

Take this transaction ID to https://testnet.arbiscan.io.

Expected Behavior

The cost that truffle reports should be the same as the cost arbiscan reports.

Actual Results

truffle total cost: 0.000013137251486502 ETH arbiscan total cost: 0.00001050980184 ETH

The support ticket example showed an even larger difference using a much larger contract:

truffle total cost: 2.69049662 ETH arbiscan total cost: 0.00271965395244 ETH.

Note

I confirmed that this is specific to Arbitrum and not other L2s like Optimism

Environment

  • Operating System:
  • Ethereum client:
  • Truffle version (truffle version): v5.4.16
  • node version (node --version): c14.18.0
  • npm version (npm --version): 8.3.0

kevinweaver avatar Dec 20 '21 16:12 kevinweaver

Ah! It looks like we aren't accounting for the fact that Arbitrum fees are surfaced differently than mainnet fees:

Arbitrum receipt:

{
    "blockHash": "0x46b4605bb9c89f3f2c19b74b5c5aa1d77a4af090bba473b4d49396fd6b0cc564",
    "blockNumber": 7794449,
    "contractAddress": "0x0EA91c4d350458880BADf654031E1FcA30B3C2Eb",
    "cumulativeGasUsed": 4053,
    "effectiveGasPrice": "0xa02494",
    "feeStats": {
        "paid": {
            "l1Calldata": "0x4bbb9c60620",
            "l1Transaction": "0x4640cbf4d10",
            "l2Computation": "0x9e7631b24",
            "l2Storage": "0x0"
        },
        "prices": {
            "l1Calldata": "0x47f06e8c",
            "l1Transaction": "0x4640cbf4d10",
            "l2Computation": "0xa02494",
            "l2Storage": "0x1e8b7a11660"
        },
        "unitsUsed": {
            "l1Calldata": "0x10d8",
            "l1Transaction": "0x1",
            "l2Computation": "0xfd5",
            "l2Storage": "0x0"
        }
    },
    "from": "0x61de0bbb6c8215af3f821fe4884a28bc737f98d3",
    "gasUsed": 959933,
    "l1BlockNumber": "0x965b70",
    "l1InboxBatchInfo": null,
    "logs": [],
    "logsBloom": "0x0...0",
    "returnCode": "0x0",
    "returnData": "0x0...0",
    "status": true,
    "to": null,
    "transactionHash": "0x5c6c6befbf30fec1643ca6acb00c6afb78759c9038d75fedfe7534b94d4bf6da",
    "transactionIndex": 0,
    "rawLogs": []
}

Local Ethereum receipt:

{
    "transactionHash": "0xf2843b54d907a0eccfbec804699a4237857f7d8257ef55d1dc4c62b648c127f1",
    "transactionIndex": 0,
    "blockHash": "0x0d196855441c0aeb4fe04a0303f949a555acedd061cddbb6ee13749c6c58d89f",
    "blockNumber": 9853902,
    "from": "0x61de0bbb6c8215af3f821fe4884a28bc737f98d3",
    "to": null,
    "gasUsed": 96405,
    "cumulativeGasUsed": 96405,
    "contractAddress": "0xa6839894F879b3cb0FfB9F5Df467e1DFf1ED4Aa9",
    "logs": [],
    "status": true,
    "logsBloom": "0x0...0",
    "rawLogs": []
}

kevinweaver avatar Dec 21 '21 17:12 kevinweaver

Whoa this is really weird. Good digging @kevinweaver! Is there any documentation for this receipt format?

gnidan avatar Dec 22 '21 23:12 gnidan

There is some documentation on the tx receipt format for Arbitrum here.

However, I wonder if this is actually a problem that these receipts look different. @kevinweaver will try this again and run the exact same transaction to be sure we are comparing truffles to truffles, but it looks like on the Truffle side we report this based on the gasUsed field which exists in both receipts (see line 80 here). Will be helpful to compare this value in both receipts when running the exact same tx I think.

fainashalts avatar Jan 03 '22 20:01 fainashalts

It looks like the feeStats metadata above is more of a symptom than the specific issue.

 Truffle uses some data from the transaction response and some from the receipt we get back:

const gasPrice = new BN(tx.gasPrice);
const gas = new BN(receipt.gasUsed);
const value = new BN(tx.value);

to calculate the cost:

const cost = gasPrice.mul(gas).add(value);

Run with an example transaction, using these values gives us:

const gasPrice = 13671205
const gas = 959032
const value = 0

and the calculated cost:

const cost = 1.3111123e+13 // 13671205 * 959032 + 0;
//      0.00001311112307356 ETH formatted correctly

Meanwhile Arbiscan displays the real transaction fee as:

Transaction Fee: 0.00001048913955 ETH 

Gas Price Bid:   0.000000000013671205 ETH
Gas Price Paid:  0.000000000010936964 ETH

Notice the “Gas Price Bid” is the same gasPrice amount we rely on in our calculations. “Price Bid” does not exist on main net/rinkeby etherscan and doesn’t seem relevant to the price paid.

 To solve this, we can’t rely on the gasPrice in the transaction. If we pull from the new effectiveGasPrice field in the receipt, we get a closer answer, but it’s still not 100%

effectiveGasPrice: '0xa6e284' // 10936964 in decimal

Replacing gasPrice with effectiveGasPrice in our calculations:

const cost = 1.0488898e+13 // 10936964 * 959032 + 0;

gives us something closer to the correct cost:

1.0488898e+13.   
// 0.00001048913955 is the “correct” answer.

Currently looking through their docs to understand this better, I'm assuming it has to do with the complexity discussed here: https://developer.offchainlabs.com/docs/arbgas

kevinweaver avatar Jan 04 '22 22:01 kevinweaver

Thanks for digging into this @kevinweaver! I suggest you reach out in their discord (not in the support channel though, ConsenSys runs that so it will just go to us!) with a question about this discrepancy in case they have any additional info.

I think it makes sense to dig more here and figure out what exactly goes into the calculation we see on Arbiscan. Looking forward to seeing what you find out!

fainashalts avatar Jan 05 '22 00:01 fainashalts

Heya, you can find the custom tx receipt fields documented here

The gas fee market in Arbitrum works a bit differently to the L1 - it is inspired by EIP 1559 but adapted to fit the L2 requirements. There is a base cost of getting your tx processed. If demand for L2 resources are high, the base cost rises. If demand is low, it decreases.

The gas price submitted in a tx only represents the maximum you are willing to pay, the value actually charged will depend on network congestion, similar to a 2nd price auction.

The gasPrice in the tx response is the bid a user submitted. The value actually charged is available in the feeStats object under feeStats.price.l2Computation. This is also the same value that is surfaced on the effectiveGasPrice field.

For most accurate view on values charged users I would suggest you add the fields returned in the receipt's feeStats.paid as these are the values that the user was charged for (all denominated in wei)

fredlacs avatar Jan 05 '22 11:01 fredlacs

Understood, thanks for the clarification @fredlacs! I just tried adding those 4 feeStats.paid values with my example transaction and came up with the accurate cost.

To support this from the truffle side, I see a few ways forward that I'm curious to hear your thoughts on @gnidan:

  1. Add support within truffle/interface-adapter. Something like this, but cleaner:
if("feeStats" in receipt && "paid" in receipt["feeStats"]){
  //Arbitrum
  cost = new BN(Object.values(receipt["feeStats"]["paid"]).reduce((sum, value) => sum + value));
} else {
  cost = new BN(gasPrice.mul(gas).add(value));
}

A little strange to find L2 specific code in truffle and though this isn't the case with Optimism, it's potentially something we could continue to see in the future.

  1. Add support within the arbitrum-box. I'm not sure what this looks like off the top of my head, perhaps some sort of migrate command wrapper that appends a warning with the correct value after? Very weird UX, unless there's a better way to approach from the box direction.

  2. Create something like an Arbitrum plugin that overrides Truffle components with Arb specific logic. (I'm not familiar enough with truffle plugins to even say that's how they work).

Any other ideas?

kevinweaver avatar Jan 05 '22 21:01 kevinweaver

If it's worth anything, here are my 2 cents: I think that the effectiveGasPrice from the tx receipt should be used instead of the gasPrice from the response when deriving this cost (regardless of the network).

The calculation using effectiveGasPrice instead of the feeStats in Arbitrum seems to be a bit off due to rounding on the node - which is something that we've flagged internally. While feeStats will give you the exact cost, the effectiveGasPrice * gasUsed will be pretty accurate.

fredlacs avatar Jan 06 '22 13:01 fredlacs

If effectiveGasPrice will eventually be updated to fix the inaccuracy, then this^ would definitely simplify things. And as per the spec, we should be migrating toward effectiveGasPrice regardless as gasPrice was deprecated post-EIP-1559.

kevinweaver avatar Jan 06 '22 17:01 kevinweaver

Just chatted with @davidmurdoch: in ganache v7 eth_getTransactionReceipt returns effectiveGasPrice, but no gasPrice. In v6 we'd only have gasPrice.

I think

const gasPrice = new BN(receipt.effectiveGasPrice ?? tx.gasPrice);

should work with no other changes, but I'll test on different ganache versions and networks.

kevinweaver avatar Jan 06 '22 18:01 kevinweaver

@fainashalts says this is no longer true!

cds-amal avatar Nov 17 '22 18:11 cds-amal