From 0967291487146bdf256a4f829ae84bbd9a9fd2ca Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 7 Dec 2023 12:00:54 +0330 Subject: [PATCH] Track burnt and selfdestruct withdraw bal changes --- core/state/metadata.go | 20 +++++--- core/state/statedb.go | 13 +++++- core/state_transition.go | 98 ++++++++++++++++++++++++++++++---------- 3 files changed, 99 insertions(+), 32 deletions(-) diff --git a/core/state/metadata.go b/core/state/metadata.go index db17b92a9e..90ea3d1742 100644 --- a/core/state/metadata.go +++ b/core/state/metadata.go @@ -33,11 +33,19 @@ const ( BalanceChangeGasRefund = 9 BalanceChangeTouchAccount = 10 // TODO: rename (debit, credit) - BalanceChangeSuicideRefund = 11 + // BalanceChangeSuicideRefund is added to the recipient as indicated by a selfdestructing account. + BalanceChangeSuicideRefund = 11 + // BalanceChangeSuicideWithdraw is deducted from a contract due to self-destruct. + // This can happen either at the point of self-destruction, or at the end of the tx + // if ether was sent to contract post-selfdestruct. BalanceChangeSuicideWithdraw = 12 - // TODO: add method on statedb to track burn without Add/Sub balance - // Or: Track via OnBurn - // 1559 burn, blob burn, withdraw to self via selfdestruct burn, receive ether after selfdestruct (at end of tx) - BalanceChangeBurn = 13 - BalanceChangeWithdrawal = 14 + // BalanceChangeBurn accounts for: + // - EIP-1559 burnt fees + // - ether that is sent to a self-destructed contract within the same tx (captured at end of tx) + // Note it doesn't account for a self-destruct which appoints same contract as recipient. + BalanceChangeBurn = 13 + // BalanceChangeBurnRefund is refunded to an account at the end of transaction based on + // gas usage from the estimated burn amount. + BalanceChangeBurnRefund = 14 + BalanceChangeWithdrawal = 15 ) diff --git a/core/state/statedb.go b/core/state/statedb.go index 0182fbb1dd..f951aa490b 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -476,13 +476,18 @@ func (s *StateDB) SelfDestruct(addr common.Address) { if stateObject == nil { return } + var ( + prev = new(big.Int).Set(stateObject.Balance()) + n = new(big.Int) + ) s.journal.append(selfDestructChange{ account: &addr, prev: stateObject.selfDestructed, - prevbalance: new(big.Int).Set(stateObject.Balance()), + prevbalance: prev, }) + s.logger.OnBalanceChange(addr, prev, n, BalanceChangeSuicideWithdraw) stateObject.markSelfdestructed() - stateObject.data.Balance = new(big.Int) + stateObject.data.Balance = n } func (s *StateDB) Selfdestruct6780(addr common.Address) { @@ -870,6 +875,10 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { obj.deleted = true + // If ether was sent to account post-selfdestruct it is burnt. + if bal := obj.Balance(); bal.Sign() != 0 { + s.logger.OnBalanceChange(obj.address, bal, new(big.Int), BalanceChangeBurn) + } // We need to maintain account deletions explicitly (will remain // set indefinitely). Note only the first occurred self-destruct // event is tracked. diff --git a/core/state_transition.go b/core/state_transition.go index a50447f3de..f4a68ec38f 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -233,44 +233,84 @@ func (st *StateTransition) to() common.Address { } func (st *StateTransition) buyGas() error { - mgval := new(big.Int).SetUint64(st.msg.GasLimit) - mgval = mgval.Mul(mgval, st.msg.GasPrice) - balanceCheck := new(big.Int).Set(mgval) if st.msg.GasFeeCap != nil { - balanceCheck.SetUint64(st.msg.GasLimit) - balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap) - balanceCheck.Add(balanceCheck, st.msg.Value) - } - if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { - if blobGas := st.blobGasUsed(); blobGas > 0 { - // Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap - blobBalanceCheck := new(big.Int).SetUint64(blobGas) - blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap) - balanceCheck.Add(balanceCheck, blobBalanceCheck) - // Pay for blobGasUsed * actual blob fee - blobFee := new(big.Int).SetUint64(blobGas) - blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee) - mgval.Add(mgval, blobFee) + if err := st.buy1559Gas(); err != nil { + return err + } + } else { + if err := st.buyLegacyGas(); err != nil { + return err } } - if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 { - return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) + if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { + if err := st.buyBlobGas(); err != nil { + return err + } } + if err := st.gp.SubGas(st.msg.GasLimit); err != nil { return err } - if st.evm.Config.Tracer != nil { st.evm.Config.Tracer.OnGasChange(0, st.msg.GasLimit, vm.GasChangeTxInitialBalance) } - st.gasRemaining += st.msg.GasLimit - st.initialGas = st.msg.GasLimit + + return nil +} + +func (st *StateTransition) buyLegacyGas() error { + mgval := new(big.Int).SetUint64(st.msg.GasLimit) + mgval = mgval.Mul(mgval, st.msg.GasPrice) + balanceCheck := new(big.Int).Set(mgval) + if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) + } st.state.SubBalance(st.msg.From, mgval, state.BalanceChangeGasBuy) return nil } +// buy1559Gas purchases gas according to EIP-1559 rules. See: +// https://eips.ethereum.org/EIPS/eip-1559 +func (st *StateTransition) buy1559Gas() error { + balanceCheck := new(big.Int).SetUint64(st.msg.GasLimit) + balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap) + balanceCheck.Add(balanceCheck, st.msg.Value) + if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) + } + + burnt := new(big.Int).SetUint64(st.msg.GasLimit) + burnt = burnt.Mul(burnt, st.evm.Context.BaseFee) + st.state.SubBalance(st.msg.From, burnt, state.BalanceChangeBurn) + + effectiveTip := cmath.BigMin(st.msg.GasTipCap, new(big.Int).Sub(st.msg.GasFeeCap, st.evm.Context.BaseFee)) + tipVal := new(big.Int).SetUint64(st.msg.GasLimit) + tipVal = tipVal.Mul(tipVal, effectiveTip) + st.state.SubBalance(st.msg.From, tipVal, state.BalanceChangeGasBuy) + + return nil +} + +// buyBlobGas purchases blob gas as per: +// https://eips.ethereum.org/EIPS/eip-4844 +func (st *StateTransition) buyBlobGas() error { + if blobGas := st.blobGasUsed(); blobGas > 0 { + // Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap + balanceCheck := new(big.Int).SetUint64(blobGas) + balanceCheck.Mul(balanceCheck, st.msg.BlobGasFeeCap) + if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) + } + // Pay for blobGasUsed * actual blob fee + blobFee := new(big.Int).SetUint64(blobGas) + blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee) + st.state.SubBalance(st.msg.From, blobFee, state.BalanceChangeBurn) + } + return nil +} + func (st *StateTransition) preCheck() error { // Only check transactions that are not fake msg := st.msg @@ -467,8 +507,18 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 { st.gasRemaining += refund // Return ETH for remaining gas, exchanged at the original rate. - remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice) - st.state.AddBalance(st.msg.From, remaining, state.BalanceChangeGasRefund) + gasRemaining := new(big.Int).SetUint64(st.gasRemaining) + if st.msg.GasFeeCap.Sign() == 0 { + remaining := new(big.Int).Mul(gasRemaining, st.msg.GasPrice) + st.state.AddBalance(st.msg.From, remaining, state.BalanceChangeGasRefund) + } else { + // Split into burnt gas and tip gas to refund. + burnt := new(big.Int).Mul(gasRemaining, st.evm.Context.BaseFee) + st.state.AddBalance(st.msg.From, burnt, state.BalanceChangeBurnRefund) + effectiveTip := cmath.BigMin(st.msg.GasTipCap, new(big.Int).Sub(st.msg.GasFeeCap, st.evm.Context.BaseFee)) + tip := new(big.Int).Mul(gasRemaining, effectiveTip) + st.state.AddBalance(st.msg.From, tip, state.BalanceChangeGasRefund) + } if st.evm.Config.Tracer != nil && st.gasRemaining > 0 { st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, vm.GasChangeTxLeftOverReturned)