I believe this EIP needs more updates, I think we discussed this, but it needs to track which code is loaded and which is not. One could “warm” an address without incurring the extra costs by calling BALANCE on them for example, thus not paying for the ceil32(excess_contract_size) * 2 // 32 (but with the overhead of the BALANCE opcode in gas which I think is 100. This is rather small and it would already be net positive if the to-be-called contract exceeds the original limit of 100 * 2 * 32 = 640 bytes)
Also note that we can warm accounts by adding them to the EIP-2930 access lists. This will thus not incur the extra ceil32(excess_contract_size) * 2 // 32 costs. Since adding the cold items touched to the access list is already a net benefit, this bonus is now thus larger (for large contracts).
I have no direct solution for this but calling the largest contract would cost (256 * 1024) * 2 // 32 = 16384 gas which is rather expensive (although the JUMPDEST analysis should be accounted for )
As mentioned in ACDE I would appreciate more metrics around the jumpdest analysis
Atm the metrics are based on a C++ implementation in a toy program run on unknown hardware?
Rather than using the EL implementations in a block, or using their test infra (where limits can be exceeded); and also machine specs, difference if using ARM vs x64
If I read correctly you are suggesting it only takes 22.86 ms for 15MiB? Whereas I am sure there were security concerns raised that it took too long at much lower sizes
At 60M gas could do 512kB of initcode at 736512 gas (0 on deployed contract size) meaning 81 contracts per block; so 40.5MB of jumpdest?
Has the following potential grieving scenario been considered?
Assume there are valid use cases of calling EXTCODECOPY on untrusted targets, one example coming to mind is from a case for “delegation introspection” made in this post. Gas cost of the EXTCODECOPY(target, 0, 0, 23) used there would go up from ~2600 to ~17000 if malicious targets manage to be injected.
For the specific 7702-related check there is a workaround of preceding EXTCODECOPY with a EXTCODESIZE to avoid grieving like this, but it does increase the caveats involved.
this is a good point, and to me is a further argument that EXTCODESIZE should not be subject to the pricing in this PR, so that users can prevent themselves from griefing attacks.
Would adding more byte code examples with larger sizes in geth’s jumpdest test here be a good place to start? Or would it be better to add some measuring to geth around the jumpdest analyis function, compile, and then try testing?
I have a couple rockpi ARM devices that meet current validator hardware requirements to run tests on too
At 60M gas could do 512kB of initcode at 736512 gas (0 on deployed contract size) meaning 81 contracts per block; so 40.5MB of jumpdest?
Are there any other test cases which might maximize the ratio of hardware usage to gas cost?
The max code size is also limited by EIP-7825: Transaction Gas Limit Cap. With the proposed transaction gas limit of 30M you will be able to deploy a contract up to ~145k bytes.
To study how a large contract may impact the cache (paging, L1, L2, etc), I wrote a program to generate a contract code that constantly jumps to the next page (the pattern can be improved), and a simple EVM simulator to evaluate the performance between 24KB and 256KB contracts.
Here are my results:
On MacBook Pro M2 (ARM):
go run page_cost.go --code=code_24KB --num=64000 => 3.870s
go run page_cost.go --code=code_256KB --num=6000 => 3.900s
There is about 0.7% difference
On AMD 5950x (x86, Ubuntu, go1.22.7)
taskset -c 1 go run page_cost.go --code=code_24KB --num=64000 => 4.513s
taskset -c 1 go run page_cost.go --code=code_256KB --num=6000 => 4.557s
There is about 1% difference (note that the task is bonded to a single core to avoid random perf results due to weird go routine scheduling).
I think it would be confusing if the code max limits were above what could actually be deployed, so I’d be in favor of having the code limits fit within the gas limit. So 24KB → 145KB (or say the contract limit is set to 128KB) is still meaningful enough to contract devs to include the increase in limit, and adding the gas metering now makes it more easy to do future code limit increases. And a ~5x instead of a ~10x here reduces some risk
I would not assume it is easy to do future code limit increases. The EIP actually had unlimited code size and just allowed size to increase with gas limit, but a hard limit was added after it was realized that there might be interactions with the p2p layer, or that L2s might have much higher gas limits see e.g. Add EIP: Meter Contract Code Size And Increase Limit by charles-cooper · Pull Request #9483 · ethereum/EIPs · GitHub. But I still think it’s better to have extra growing room if the gas limit increases (or the gas schedule somehow changes so that it is cheaper to deploy contracts).
With keeping the original limits, we could just add warnings in dev tools like foundry/hardhat when a contract is close to the allowable size derived from the txn gas limit and quickly adjust there if/when the gas txn limit increases. That should reduce confusion.
I see that the largeContractCost is not charged when a large contract is called by a transaction. Is there an assumption that the intrinsic transaction cost of 21k gas sufficiently covers that? Given that largeContractCost can max out at about 15k gas this may not be obvious and/or maybe deserves a mention in the Rationale sections.
While the main motivation for metering (DoS vector) is not so strong in such case, there might be 2nd order effects to consider, e.g. that there would be a premium for having a large contract called at the top level, as opposed to via a proxy or a 7702 delegate.
Tangentially - it should be clarified that calling a contract in a txn warms its code for the duration of the txn.
Lastly, note that the large contract we’re considering here might not be sitting at tx.to - it might be at the delegation target if tx.to holds a 7702 indicator, so the spec needs to be clear on what happens then.
Edit: This validation is done as a pre-execution check and the txn isn’t included.
It looks like code is loaded in 7702 Step 5 of processing authorization_list where typically code for an EOA is empty or just 0xef0100 || address, but it could be MAX_CODE_SIZE if the authority is a max sized contract. 7702 spec states to fail the individual authorization and continue processing the list.
Not sure if it would be practical for clients to limit code loading here to size(0xef0100 || address), otherwise it might make sense to apply loading code costs here too.
We should add largeContractCost for contract’s at tx.to to the spec. It looks like it is processed like a CALL in clients and might be why it was left out.
Agree
it should be clarified that calling a contract in a txn warms its code for the duration of the txn.
In the 7702 tx.to case, if it loads an EOA’s delegation smart contract code then it makes sense to charge the metering. I’ll look for this as I’m digging into 7702 related code.
No strong opinion on the gas price changes (either way would be okay), but doubling the contract code size would make it so much easier to write cleaner / more explicit code.