diff --git a/arbos/arbosState/arbosstate.go b/arbos/arbosState/arbosstate.go index a15f95b8f5..18d6baf5ad 100644 --- a/arbos/arbosState/arbosstate.go +++ b/arbos/arbosState/arbosstate.go @@ -277,6 +277,8 @@ func (state *ArbosState) UpgradeArbosVersion(upgradeTo uint64, firstTime bool) e // no state changes needed case 7: // no state changes needed + case 8: + // no state changes needed default: return fmt.Errorf("unrecognized ArbOS version %v, %w", state.arbosVersion, ErrFatalNodeOutOfDate) } diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 7b337ab3e0..6c67d35900 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -177,7 +177,7 @@ func ProduceBlockAdvanced( complete := types.Transactions{} receipts := types.Receipts{} - gasPrice := header.BaseFee + basefee := header.BaseFee time := header.Time expectedBalanceDelta := new(big.Int) redeems := types.Transactions{} @@ -237,15 +237,15 @@ func ProduceBlockAdvanced( return nil, nil, err } - if gasPrice.Sign() > 0 { + if basefee.Sign() > 0 { dataGas = math.MaxUint64 posterCost, _ := state.L1PricingState().GetPosterInfo(tx, poster) - posterCostInL2Gas := arbmath.BigDiv(posterCost, gasPrice) + posterCostInL2Gas := arbmath.BigDiv(posterCost, basefee) if posterCostInL2Gas.IsUint64() { dataGas = posterCostInL2Gas.Uint64() } else { - log.Error("Could not get poster cost in L2 terms", "posterCost", posterCost, "gasPrice", gasPrice) + log.Error("Could not get poster cost in L2 terms", "posterCost", posterCost, "basefee", basefee) } } diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 30134e5bd0..a8bb4a0abd 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -6,7 +6,6 @@ package arbos import ( "errors" "fmt" - "math" "math/big" "time" @@ -42,6 +41,7 @@ type TxProcessor struct { PosterFee *big.Int // set once in GasChargingHook to track L1 calldata costs posterGas uint64 computeHoldGas uint64 // amount of gas temporarily held to prevent compute from exceeding the gas limit + delayedInbox bool // whether this tx was submitted through the delayed inbox Callers []common.Address TopTxType *byte // set once in StartTxHook evm *vm.EVM @@ -62,6 +62,7 @@ func NewTxProcessor(evm *vm.EVM, msg core.Message) *TxProcessor { state: arbosState, PosterFee: new(big.Int), posterGas: 0, + delayedInbox: evm.Context.Coinbase != l1pricing.BatchPosterAddress, Callers: []common.Address{}, TopTxType: nil, evm: evm, @@ -350,13 +351,14 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r return false, 0, nil, nil } -func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error { +func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (common.Address, error) { // Because a user pays a 1-dimensional gas price, we must re-express poster L1 calldata costs // as if the user was buying an equivalent amount of L2 compute gas. This hook determines what // that cost looks like, ensuring the user can pay and saving the result for later reference. var gasNeededToStartEVM uint64 - gasPrice := p.evm.Context.BaseFee + tipReceipient, _ := p.state.NetworkFeeAccount() + basefee := p.evm.Context.BaseFee var poster common.Address if p.msg.RunMode() != types.MessageCommitMode { @@ -366,9 +368,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error { } posterCost, calldataUnits := p.state.L1PricingState().PosterDataCost(p.msg, poster) if calldataUnits > 0 { - if err := p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits); err != nil { - return err - } + p.state.Restrict(p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits)) } if p.msg.RunMode() == types.MessageGasEstimationMode { @@ -378,29 +378,28 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error { minGasPrice, _ := p.state.L2PricingState().MinBaseFeeWei() - adjustedPrice := arbmath.BigMulByFrac(gasPrice, 7, 8) // assume congestion + adjustedPrice := arbmath.BigMulByFrac(basefee, 7, 8) // assume congestion if arbmath.BigLessThan(adjustedPrice, minGasPrice) { adjustedPrice = minGasPrice } - gasPrice = adjustedPrice + basefee = adjustedPrice // Pad the L1 cost in case the L1 gas price rises posterCost = arbmath.BigMulByBips(posterCost, GasEstimationL1PricePadding) } - if gasPrice.Sign() > 0 { - posterCostInL2Gas := arbmath.BigDiv(posterCost, gasPrice) // the cost as if it were an amount of gas - if !posterCostInL2Gas.IsUint64() { - posterCostInL2Gas = arbmath.UintToBig(math.MaxUint64) - } - p.posterGas = posterCostInL2Gas.Uint64() - p.PosterFee = arbmath.BigMul(posterCostInL2Gas, gasPrice) // round down + if basefee.Sign() > 0 { + // Since tips go to the network, and not to the poster, we use the basefee. + // Note, this only determines the amount of gas bought, not the price per gas. + + p.posterGas = arbmath.BigToUintSaturating(arbmath.BigDiv(posterCost, basefee)) + p.PosterFee = arbmath.BigMulByUint(basefee, p.posterGas) // round down gasNeededToStartEVM = p.posterGas } if *gasRemaining < gasNeededToStartEVM { // the user couldn't pay for call data, so give up - return core.ErrIntrinsicGas + return tipReceipient, core.ErrIntrinsicGas } *gasRemaining -= gasNeededToStartEVM @@ -413,7 +412,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) error { *gasRemaining = gasAvailable } } - return nil + return tipReceipient, nil } func (p *TxProcessor) NonrefundableGas() uint64 { @@ -431,7 +430,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { underlyingTx := p.msg.UnderlyingTransaction() networkFeeAccount, _ := p.state.NetworkFeeAccount() - gasPrice := p.evm.Context.BaseFee + basefee := p.evm.Context.BaseFee scenario := util.TracingAfterEVM if gasLeft > p.msg.Gas() { @@ -443,7 +442,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { inner, _ := underlyingTx.GetInner().(*types.ArbitrumRetryTx) // undo Geth's refund to the From address - gasRefund := arbmath.BigMulByUint(gasPrice, gasLeft) + gasRefund := arbmath.BigMulByUint(basefee, gasLeft) err := util.BurnBalance(&inner.From, gasRefund, p.evm, scenario, "undoRefund") if err != nil { log.Error("Uh oh, Geth didn't refund the user", inner.From, gasRefund) @@ -478,7 +477,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { takeFunds(maxRefund, inner.SubmissionFeeRefund) } // Conceptually, the gas charge is taken from the L1 deposit pool if possible. - takeFunds(maxRefund, arbmath.BigMulByUint(gasPrice, gasUsed)) + takeFunds(maxRefund, arbmath.BigMulByUint(basefee, gasUsed)) // Refund any unused gas, without overdrafting the L1 deposit. refundNetworkFee(gasRefund) @@ -502,13 +501,13 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { return } - totalCost := arbmath.BigMul(gasPrice, arbmath.UintToBig(gasUsed)) // total cost = price of gas * gas burnt - computeCost := arbmath.BigSub(totalCost, p.PosterFee) // total cost = network's compute + poster's L1 costs + totalCost := arbmath.BigMul(basefee, arbmath.UintToBig(gasUsed)) // total cost = price of gas * gas burnt + computeCost := arbmath.BigSub(totalCost, p.PosterFee) // total cost = network's compute + poster's L1 costs if computeCost.Sign() < 0 { // Uh oh, there's a bug in our charging code. // Give all funds to the network account and continue. - log.Error("total cost < poster cost", "gasUsed", gasUsed, "gasPrice", gasPrice, "posterFee", p.PosterFee) + log.Error("total cost < poster cost", "gasUsed", gasUsed, "basefee", basefee, "posterFee", p.PosterFee) p.PosterFee = big.NewInt(0) computeCost = totalCost } @@ -520,8 +519,8 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { if infraFeeAccount != (common.Address{}) { infraFee, err := p.state.L2PricingState().MinBaseFeeWei() p.state.Restrict(err) - if arbmath.BigLessThan(gasPrice, infraFee) { - infraFee = gasPrice + if arbmath.BigLessThan(basefee, infraFee) { + infraFee = basefee } computeGas := arbmath.SaturatingUSub(gasUsed, p.posterGas) infraComputeCost := arbmath.BigMulByUint(infraFee, computeGas) @@ -532,9 +531,9 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { if arbmath.BigGreaterThan(computeCost, common.Big0) { util.MintBalance(&networkFeeAccount, computeCost, p.evm, scenario, purpose) } - posterFeeDestination := p.evm.Context.Coinbase - if p.state.ArbOSVersion() >= 2 { - posterFeeDestination = l1pricing.L1PricerFundsPoolAddress + posterFeeDestination := l1pricing.L1PricerFundsPoolAddress + if p.state.ArbOSVersion() < 2 { + posterFeeDestination = p.evm.Context.Coinbase } util.MintBalance(&posterFeeDestination, p.PosterFee, p.evm, scenario, purpose) @@ -628,10 +627,17 @@ func (p *TxProcessor) L1BlockHash(blockCtx vm.BlockContext, l1BlockNumber uint64 return hash, nil } +func (p *TxProcessor) DropTip() bool { + return p.state.ArbOSVersion() < 9 || p.delayedInbox +} + func (p *TxProcessor) GetPaidGasPrice() *big.Int { - gasPrice := p.evm.Context.BaseFee - if p.msg.RunMode() != types.MessageCommitMode && p.msg.GasFeeCap().Sign() == 0 { - gasPrice.SetInt64(0) // gasprice zero behavior + gasPrice := p.evm.GasPrice + if p.state.ArbOSVersion() < 9 { + gasPrice = p.evm.Context.BaseFee + if p.msg.RunMode() != types.MessageCommitMode && p.msg.GasFeeCap().Sign() == 0 { + gasPrice.SetInt64(0) // gasprice zero behavior + } } return gasPrice } diff --git a/go-ethereum b/go-ethereum index 5fac5a2b0d..b11dda356f 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 5fac5a2b0db765f962acdfce432b3953fdd3779a +Subproject commit b11dda356f6d8373bd87a3a92a51bc5cc9f2f4f1 diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index 088698217b..f53a259dfc 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -41,36 +41,68 @@ func TestSequencerFeePaid(t *testing.T) { // get the network fee account arbOwnerPublic, err := precompilesgen.NewArbOwnerPublic(common.HexToAddress("0x6b"), l2client) - Require(t, err, "could not deploy ArbOwner contract") + Require(t, err, "failed to deploy contract") arbGasInfo, err := precompilesgen.NewArbGasInfo(common.HexToAddress("0x6c"), l2client) - Require(t, err, "could not deploy ArbOwner contract") + Require(t, err, "failed to deploy contract") + arbDebug, err := precompilesgen.NewArbDebug(common.HexToAddress("0xff"), l2client) + Require(t, err, "failed to deploy contract") networkFeeAccount, err := arbOwnerPublic.GetNetworkFeeAccount(callOpts) Require(t, err, "could not get the network fee account") l1Estimate, err := arbGasInfo.GetL1BaseFeeEstimate(callOpts) Require(t, err) - networkBefore := GetBalance(t, ctx, l2client, networkFeeAccount) - l2info.GasPrice = GetBaseFee(t, l2client, ctx) - tx, receipt := TransferBalance(t, "Faucet", "Faucet", big.NewInt(0), l2info, l2client, ctx) - txSize := compressedTxSize(t, tx) + baseFee := GetBaseFee(t, l2client, ctx) + l2info.GasPrice = baseFee - networkAfter := GetBalance(t, ctx, l2client, networkFeeAccount) - l1Charge := arbmath.BigMulByUint(l2info.GasPrice, receipt.GasUsedForL1) + testFees := func(tip uint64) (*big.Int, *big.Int) { + tipCap := arbmath.BigMulByUint(baseFee, tip) + txOpts := l2info.GetDefaultTransactOpts("Faucet", ctx) + txOpts.GasTipCap = tipCap + gasPrice := arbmath.BigAdd(baseFee, tipCap) - networkRevenue := arbmath.BigSub(networkAfter, networkBefore) - gasUsedForL2 := receipt.GasUsed - receipt.GasUsedForL1 - if !arbmath.BigEquals(networkRevenue, arbmath.BigMulByUint(tx.GasPrice(), gasUsedForL2)) { - Fail(t, "network didn't receive expected payment") - } + networkBefore := GetBalance(t, ctx, l2client, networkFeeAccount) + + tx, err := arbDebug.Events(&txOpts, true, [32]byte{}) + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, l2client, tx) + Require(t, err) + + networkAfter := GetBalance(t, ctx, l2client, networkFeeAccount) + l1Charge := arbmath.BigMulByUint(l2info.GasPrice, receipt.GasUsedForL1) + + // the network should receive + // 1. compute costs + // 2. tip on the compute costs + // 3. tip on the data costs + networkRevenue := arbmath.BigSub(networkAfter, networkBefore) + gasUsedForL2 := receipt.GasUsed - receipt.GasUsedForL1 + feePaidForL2 := arbmath.BigMulByUint(gasPrice, gasUsedForL2) + tipPaidToNet := arbmath.BigMulByUint(tipCap, receipt.GasUsedForL1) + if !arbmath.BigEquals(networkRevenue, arbmath.BigAdd(feePaidForL2, tipPaidToNet)) { + Fail(t, "network didn't receive expected payment", networkRevenue, feePaidForL2, tipPaidToNet) + } - l1GasBought := arbmath.BigDiv(l1Charge, l1Estimate).Uint64() - l1GasActual := txSize * params.TxDataNonZeroGasEIP2028 + txSize := compressedTxSize(t, tx) + l1GasBought := arbmath.BigDiv(l1Charge, l1Estimate).Uint64() + l1GasActual := txSize * params.TxDataNonZeroGasEIP2028 - colors.PrintBlue("bytes ", l1GasBought/params.TxDataNonZeroGasEIP2028, txSize) + colors.PrintBlue("bytes ", l1GasBought/params.TxDataNonZeroGasEIP2028, txSize) - if l1GasBought != l1GasActual { - Fail(t, "the sequencer's future revenue does not match its costs", l1GasBought, l1GasActual) + if l1GasBought != l1GasActual { + Fail(t, "the sequencer's future revenue does not match its costs", l1GasBought, l1GasActual) + } + return networkRevenue, tipPaidToNet + } + + net0, tip0 := testFees(0) + net2, tip2 := testFees(2) + + if tip0.Sign() != 0 { + Fail(t, "nonzero tip") + } + if arbmath.BigEquals(arbmath.BigSub(net2, tip2), net0) { + Fail(t, "a tip of 2 should yield a total of 3") } }