Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AA-408 deduct gas for aatx #28

Merged
merged 10 commits into from
Sep 16, 2024
2 changes: 1 addition & 1 deletion core/rip7560_abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"strings"
)

var Rip7560Abi, err = abi.JSON(strings.NewReader(Rip7560AbiJson))
var Rip7560Abi, _ = abi.JSON(strings.NewReader(Rip7560AbiJson))

type AcceptAccountData struct {
ValidAfter *big.Int
Expand Down
140 changes: 93 additions & 47 deletions core/state_processor_rip7560.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
cmath "github.com/ethereum/go-ethereum/common/math"
forshtat marked this conversation as resolved.
Show resolved Hide resolved
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -25,21 +26,23 @@ type EntryPointCall struct {
}

type ValidationPhaseResult struct {
TxIndex int
Tx *types.Transaction
TxHash common.Hash
PaymasterContext []byte
PreCharge *uint256.Int
EffectiveGasPrice *uint256.Int
CallDataUsedGas uint64
NonceManagerUsedGas uint64
DeploymentUsedGas uint64
ValidationUsedGas uint64
PmValidationUsedGas uint64
SenderValidAfter uint64
SenderValidUntil uint64
PmValidAfter uint64
PmValidUntil uint64
TxIndex int
Tx *types.Transaction
TxHash common.Hash
PaymasterContext []byte
PreCharge *uint256.Int
EffectiveGasPrice *uint256.Int
TotalValidationGasUsed uint64

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having both ValidationUsedGas and TotalValidationGasUsed in the same object is extremely confusing. Let's make the names AccountValidationUsedGas and ValidationPhaseUsedGas?

ValidationRefund uint64
CallDataUsedGas uint64
NonceManagerUsedGas uint64
DeploymentUsedGas uint64
ValidationUsedGas uint64
PmValidationUsedGas uint64
SenderValidAfter uint64
SenderValidUntil uint64
PmValidAfter uint64
PmValidUntil uint64
}

const (
Expand Down Expand Up @@ -211,11 +214,10 @@ func BuyGasRip7560Transaction(st *types.Rip7560AccountAbstractionTx, state vm.St
//TODO: check gasLimit against block gasPool
preCharge := new(uint256.Int).SetUint64(gasLimit)
preCharge = preCharge.Mul(preCharge, gasPrice)
balanceCheck := new(uint256.Int).Set(preCharge)

chargeFrom := st.GasPayer()

if have, want := state.GetBalance(*chargeFrom), balanceCheck; have.Cmp(want) < 0 {
if have, want := state.GetBalance(*chargeFrom), preCharge; have.Cmp(want) < 0 {
return 0, nil, fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, chargeFrom.Hex(), have, want)
}

Expand Down Expand Up @@ -291,7 +293,7 @@ func ptr(s string) *string { return &s }
func ApplyRip7560ValidationPhases(
chainConfig *params.ChainConfig,
bc ChainContext,
author *common.Address,
coinbase *common.Address,
gp *GasPool,
statedb *state.StateDB,
header *types.Header,
Expand All @@ -300,21 +302,17 @@ func ApplyRip7560ValidationPhases(
) (*ValidationPhaseResult, error) {
aatx := tx.Rip7560TransactionData()

gasPrice := new(big.Int).Add(header.BaseFee, tx.GasTipCap())
if gasPrice.Cmp(tx.GasFeeCap()) > 0 {
gasPrice = tx.GasFeeCap()
}
gasPriceUint256, _ := uint256.FromBig(gasPrice)

gasLimit, preCharge, err := BuyGasRip7560Transaction(aatx, statedb, gasPriceUint256)
gasPrice := aatx.EffectiveGasPrice(header.BaseFee)
effectiveGasPrice := uint256.MustFromBig(gasPrice)
gasLimit, preCharge, err := BuyGasRip7560Transaction(aatx, statedb, effectiveGasPrice)
if err != nil {
return nil, newValidationPhaseError(err, nil, nil, false)
}

blockContext := NewEVMBlockContext(header, bc, author)
sender := tx.Rip7560TransactionData().Sender
blockContext := NewEVMBlockContext(header, bc, coinbase)
sender := aatx.Sender
txContext := vm.TxContext{
Origin: *sender,
Origin: *aatx.Sender,
GasPrice: gasPrice,
}
evm := vm.NewEVM(blockContext, txContext, statedb, chainConfig, cfg)
Expand Down Expand Up @@ -422,24 +420,34 @@ func ApplyRip7560ValidationPhases(
}

callDataUsedGas, err := aatx.CallDataGasCost()
//this is the value to refund, but we refund at the end, after execution.
// we COULD refund here (and thus restore unused gas to the gaspool),and that would allow more TXs to be included in a block,
// but that would require a more complex refund mechanism: splitting evm refund from excessive prefund refund.
forshtat marked this conversation as resolved.
Show resolved Hide resolved
gasRefund := st.state.GetRefund()

validationGasUsed := st.gasUsed() + callDataUsedGas
forshtat marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
vpr := &ValidationPhaseResult{
Tx: tx,
TxHash: tx.Hash(),
PreCharge: preCharge,
EffectiveGasPrice: gasPriceUint256,
PaymasterContext: paymasterContext,
Tx: tx,
TxHash: tx.Hash(),
PreCharge: preCharge,
EffectiveGasPrice: effectiveGasPrice,
PaymasterContext: paymasterContext,
ValidationRefund: gasRefund,
TotalValidationGasUsed: validationGasUsed,

CallDataUsedGas: callDataUsedGas,
DeploymentUsedGas: deploymentUsedGas,
NonceManagerUsedGas: nonceManagerUsedGas,
ValidationUsedGas: resultAccountValidation.UsedGas,
PmValidationUsedGas: pmValidationUsedGas,
SenderValidAfter: aad.ValidAfter.Uint64(),
SenderValidUntil: aad.ValidUntil.Uint64(),
PmValidAfter: pmValidAfter,
PmValidUntil: pmValidUntil,

SenderValidAfter: aad.ValidAfter.Uint64(),
SenderValidUntil: aad.ValidUntil.Uint64(),
PmValidAfter: pmValidAfter,
PmValidUntil: pmValidUntil,
}
statedb.Finalise(true)

Expand Down Expand Up @@ -486,6 +494,14 @@ func applyPaymasterPostOpFrame(st *StateTransition, aatx *types.Rip7560AccountAb
return paymasterPostOpResult
}

func capRefund(getRefund uint64, gasUsed uint64) uint64 {
refund := gasUsed / params.RefundQuotientEIP3529
if refund > getRefund {
return getRefund
}
return refund
}

func ApplyRip7560ExecutionPhase(
config *params.ChainConfig,
vpr *ValidationPhaseResult,
Expand Down Expand Up @@ -516,25 +532,25 @@ func ApplyRip7560ExecutionPhase(
executionResult := CallFrame(st, &AA_ENTRY_POINT, sender, accountExecutionMsg, aatx.Gas)
receiptStatus := types.ReceiptStatusSuccessful
executionStatus := ExecutionStatusSuccess
execRefund := capRefund(st.state.GetRefund(), executionResult.UsedGas)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no point in having capRefund called for each call - we probably should apply EIP-3529 refund cap the entire transaction's refund, not per-frame or per-phase.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be calculated and capped separately for each phase.
In in the future (see AA-450), refund it just after validation, before execution. This way, it might allow more TXs to be added to the block. Otherwise, this known-to-be-refunded gas can be used for more AATXs in the block.

if executionResult.Failed() {
receiptStatus = types.ReceiptStatusFailed
executionStatus = ExecutionStatusExecutionFailure
}
executionGasPenalty := (aatx.Gas - executionResult.UsedGas) * AA_GAS_PENALTY_PCT / 100

gasUsed := vpr.ValidationUsedGas +
vpr.NonceManagerUsedGas +
vpr.DeploymentUsedGas +
vpr.PmValidationUsedGas +
vpr.CallDataUsedGas +
gasUsed := vpr.TotalValidationGasUsed +
executionResult.UsedGas +
executionGasPenalty

gasRefund := capRefund(execRefund+vpr.ValidationRefund, gasUsed)

var postOpGasUsed uint64
var paymasterPostOpResult *ExecutionResult
if len(vpr.PaymasterContext) != 0 {
paymasterPostOpResult = applyPaymasterPostOpFrame(st, aatx, vpr, !executionResult.Failed(), gasUsed)
paymasterPostOpResult = applyPaymasterPostOpFrame(st, aatx, vpr, !executionResult.Failed(), gasUsed-gasRefund)
postOpGasUsed = paymasterPostOpResult.UsedGas
gasRefund += capRefund(paymasterPostOpResult.RefundedGas, postOpGasUsed)
// PostOp failed, reverting execution changes
if paymasterPostOpResult.Failed() {
statedb.RevertToSnapshot(beforeExecSnapshotId)
Expand All @@ -545,10 +561,14 @@ func ApplyRip7560ExecutionPhase(
executionStatus = ExecutionStatusPostOpFailure
}
postOpGasPenalty := (aatx.PostOpGas - postOpGasUsed) * AA_GAS_PENALTY_PCT / 100
gasUsed += postOpGasUsed + postOpGasPenalty
postOpGasUsed += postOpGasPenalty
gasUsed += postOpGasUsed
}
gasUsed -= gasRefund
refundPayer(vpr, statedb, gasUsed)
payCoinbase(st, aatx, gasUsed)

err = injectRIP7560TransactionEvent(aatx, executionStatus, header, statedb)
err := injectRIP7560TransactionEvent(aatx, executionStatus, header, statedb)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -578,8 +598,6 @@ func ApplyRip7560ExecutionPhase(

receipt.Status = receiptStatus

refundPayer(vpr, statedb, gasUsed)

// Set the receipt logs and create the bloom filter.
blockNumber := header.Number
receipt.Logs = statedb.GetLogs(vpr.TxHash, blockNumber.Uint64(), common.Hash{})
Expand Down Expand Up @@ -669,6 +687,34 @@ func injectEvent(topics []common.Hash, data []byte, blockNumber uint64, statedb
return nil
}

// extracted from TransitionDb()
func payCoinbase(st *StateTransition, msg *types.Rip7560AccountAbstractionTx, gasUsed uint64) {
rules := st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)

effectiveTip := msg.GasTipCap
if rules.IsLondon {
effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee))
}

effectiveTipU256, _ := uint256.FromBig(effectiveTip)

fmt.Printf("=== paying coinbase %v gasUsed %v effectiveTip %v\n", st.evm.Context.Coinbase.Hex(), gasUsed, effectiveTipU256)
drortirosh marked this conversation as resolved.
Show resolved Hide resolved

if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
// Skip fee payment when NoBaseFee is set and the fee fields
// are 0. This avoids a negative effectiveTip being applied to
// the coinbase when simulating calls.
} else {
fee := new(uint256.Int).SetUint64(gasUsed)
fee.Mul(fee, effectiveTipU256)
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
// add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true)
}
}
}

func prepareAccountValidationMessage(tx *types.Rip7560AccountAbstractionTx, signingHash common.Hash) ([]byte, error) {
return abiEncodeValidateTransaction(tx, signingHash)
}
Expand Down
4 changes: 4 additions & 0 deletions core/types/tx_rip7560.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ func (tx *Rip7560AccountAbstractionTx) IsRip7712Nonce() bool {
return tx.NonceKey != nil && tx.NonceKey.Cmp(big.NewInt(0)) == 1
}

func (tx *Rip7560AccountAbstractionTx) EffectiveGasPrice(baseFee *big.Int) *big.Int {
return tx.effectiveGasPrice(new(big.Int), baseFee)
}

func (tx *Rip7560AccountAbstractionTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
if baseFee == nil {
return dst.Set(tx.GasFeeCap)
Expand Down
Loading