Deposit Fee design #112
-
Originally written by @ben-chain here, moved here for easier discussion. In the interest of soliciting external feedback, I'm going to try to write each tradeoff as succinctly as possible for readers. Here's how I would put it: Problem StatementWe would like to make deposits as cheap as possible in the happy case. In 1.0, we can get the L1 gas down to <5k, which would open up a DoS if unchecked. We need a way to block/increase the cost of deposits if they start getting spammed. We've identified two main approaches, corresponding to 1 and 2 in the first section here. A) L1 EIP1559 mechanism: the deposit contract on L1 limits deposit frequency by increasing cost under congestion. TradeoffsHere's what the effects of these options are. Failure Mode UX
Both failure modes require that users replay the deposit TX (from L1 in A, from L2 in B), but option B) is worse. This is because the L1 half of the transaction still succeeds, causing devex issues. Cost
It is possible to implement option B without any Failure Mode Likelihood
Difference in Ratelimit Trigger
This is minor, but it does mean that option A) will trigger more easily/at lower throughput. If we refund the extra ETH paid in option A), we will have to deal with an attacker's deposit consuming very little gas on L2, or else the cost of a spam attack would be significantly lower for option A). The mitigation I see is probably to reinforce a minimum gas usage or only refund above a certain amount or % of gas, but it may make the cost of attack proportionally cheaper in A). |
Beta Was this translation helpful? Give feedback.
Replies: 11 comments 31 replies
-
There is a third alternative, one that we were actually considering even more strongly (sorry if that wasn't clear from the previous thread) which is to not do a congestion control system on L1 at all. This remove the (We would still burn L1 gas or charge an ETH fee on L1, but the price of this would be dependent on the price of L1 gas, and not on the number of deposits, or L2 congestion in general.) The idea is that DoSing the chain from L1 is not really possible if we burn L1 gas, because the largest amount of L2 gas that could be consumed by the deposits of an L1 block is (Such a "DoS" is still possible if paying ETH for L2 gas instead of burning L1 gas, something I hadn't considered!) The only problem with this is that it is possible (it would take some extreme level of L2 congestion though) for it to become temporarily cheaper to transact via deposits than via L2. I think people agree that this is less important than minimizing deposit gas cost.
The current plan is not to ever refund ETH for deposits. Such refunds do introduce extra complexity (esp. given the stipend), are not particularly intuitive, and we expect 95%+ of deposits to be effectively free because of the stipend. There are further mitigations for people who would like to do "dynamic calls" (calls that might consume little or much ETH), search "dynamic calls" in the previous thread. |
Beta Was this translation helpful? Give feedback.
-
Some stream-of-consciousness notes on my end:
Generally speaking, after thinking about this more, I feel that we should treat accounts on L1 and L2 as fundamentally different and that we should have the L1 account pay for the action on L1. In particular, I think this should be done as a gas burn on L1 like we currently do. I think the main question to be answered then is how we determine the burn ratio, effectively "how much is L2 gas worth right now relative to L1 gas?". I feel that we will ultimately have to come up with an automated mechanism to handle this some time in the future. Any fixed value that we choose will either have to be extremely expensive or will eventually be so cheap that it's more efficient to send all transactions on L1 instead of going through the Sequencer. Using a dynamic fee means getting information from L2 that tells us how congested L2 is. Congestion can spike up or down in the short term and it takes 7 days for information to flow from L2 to L1. However, it might be the case that this doesn't matter on short time windows and we can get away with using the |
Beta Was this translation helpful? Give feedback.
-
@smartcontracts I agree with a lot of what you've written and am coming around to L1 metering more and more. I think the remaining concern I have is still what we discussed here in the second half--in the world where L2 is completely uncongested, and you want a larger L2 gaslimit than already burned by calling the L1 deposit function, then you have to burn additional L1 gas, which is ultimately overpaying given the resource price on L2. I would propose the following modification to the scheme to solve for this without introducing too much complexity. I think this might basically be what we meant with "combining proto and norswap's approaches" in the last thread, but I didn't have my head wrapped around it yet.
So basically, you can try to pay for additional gas, that can then be refunded--but no guarantees.
|
Beta Was this translation helpful? Give feedback.
-
@ben-chain sounds good to me. Previously I would go for the other variant: remove the "Checking if the gas is sufficient and diverting execution to a fallback" can technically be moved to the application layer with above variant, but it's probably a good idea to build it into the deposit system to avoid surprises for users and enforce it as a practice. |
Beta Was this translation helpful? Give feedback.
-
The main issue I see with this proposal is that it enables DoS. There are no intrinsic limits on how big your Even if we do impose a L1-side limit on If we impose a gas limit on the deposit block, we either have to enforce it on L1 (incurring |
Beta Was this translation helpful? Give feedback.
-
Had a great discussion with @K-Ho, here's a summary:
|
Beta Was this translation helpful? Give feedback.
-
Writing down a remark by @ben-chain: if we at some point we want to have a "rollup-on-a-rollup" (i.e. L3), then we will need some form of explicit limit, because L3 won't be able to take 25x the L2 block size (which, in the worst case is itself as big as 25x the L1 block size). Own note: given the lower gas costs of L2, it's probably more feasible to implement EIP-1559 on L2 for L3. |
Beta Was this translation helpful? Give feedback.
-
Updated ProposalHere's where we have come to with these discussions, and the path ahead to finalization/open decisions. It is a combination of @norswap 's original proposal and @protolambda 's idea for a simplified, optional way to pay for gas at L2 prices. This is basically me expanding on this comment here and discussing next steps. Summary
Pending DecisionsFinalize deposit TX type & check implementation overheadWe need to make sure that implementing this form of deposits is not too much implementation complexity in the engine, and precisely what the tx type would be. I will include a proposal based on @protolambda 's post in the older thread separately to this post. This decision is independent of the others below; we can finalize the engine before making any of the below decisions, which are both at the L1 contract/block derivation level. Acceptable Gas StipendsAs stated above, L1 gas expenditures will be credited towards a stipend. We need to know what the minimum meaningful stipend to provide is. This is fundamentally a developer experience question. Note that we can count trusted L1 codepaths' expenditures towards the stipend. For example, and ERC20 deposit costs more on L1 due to
Note that we can allow more than this minimum to be purchasable, so devs can buy as much guaranteed gas as they want on L1, but ideally we'd like no additional cost in the standard code paths for deposits (minting on L2). This decision will inform what we go with in the next decision: How to rate limit stipends on L1Our solution needs to guarantee that the total gas guaranteed by L1 does not exceed our maximum L2 gas/second.
edit: see below for clarification I would recommend that we denominate this rate limit in ETH, not L1 gas. This would work as follows: the deposit contract would track an
|
Beta Was this translation helpful? Give feedback.
-
Adding more color: Deposit ContractImplements EIP4396 basefee calculations in solidity. Storage
Ideally, these values could be compressed into one slot. Interface/functionality
Optional gasIf we want L2-purchasable, optional gas, we would do:
|
Beta Was this translation helpful? Give feedback.
-
Crunched the numbers comparing gas costs on current OP network to what we'll have with/without the on-chain EIP1559:
Conclusion:
I'm leaning strongly towards the EIP1559 with this context.
|
Beta Was this translation helpful? Give feedback.
-
Reposting for visibility/transparency an internal document that summarizes the design we arrived at from this discussion, as well as following points of discussion: Deposit Fees Design SummaryDesign Goals
Orthogonal to this proposal but complementary:
Context: L2 Gas PriceOn L2, the current proposal is to price gas using EIP-4396, a variant of EIP-1559 that targets a stable transacton throughput by time instead of by block. Proposed MechanismsEach L1 deposit would include the following parameters, specified by the user and relayed in the L1 event that will be read by the rollup node:
Guaranteed GasThe guaranteed gas is gas paid for on L1. It itself comprises:
We need guaranteed gas because we want a mechanism for guaranteed execution If This advantage is also the major inconvenient: See the "Pricing Guaranteed Gas" section below for a further discussion of the gas stipend on how to price guaranteed gas. Note that the user cannot opt out of the Finally note that due to its "gas-burn" nature, unused guaranteed gas is never refunded. Additional GasBesides guaranteed gas, the user can also request additional gas which will be paid on L2, and thus be subjected to the L2 fee pricing mechanism. The user specified how much additional gas he wants to purchase with On L2, if the deposit can purchase gas for Multiple remarks:
The advantage of
Any ETH leftover after paying for the actual transaction gas is sent to the Pricing Guaranteed GasNaive Guaranteed Gas PricingThe whole problem in pricing guaranteed gas is that it allows to bypass the L2 congestion control mechanim. That being said, guaranteed gas is not infinite, as L1 has its own congestion control mechanism (EIP-1559). There is therefor a lever which is how much L2 gas an attacker can cause to be used by unit of L1 gas spent. The absolute worst-case scenario being: how much L2 gas most we be able to process if a whole L1 block worth of L1 gas is dedicated to attacking L2? A simple approach is simply to set the "L2 gas purchasing power" of one unit of L1 gas (i.e. the lever) to Note that one L2 block can also contain additional transactions besides deposits but:
Gas Stipend LeverThe problem is with the We would like the gas stipend to cover the most common use case, which is an ERC-20 token transfer, as that would make most deposits as cheap as possible. Etherscan estimates an ERC-20 transfer to 65k gas (though that can vary a lot For comparison, the BSC chain handles between 10 and 20 times the throughput of Ethereum, and sometimes struggles towards the high end of that range. So we can probably handle a 75k stipend with some engineering (especially since an attack that purchases 100% of the Ethereum block space, let along for a long duration, is not really practical). L1 Congestion Control ("EIP-1559")Say that the L2 chain has 10x the throughput of Ethereum. This make our naive lever One solution is to implement a congestion control mechanism on L1 (like EIP-1559), where there is a This would enable us to have a low cost for guaranteed gas in general, but to increase the cost rapidly as a larger portion of L1 gas gets used to send deposits. Note that the mechanism can also apply to the Avoiding Gas BurnWhile there is a need for guaranteed gas (to avoid reverts), gas burning is very wasteful. Without a L1 congestion control mechanism, we need it to avoid an infinite lever however. With the congestion control mechanism however, we're free to pay for guaranteed gas in ETH. The idea would be to provide the ability to pay in ETH as an alternative to the gas burn. We would keep the gas burn — which has maximally simple UX: there is no need to attach ETH and no need to compute the ETH needed to pay for a given amount of guaranteed gas: the computation is taken care of by the node's gas estimation logic. However, we'd also allow paying for guaranteed gas in ETH, and would incentivize for this by giving a small discount (5%?) when using ETH for payment. Note that enabling guaranteed gas payment in ETH means we most definitely can't reuse EIP-1559 as-is, because it only updates every block. Using EIP-1559 would allow someone to use a stupendous amount of ETH to purchase a ton of cheap L2 gas all-at-once in order to DOS the chain. Not to mention the technical challenges of handling blocks with unbounded amount of ETH (and potentially unbounded amounts of transactions). (Unless that is, if we put a hard cap on the L2 gas usable by deposit every L1 block, meaning deposits that would cause gas the L2 gas consumption to go beyond this limit would revert. That's a pretty unexpected UX imho, better to let the user go to the frontend, try, see the huge fee, and figure out they should retry later. Though we should also make it abundantly clear that the fees do vary and are not fixed!) DiscussionAbove is the consensus we came to last time. Feedback from Kevin is that it's pretty complex. I do agree, although everything has a justification. If I were to convey to developers what to use, here's what I would say:
Things should be introduced in that order, with a strong recommendation on the first mode because it's the simplest. One thing that's not quite clear to me though: that purchasing additional gas will necessary be cheaper than purchasing guaranteed gas with an L1 congestion mechanism. If we add to make one simplification to the design, I would make it that we simply drop additional gas altogether. Although we could also go in the other direction, keep additional gas, but also allow the additional gas to be paid by L2 balances. I think there's also something to be said about making the protocol more complex to accomodate UX: we could introduce a "retry tx" type which retries a previous deposit transaction that reverted because it ran out of gas. This would assuage most of my concern with additional gas and even letting users pay for deposits with L2 balances. Protocol Office Hours Discussion from 25/03
|
Beta Was this translation helpful? Give feedback.
Adding more color:
Deposit Contract
Implements EIP4396 basefee calculations in solidity.
Storage
prev_deposit_timestamp
:self.parent(self.parent(block)).timestamp
for L1 EIPbase_fee_per_guaranteed_gas
self.parent(block).base_fee_per_gas
in L1 EIPguaranteed_gas_allotted_in_block
:self.parent(block).gas_used
in L1 EIPIdeally, these values could be compressed into one slot.
Interface/functionality
deposit(guaranteedGasLimit, target, data) payable
: chargesguaranteedGasLimit * base_f…