Skip to content

Commit

Permalink
Handle fee currencies in state transition
Browse files Browse the repository at this point in the history
  • Loading branch information
palango committed Nov 6, 2023
1 parent 91135bf commit 7eb9249
Showing 1 changed file with 122 additions and 15 deletions.
137 changes: 122 additions & 15 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
contracts "github.com/ethereum/go-ethereum/contracts"
celo "github.com/ethereum/go-ethereum/contracts/celo"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -287,13 +289,22 @@ 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)

var l1Cost *big.Int
if st.evm.Context.L1CostFunc != nil && !st.msg.SkipAccountChecks {
l1Cost = st.evm.Context.L1CostFunc(st.evm.Context.BlockNumber.Uint64(), st.evm.Context.Time, st.msg.RollupDataGas, st.msg.IsDepositTx)

// L1 data fee needs to be converted in fee currency
if st.msg.FeeCurrency != nil {
// Existence of the fee currency has been checked in `preCheck`
rate := st.evm.Context.ExchangeRates[*st.msg.FeeCurrency]
l1Cost = l1Cost.Div(l1Cost.Mul(l1Cost, rate.Num()), rate.Denom())
}
}
if l1Cost != nil {
mgval = mgval.Add(mgval, l1Cost)
}

balanceCheck := new(big.Int).Set(mgval)
if st.msg.GasFeeCap != nil {
balanceCheck.SetUint64(st.msg.GasLimit)
Expand All @@ -315,19 +326,54 @@ func (st *StateTransition) buyGas() error {
mgval.Add(mgval, blobFee)
}
}
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 err := st.canPayFee(balanceCheck); err != nil {
return err
}
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err
}
st.gasRemaining += st.msg.GasLimit

st.initialGas = st.msg.GasLimit
st.state.SubBalance(st.msg.From, mgval)

return st.subFees(st.msg.From, st.msg.FeeCurrency, mgval)
}

// canPayFee checks whether accountOwner's balance can cover transaction fee.
func (st *StateTransition) canPayFee(checkAmount *big.Int) error {
if st.msg.FeeCurrency == nil {
balance := st.state.GetBalance(st.msg.From)

if balance.Cmp(checkAmount) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), balance, checkAmount)
}
return nil
} else {
balance, err := contracts.GetBalanceOf(st.backend, st.msg.From, *st.msg.FeeCurrency)
if err != nil {
return err
}

if balance.Cmp(checkAmount) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), balance, checkAmount)
}
}
return nil
}

func (st *StateTransition) subFees(from common.Address, feeCurrency *common.Address, effectiveFee *big.Int) (err error) {
log.Trace("Debiting fee", "from", from, "amount", effectiveFee, "feeCurrency", feeCurrency)

// native currency
if feeCurrency == nil {
st.state.SubBalance(from, effectiveFee)
return nil
} else {
return contracts.DebitFees(st.evm, from, effectiveFee, feeCurrency)
}
}

func (st *StateTransition) preCheck() error {
if st.msg.IsDepositTx {
// No fee fields to check, no nonce to check, and no need to check if EOA (L1 already verified it for us)
Expand Down Expand Up @@ -359,6 +405,7 @@ func (st *StateTransition) preCheck() error {
return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax,
msg.From.Hex(), stNonce)
}
// TODO(pl): This is removed in celo-blockchain, find out why
// Make sure the sender is an EOA
codeHash := st.state.GetCodeHash(msg.From)
if codeHash != (common.Hash{}) && codeHash != types.EmptyCodeHash {
Expand Down Expand Up @@ -391,6 +438,16 @@ func (st *StateTransition) preCheck() error {
}
}
}

if st.evm.ChainConfig().IsCel2(st.evm.Context.Time) {
isWhiteListed := st.evm.Context.IsCurrencyWhitelisted(st.msg.FeeCurrency)
if !isWhiteListed {
log.Trace("Fee currency not whitelisted", "fee currency address", st.msg.FeeCurrency)
return ErrNonWhitelistedFeeCurrency

}
}

// Check the blob version validity
if msg.BlobHashes != nil {
if len(msg.BlobHashes) == 0 {
Expand Down Expand Up @@ -469,6 +526,9 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
// 5. there is no overflow when calculating intrinsic gas
// 6. caller has enough balance to cover asset transfer for **topmost** call

// TODO(pl): what about this clause? Was this only pre-London?
// 2. the gas price meets the minimum gas price

// Check clauses 1-3, buy gas if everything is correct
if err := st.preCheck(); err != nil {
return nil, err
Expand Down Expand Up @@ -564,19 +624,10 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
ReturnData: ret,
}, nil
}
effectiveTip := msg.GasPrice
if rules.IsLondon {
effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee))
}

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(big.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTip)
st.state.AddBalance(st.evm.Context.Coinbase, fee)
err = st.distributeTxFees()
if err != nil {
return nil, err
}

// Check that we are post bedrock to enable op-geth to be able to create pseudo pre-bedrock blocks (these are pre-bedrock, but don't follow l2 geth rules)
Expand Down Expand Up @@ -621,3 +672,59 @@ func (st *StateTransition) gasUsed() uint64 {
func (st *StateTransition) blobGasUsed() uint64 {
return uint64(len(st.msg.BlobHashes) * params.BlobTxBlobGasPerBlob)
}

// distributeTxFees calculates the amounts and recipients of transaction fees and credits the accounts.
func (st *StateTransition) distributeTxFees() error {
// TODO(pl): check tracing
// Run only primary evm.Call() with tracer
// if st.evm.GetDebug() {
// st.evm.SetDebug(false)
// defer func() { st.evm.SetDebug(true) }()
// }
msg := st.msg
// effectiveTip := msg.GasPrice
// if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
// effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee))
// }

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.
return nil
}
// else {
// fee := new(big.Int).SetUint64(st.gasUsed())
// fee.Mul(fee, effectiveTip)
// st.state.AddBalance(st.evm.Context.Coinbase, fee)
// }
// Determine the refund and transaction fee to be distributed.
refund := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice)
gasUsed := new(big.Int).SetUint64(st.gasUsed())
totalTxFee := new(big.Int).Mul(gasUsed, st.msg.GasPrice)
from := st.msg.From

// Divide the transaction into a base (the minimum transaction fee) and tip (any extra, or min(max tip, feecap - GPM) if espresso).
baseTxFee := new(big.Int).Mul(gasUsed, st.gasPriceMinimum)
// No need to do effectiveTip calculation, because st.gasPrice == effectiveGasPrice, and effectiveTip = effectiveGasPrice - baseTxFee
tipTxFee := new(big.Int).Sub(totalTxFee, baseTxFee)

feeCurrency := st.msg.FeeCurrency
feeHandlerAddress := celo.FeeHandlerAddress

log.Trace("distributeTxFees", "from", from, "refund", refund, "feeCurrency", st.msg.FeeCurrency,
"coinbaseFeeRecipient", st.evm.Context.Coinbase, "coinbaseFee", tipTxFee,
"feeHandler", feeHandlerAddress, "communityFundFee", baseTxFee)
if feeCurrency == nil {
st.state.AddBalance(feeHandlerAddress, baseTxFee)
st.state.AddBalance(st.evm.Context.Coinbase, tipTxFee)
st.state.AddBalance(from, refund)
} else {
if err := contracts.CreditFees(st.evm, from, st.evm.Context.Coinbase, feeHandlerAddress, refund, tipTxFee, baseTxFee, feeCurrency); err != nil {
log.Error("Error crediting", "from", from, "coinbase", st.evm.Context.Coinbase, "feeHandler", feeHandlerAddress)
return err
}

}
return nil
}

0 comments on commit 7eb9249

Please sign in to comment.