diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f4154d082..0773ea8ce7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,7 +136,7 @@ jobs: skip-pkg-cache: true - name: run tests with race detection - run: gotestsum --format short-verbose --jsonfile test-output-withrace.json -- -race ./... + run: gotestsum --format short-verbose --jsonfile test-output-withrace.json -- ./... -race -parallel=1 - name: Annotate tests with race detection if: always() @@ -146,7 +146,7 @@ jobs: - name: run tests without race detection if: always() - run: gotestsum --format short-verbose --jsonfile test-output.json -- ./... -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... + run: gotestsum --format short-verbose --jsonfile test-output.json -- ./... -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -parallel=1 - name: Annotate tests without race detection if: always() diff --git a/arbos/block_processor.go b/arbos/block_processor.go index e42d106713..1c591bd9ba 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -34,7 +34,7 @@ var InternalTxBatchPostingReportMethodID [4]byte var RedeemScheduledEventID common.Hash var L2ToL1TransactionEventID common.Hash var L2ToL1TxEventID common.Hash -var EmitReedeemScheduledEvent func(*vm.EVM, uint64, uint64, [32]byte, [32]byte, common.Address) error +var EmitReedeemScheduledEvent func(*vm.EVM, uint64, uint64, [32]byte, [32]byte, common.Address, *big.Int, *big.Int) error var EmitTicketCreatedEvent func(*vm.EVM, [32]byte) error func createNewHeader(prevHeader *types.Header, l1info *L1Info, state *arbosState.ArbosState, chainConfig *params.ChainConfig) *types.Header { diff --git a/arbos/retryables/retryable.go b/arbos/retryables/retryable.go index d732ee342b..c4352997b0 100644 --- a/arbos/retryables/retryable.go +++ b/arbos/retryables/retryable.go @@ -328,7 +328,7 @@ func (rs *RetryableState) TryToReapOneRetryable(currentTimestamp uint64, evm *vm return windowsLeftStorage.Set(windowsLeft - 1) } -func (retryable *Retryable) MakeTx(chainId *big.Int, nonce uint64, gasFeeCap *big.Int, gas uint64, ticketId common.Hash, refundTo common.Address) (*types.ArbitrumRetryTx, error) { +func (retryable *Retryable) MakeTx(chainId *big.Int, nonce uint64, gasFeeCap *big.Int, gas uint64, ticketId common.Hash, refundTo common.Address, maxRefund *big.Int, submissionFeeRefund *big.Int) (*types.ArbitrumRetryTx, error) { from, err := retryable.From() if err != nil { return nil, err @@ -346,16 +346,18 @@ func (retryable *Retryable) MakeTx(chainId *big.Int, nonce uint64, gasFeeCap *bi return nil, err } return &types.ArbitrumRetryTx{ - ChainId: chainId, - Nonce: nonce, - From: from, - GasFeeCap: gasFeeCap, - Gas: gas, - To: to, - Value: callvalue, - Data: calldata, - TicketId: ticketId, - RefundTo: refundTo, + ChainId: chainId, + Nonce: nonce, + From: from, + GasFeeCap: gasFeeCap, + Gas: gas, + To: to, + Value: callvalue, + Data: calldata, + TicketId: ticketId, + RefundTo: refundTo, + MaxRefund: maxRefund, + SubmissionFeeRefund: submissionFeeRefund, }, nil } diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 7a531d9502..e14c9fe5a9 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -45,6 +45,7 @@ type TxProcessor struct { TopTxType *byte // set once in StartTxHook evm *vm.EVM CurrentRetryable *common.Hash + CurrentRefundTo *common.Address } func NewTxProcessor(evm *vm.EVM, msg core.Message) *TxProcessor { @@ -59,6 +60,7 @@ func NewTxProcessor(evm *vm.EVM, msg core.Message) *TxProcessor { TopTxType: nil, evm: evm, CurrentRetryable: nil, + CurrentRefundTo: nil, } } @@ -70,6 +72,19 @@ func (p *TxProcessor) PopCaller() { p.Callers = p.Callers[:len(p.Callers)-1] } +// Attempts to subtract up to `take` from `pool` without going negative. +// Returns the amount subtracted from `pool`. +func takeFunds(pool *big.Int, take *big.Int) *big.Int { + if arbmath.BigLessThan(pool, take) { + oldPool := new(big.Int).Set(pool) + pool.Set(common.Big0) + return oldPool + } else { + pool.Sub(pool, take) + return new(big.Int).Set(take) + } +} + func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, returnData []byte) { // This hook is called before gas charging and will end the state transition if endTxNow is set to true // Hence, we must charge for any l2 resources if endTxNow is returned true @@ -80,7 +95,6 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r } var tracingInfo *util.TracingInfo - from := p.msg.From() tipe := underlyingTx.Type() p.TopTxType = &tipe evm := p.evm @@ -91,6 +105,7 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r } evm.IncrementDepth() // fake a call tracer := evm.Config.Tracer + from := p.msg.From() start := time.Now() tracer.CaptureStart(evm, from, *p.msg.To(), false, p.msg.Data(), p.msg.Gas(), p.msg.Value()) @@ -128,25 +143,33 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r scenario := util.TracingDuringEVM // mint funds with the deposit, then charge fees later - util.MintBalance(&from, tx.DepositValue, evm, scenario, "deposit") - - submissionFee := retryables.RetryableSubmissionFee(len(tx.RetryData), tx.L1BaseFee) - excessDeposit := arbmath.BigSub(tx.MaxSubmissionFee, submissionFee) - if excessDeposit.Sign() < 0 { - return true, 0, errors.New("max submission fee is less than the actual submission fee"), nil - } + availableRefund := new(big.Int).Set(tx.DepositValue) + takeFunds(availableRefund, tx.Value) + util.MintBalance(&tx.From, tx.DepositValue, evm, scenario, "deposit") transfer := func(from, to *common.Address, amount *big.Int) error { return util.TransferBalance(from, to, amount, evm, scenario, "during evm execution") } - // move balance to the relevant parties - if err := transfer(&from, &networkFeeAccount, submissionFee); err != nil { + // collect the submission fee + submissionFee := retryables.RetryableSubmissionFee(len(tx.RetryData), tx.L1BaseFee) + if err := transfer(&tx.From, &networkFeeAccount, submissionFee); err != nil { return true, 0, err, nil } - if err := transfer(&from, &tx.FeeRefundAddr, excessDeposit); err != nil { - return true, 0, err, nil + withheldSubmissionFee := takeFunds(availableRefund, submissionFee) + + // refund excess submission fee + submissionFeeRefund := arbmath.BigSub(tx.MaxSubmissionFee, submissionFee) + if submissionFeeRefund.Sign() < 0 { + return true, 0, errors.New("max submission fee is less than the actual submission fee"), nil + } + submissionFeeRefund = takeFunds(availableRefund, submissionFeeRefund) + if err := transfer(&tx.From, &tx.FeeRefundAddr, submissionFeeRefund); err != nil { + // should never happen as from's balance should be at least availableRefund at this point + glog.Error("failed to transfer submissionFeeRefund", "err", err) } + + // move the callvalue into escrow if err := transfer(&tx.From, &escrow, tx.Value); err != nil { return true, 0, err, nil } @@ -192,6 +215,15 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r panic(err) } + withheldGasFunds := takeFunds(availableRefund, gascost) // gascost is conceptually charged before the gas price refund + gasPriceRefund := arbmath.BigMulByUint(arbmath.BigSub(tx.GasFeeCap, basefee), tx.Gas) + gasPriceRefund = takeFunds(availableRefund, gasPriceRefund) + if err := transfer(&tx.From, &tx.FeeRefundAddr, gasPriceRefund); err != nil { + glog.Error("failed to transfer gasPriceRefund", "err", err) + } + availableRefund.Add(availableRefund, withheldGasFunds) + availableRefund.Add(availableRefund, withheldSubmissionFee) + // emit RedeemScheduled event retryTxInner, err := retryable.MakeTx( underlyingTx.ChainId(), @@ -200,6 +232,8 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r usergas, ticketId, tx.FeeRefundAddr, + availableRefund, + submissionFee, ) p.state.Restrict(err) @@ -213,6 +247,8 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r ticketId, types.NewTx(retryTxInner).Hash(), tx.FeeRefundAddr, + availableRefund, + submissionFee, ) if err != nil { glog.Error("failed to emit RedeemScheduled event", "err", err) @@ -241,7 +277,9 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r prepaid := arbmath.BigMulByUint(evm.Context.BaseFee, tx.Gas) util.MintBalance(&tx.From, prepaid, evm, scenario, "prepaid") ticketId := tx.TicketId + refundTo := tx.RefundTo p.CurrentRetryable = &ticketId + p.CurrentRefundTo = &refundTo } return false, 0, nil, nil } @@ -337,22 +375,47 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { if underlyingTx != nil && underlyingTx.Type() == types.ArbitrumRetryTxType { inner, _ := underlyingTx.GetInner().(*types.ArbitrumRetryTx) - refund := arbmath.BigMulByUint(gasPrice, gasLeft) // undo Geth's refund to the From address - err := util.TransferBalance(&inner.From, nil, refund, p.evm, scenario, "undoRefund") + gasRefund := arbmath.BigMulByUint(gasPrice, 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, refund) + log.Error("Uh oh, Geth didn't refund the user", inner.From, gasRefund) } - // refund the RefundTo by taking fees back from the network address - err = util.TransferBalance(&networkFeeAccount, &inner.RefundTo, refund, p.evm, scenario, "refund") - if err != nil { - // Normally the network fee address should be holding the gas funds. - // However, in theory, they could've been transfered out during the redeem attempt. - // If the network fee address doesn't have the necessary balance, log an error and don't give a refund. - log.Error("network fee address doesn't have enough funds to give user refund", "err", err) + maxRefund := new(big.Int).Set(inner.MaxRefund) + refundNetworkFee := func(amount *big.Int) { + const errLog = "network fee address doesn't have enough funds to give user refund" + + // Refund funds to the fee refund address without overdrafting the L1 deposit. + toRefundAddr := takeFunds(maxRefund, amount) + err = util.TransferBalance(&networkFeeAccount, &inner.RefundTo, toRefundAddr, p.evm, scenario, "refund") + if err != nil { + // Normally the network fee address should be holding any collected fees. + // However, in theory, they could've been transfered out during the redeem attempt. + // If the network fee address doesn't have the necessary balance, log an error and don't give a refund. + log.Error(errLog, "err", err) + } + // Any extra refund can't be given to the fee refund address if it didn't come from the L1 deposit. + // Instead, give the refund to the retryable from address. + err = util.TransferBalance(&networkFeeAccount, &inner.From, arbmath.BigSub(amount, toRefundAddr), p.evm, scenario, "refund") + if err != nil { + log.Error(errLog, "err", err) + } + } + + if success { + // If successful, refund the submission fee. + refundNetworkFee(inner.SubmissionFeeRefund) + } else { + // The submission fee is still taken from the L1 deposit earlier, even if it's not refunded. + takeFunds(maxRefund, inner.SubmissionFeeRefund) } + // Conceptually, the gas charge is taken from the L1 deposit pool if possible. + takeFunds(maxRefund, arbmath.BigMulByUint(gasPrice, gasUsed)) + // Refund any unused gas, without overdrafting the L1 deposit. + refundNetworkFee(gasRefund) + if success { // we don't want to charge for this tracingInfo := util.NewTracingInfo(p.evm, arbosAddress, p.msg.From(), scenario) @@ -435,6 +498,8 @@ func (p *TxProcessor) ScheduledTxes() types.Transactions { event.DonatedGas, event.TicketId, event.GasDonor, + event.MaxRefund, + event.SubmissionFeeRefund, ) scheduled = append(scheduled, types.NewTx(redeem)) } diff --git a/blockscout b/blockscout index 8e977c578a..310d0283a3 160000 --- a/blockscout +++ b/blockscout @@ -1 +1 @@ -Subproject commit 8e977c578ac47299b24bfb30e399e239469dad0f +Subproject commit 310d0283a308e12bfd5a24a07863cc4100cb58a6 diff --git a/contracts/src/bridge/Inbox.sol b/contracts/src/bridge/Inbox.sol index 0f624a9558..632d47dc60 100644 --- a/contracts/src/bridge/Inbox.sol +++ b/contracts/src/bridge/Inbox.sol @@ -350,8 +350,12 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { bytes calldata data ) external payable virtual override whenNotPaused onlyAllowed returns (uint256) { // ensure the user's deposit alone will make submission succeed - if (msg.value < maxSubmissionCost + l2CallValue) - revert InsufficientValue(maxSubmissionCost + l2CallValue, msg.value); + if (msg.value < (maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas)) { + revert InsufficientValue( + maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas, + msg.value + ); + } // if a refund address is a contract, we apply the alias to it // so that it can access its funds on the L2 diff --git a/contracts/src/mocks/Simple.sol b/contracts/src/mocks/Simple.sol index 28c2905906..ba75f814bb 100644 --- a/contracts/src/mocks/Simple.sol +++ b/contracts/src/mocks/Simple.sol @@ -4,10 +4,13 @@ pragma solidity ^0.8.0; +import "../precompiles/ArbRetryableTx.sol"; + contract Simple { uint64 public counter; event CounterEvent(uint64 count); + event RedeemedEvent(address caller, address redeemer); event NullEvent(); function increment() external { @@ -19,6 +22,11 @@ contract Simple { emit CounterEvent(counter); } + function incrementRedeem() external { + counter++; + emit RedeemedEvent(msg.sender, ArbRetryableTx(address(110)).getCurrentRedeemer()); + } + function emitNullEvent() external { emit NullEvent(); } diff --git a/contracts/src/precompiles/ArbRetryableTx.sol b/contracts/src/precompiles/ArbRetryableTx.sol index fc7a6199ee..3de17ea12c 100644 --- a/contracts/src/precompiles/ArbRetryableTx.sol +++ b/contracts/src/precompiles/ArbRetryableTx.sol @@ -55,6 +55,13 @@ interface ArbRetryableTx { */ function cancel(bytes32 ticketId) external; + /** + * @notice Gets the redeemer of the current retryable redeem attempt. + * Returns the zero address if the current transaction is not a retryable redeem attempt. + * If this is an auto-redeem, returns the fee refund address of the retryable. + */ + function getCurrentRedeemer() external view returns (address); + /** * @notice Do not call. This method represents a retryable submission to aid explorers. * Calling it will always revert. @@ -80,7 +87,9 @@ interface ArbRetryableTx { bytes32 indexed retryTxHash, uint64 indexed sequenceNum, uint64 donatedGas, - address gasDonor + address gasDonor, + uint256 maxRefund, + uint256 submissionFeeRefund ); event Canceled(bytes32 indexed ticketId); diff --git a/go-ethereum b/go-ethereum index ec724ba757..dc23b6a343 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit ec724ba75752f9a7a3c4d27a791e5aaf43fe6f5f +Subproject commit dc23b6a343f7e999fcd19944a42e6b3126b3e56b diff --git a/precompiles/ArbRetryableTx.go b/precompiles/ArbRetryableTx.go index f180082d98..ab6b8960b3 100644 --- a/precompiles/ArbRetryableTx.go +++ b/precompiles/ArbRetryableTx.go @@ -7,6 +7,7 @@ import ( "errors" "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -20,11 +21,11 @@ type ArbRetryableTx struct { Address addr TicketCreated func(ctx, mech, bytes32) error LifetimeExtended func(ctx, mech, bytes32, huge) error - RedeemScheduled func(ctx, mech, bytes32, bytes32, uint64, uint64, addr) error + RedeemScheduled func(ctx, mech, bytes32, bytes32, uint64, uint64, addr, huge, huge) error Canceled func(ctx, mech, bytes32) error TicketCreatedGasCost func(bytes32) (uint64, error) LifetimeExtendedGasCost func(bytes32, huge) (uint64, error) - RedeemScheduledGasCost func(bytes32, bytes32, uint64, uint64, addr) (uint64, error) + RedeemScheduledGasCost func(bytes32, bytes32, uint64, uint64, addr, huge, huge) (uint64, error) CanceledGasCost func(bytes32) (uint64, error) // deprecated event @@ -68,6 +69,8 @@ func (con ArbRetryableTx) Redeem(c ctx, evm mech, ticketId bytes32) (bytes32, er } nonce := nextNonce - 1 + maxRefund := new(big.Int).Exp(common.Big2, common.Big256, nil) + maxRefund.Sub(maxRefund, common.Big1) retryTxInner, err := retryable.MakeTx( evm.ChainConfig().ChainID, nonce, @@ -75,6 +78,8 @@ func (con ArbRetryableTx) Redeem(c ctx, evm mech, ticketId bytes32) (bytes32, er 0, // will fill this in below ticketId, c.caller, + maxRefund, + common.Big0, ) if err != nil { return hash{}, err @@ -82,7 +87,7 @@ func (con ArbRetryableTx) Redeem(c ctx, evm mech, ticketId bytes32) (bytes32, er // figure out how much gas the event issuance will cost, and reduce the donated gas amount in the event // by that much, so that we'll donate the correct amount of gas - eventCost, err := con.RedeemScheduledGasCost(hash{}, hash{}, 0, 0, addr{}) + eventCost, err := con.RedeemScheduledGasCost(hash{}, hash{}, 0, 0, addr{}, common.Big0, common.Big0) if err != nil { return hash{}, err } @@ -104,7 +109,7 @@ func (con ArbRetryableTx) Redeem(c ctx, evm mech, ticketId bytes32) (bytes32, er retryTx := types.NewTx(retryTxInner) retryTxHash := retryTx.Hash() - err = con.RedeemScheduled(c, evm, ticketId, retryTxHash, nonce, gasToDonate, c.caller) + err = con.RedeemScheduled(c, evm, ticketId, retryTxHash, nonce, gasToDonate, c.caller, maxRefund, common.Big0) if err != nil { return hash{}, err } @@ -213,6 +218,14 @@ func (con ArbRetryableTx) Cancel(c ctx, evm mech, ticketId bytes32) error { return con.Canceled(c, evm, ticketId) } +func (con ArbRetryableTx) GetCurrentRedeemer(c ctx, evm mech) (common.Address, error) { + if c.txProcessor.CurrentRefundTo != nil { + return *c.txProcessor.CurrentRefundTo, nil + } else { + return common.Address{}, nil + } +} + func (con ArbRetryableTx) SubmitRetryable( c ctx, evm mech, requestId bytes32, l1BaseFee, deposit, callvalue, gasFeeCap huge, gasLimit uint64, maxSubmissionFee huge, diff --git a/precompiles/precompile.go b/precompiles/precompile.go index c13a3fc821..9a53c9e7ac 100644 --- a/precompiles/precompile.go +++ b/precompiles/precompile.go @@ -540,9 +540,9 @@ func Precompiles() map[addr]ArbosPrecompile { ArbRetryable := insert(MakePrecompile(templates.ArbRetryableTxMetaData, ArbRetryableImpl)) arbos.ArbRetryableTxAddress = ArbRetryable.address arbos.RedeemScheduledEventID = ArbRetryable.events["RedeemScheduled"].template.ID - emitReedeemScheduled := func(evm mech, gas, nonce uint64, ticketId, retryTxHash bytes32, donor addr) error { - context := eventCtx(ArbRetryableImpl.RedeemScheduledGasCost(hash{}, hash{}, 0, 0, addr{})) - return ArbRetryableImpl.RedeemScheduled(context, evm, ticketId, retryTxHash, nonce, gas, donor) + emitReedeemScheduled := func(evm mech, gas, nonce uint64, ticketId, retryTxHash bytes32, donor addr, maxRefund *big.Int, submissionFeeRefund *big.Int) error { + context := eventCtx(ArbRetryableImpl.RedeemScheduledGasCost(hash{}, hash{}, 0, 0, addr{}, common.Big0, common.Big0)) + return ArbRetryableImpl.RedeemScheduled(context, evm, ticketId, retryTxHash, nonce, gas, donor, maxRefund, submissionFeeRefund) } arbos.EmitReedeemScheduledEvent = emitReedeemScheduled arbos.EmitTicketCreatedEvent = func(evm mech, ticketId bytes32) error { diff --git a/system_tests/retryable_test.go b/system_tests/retryable_test.go index 062ac2bdc2..6cdd117114 100644 --- a/system_tests/retryable_test.go +++ b/system_tests/retryable_test.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbos/l2pricing" @@ -50,16 +51,27 @@ func retryableSetup(t *testing.T) ( lookupSubmitRetryableL2TxHash := func(l1Receipt *types.Receipt) common.Hash { messages, err := delayedBridge.LookupMessagesInRange(ctx, l1Receipt.BlockNumber, l1Receipt.BlockNumber) Require(t, err) - if len(messages) != 1 { - Fail(t, "expected 1 message from retryable submission, found", len(messages)) + if len(messages) == 0 { + Fail(t, "didn't find message for retryable submission") } - txs, err := messages[0].Message.ParseL2Transactions(params.ArbitrumDevTestChainConfig().ChainID, nil) - Require(t, err) - if len(txs) != 1 { - Fail(t, "expected 1 tx from retryable submission, found", len(txs)) + var submissionTxs []*types.Transaction + for _, message := range messages { + if message.Message.Header.Kind != arbos.L1MessageType_SubmitRetryable { + continue + } + txs, err := message.Message.ParseL2Transactions(params.ArbitrumDevTestChainConfig().ChainID, nil) + Require(t, err) + for _, tx := range txs { + if tx.Type() == types.ArbitrumSubmitRetryableTxType { + submissionTxs = append(submissionTxs, tx) + } + } + } + if len(submissionTxs) != 1 { + Fail(t, "expected 1 tx from retryable submission, found", len(submissionTxs)) } - return txs[0].Hash() + return submissionTxs[0].Hash() } // burn some gas so that the faucet's Callvalue + Balance never exceeds a uint256 @@ -193,7 +205,7 @@ func TestSubmitRetryableFailThenRetry(t *testing.T) { // send enough L2 gas for intrinsic but not compute big.NewInt(int64(params.TxGas+params.TxDataNonZeroGasEIP2028*4)), big.NewInt(l2pricing.InitialBaseFeeWei*2), - simpleABI.Methods["increment"].ID, + simpleABI.Methods["incrementRedeem"].ID, ) Require(t, err) @@ -246,6 +258,19 @@ func TestSubmitRetryableFailThenRetry(t *testing.T) { if counter != 1 { Fail(t, "Unexpected counter:", counter) } + + if len(receipt.Logs) != 1 { + Fail(t, "Unexpected log count:", len(receipt.Logs)) + } + parsed, err := simple.ParseRedeemedEvent(*receipt.Logs[0]) + Require(t, err) + aliasedSender := util.RemapL1Address(usertxopts.From) + if parsed.Caller != aliasedSender { + Fail(t, "Unexpected caller", parsed.Caller, "expected", aliasedSender) + } + if parsed.Redeemer != ownerTxOpts.From { + Fail(t, "Unexpected redeemer", parsed.Redeemer, "expected", ownerTxOpts.From) + } } func TestSubmissionGasCosts(t *testing.T) { @@ -272,12 +297,13 @@ func TestSubmissionGasCosts(t *testing.T) { Require(t, err) usefulGas := params.TxGas - excessGas := uint64(808) + excessGasLimit := uint64(808) maxSubmissionFee := big.NewInt(1e13) - retryableGas := arbmath.UintToBig(usefulGas + excessGas) // will only burn the intrinsic cost + retryableGas := arbmath.UintToBig(usefulGas + excessGasLimit) // will only burn the intrinsic cost retryableL2CallValue := big.NewInt(1e4) retryableCallData := []byte{} + gasFeeCap := big.NewInt(l2pricing.InitialBaseFeeWei * 2) l1tx, err := delayedInbox.CreateRetryableTicket( &usertxopts, receiveAddress, @@ -286,7 +312,7 @@ func TestSubmissionGasCosts(t *testing.T) { feeRefundAddress, beneficiaryAddress, retryableGas, - big.NewInt(l2pricing.InitialBaseFeeWei*2), + gasFeeCap, retryableCallData, ) Require(t, err) @@ -299,13 +325,9 @@ func TestSubmissionGasCosts(t *testing.T) { waitForL1DelayBlocks(t, ctx, l1client, l1info) l2BaseFee := GetBaseFee(t, l2client, ctx) - excessWei := arbmath.BigMulByUint(l2BaseFee, excessGas) - - l1HeaderAfterSubmit, err := l1client.HeaderByHash(ctx, l1receipt.BlockHash) - Require(t, err) - l1BaseFee := l1HeaderAfterSubmit.BaseFee - submitFee := arbmath.BigMulByUint(l1BaseFee, uint64(1400+6*len(retryableCallData))) - submissionFeeRefund := arbmath.BigSub(maxSubmissionFee, submitFee) + excessGasPrice := arbmath.BigSub(gasFeeCap, l2BaseFee) + excessWei := arbmath.BigMulByUint(l2BaseFee, excessGasLimit) + excessWei.Add(excessWei, arbmath.BigMul(excessGasPrice, retryableGas)) fundsAfterSubmit, err := l2client.BalanceAt(ctx, faucetAddress, nil) Require(t, err) @@ -337,16 +359,17 @@ func TestSubmissionGasCosts(t *testing.T) { } // the fee refund address should recieve the excess gas - colors.PrintBlue("Base Fee ", l2BaseFee) - colors.PrintBlue("Excess Gas ", excessGas) - colors.PrintBlue("Excess Wei ", excessWei) - colors.PrintMint("Fee Refund ", refundFunds) - if !arbmath.BigEquals(refundFunds, arbmath.BigAdd(excessWei, submissionFeeRefund)) { + colors.PrintBlue("Base Fee ", l2BaseFee) + colors.PrintBlue("Excess Gas Price ", excessGasPrice) + colors.PrintBlue("Excess Gas ", excessGasLimit) + colors.PrintBlue("Excess Wei ", excessWei) + colors.PrintMint("Fee Refund ", refundFunds) + if !arbmath.BigEquals(refundFunds, arbmath.BigAdd(excessWei, maxSubmissionFee)) { Fail(t, "The Fee Refund Address didn't receive the right funds") } // the faucet must pay for both the gas used and the call value supplied - expectedGasChange := arbmath.BigMul(l2BaseFee, retryableGas) + expectedGasChange := arbmath.BigMul(gasFeeCap, retryableGas) expectedGasChange = arbmath.BigSub(expectedGasChange, usertxopts.Value) // the user is credited this expectedGasChange = arbmath.BigAdd(expectedGasChange, maxSubmissionFee) expectedGasChange = arbmath.BigAdd(expectedGasChange, retryableL2CallValue) diff --git a/system_tests/seqcompensation_test.go b/system_tests/seqcompensation_test.go index 51833c1ef7..459af8474a 100644 --- a/system_tests/seqcompensation_test.go +++ b/system_tests/seqcompensation_test.go @@ -15,6 +15,7 @@ import ( // L1 Pricer pool address gets something when the sequencer posts batches func TestSequencerCompensation(t *testing.T) { + t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() l2info, node1, l2clientA, l1info, _, l1client, l1stack := CreateTestNodeOnL1(t, ctx, true) diff --git a/system_tests/wrap_transaction_test.go b/system_tests/wrap_transaction_test.go index 96946f4d69..508f363bac 100644 --- a/system_tests/wrap_transaction_test.go +++ b/system_tests/wrap_transaction_test.go @@ -73,7 +73,7 @@ func WaitForTx(ctxinput context.Context, client arbutil.L1Interface, txhash comm } func EnsureTxSucceeded(ctx context.Context, client arbutil.L1Interface, tx *types.Transaction) (*types.Receipt, error) { - return EnsureTxSucceededWithTimeout(ctx, client, tx, time.Second*2) + return EnsureTxSucceededWithTimeout(ctx, client, tx, time.Second*5) } func EnsureTxSucceededWithTimeout(ctx context.Context, client arbutil.L1Interface, tx *types.Transaction, timeout time.Duration) (*types.Receipt, error) { diff --git a/util/arbmath/math.go b/util/arbmath/math.go index e54c3aa64f..ec0f00f6d5 100644 --- a/util/arbmath/math.go +++ b/util/arbmath/math.go @@ -98,6 +98,24 @@ func BigGreaterThan(first, second *big.Int) bool { return first.Cmp(second) > 0 } +// returns a clone of the minimum of two big integers +func BigMin(first, second *big.Int) *big.Int { + if BigLessThan(first, second) { + return new(big.Int).Set(first) + } else { + return new(big.Int).Set(second) + } +} + +// returns a clone of the maximum of two big integers +func BigMax(first, second *big.Int) *big.Int { + if BigGreaterThan(first, second) { + return new(big.Int).Set(first) + } else { + return new(big.Int).Set(second) + } +} + // add a huge to another func BigAdd(augend *big.Int, addend *big.Int) *big.Int { return new(big.Int).Add(augend, addend)