Skip to content

Commit

Permalink
Merge pull request #16 from ethstorage/partial_sgt
Browse files Browse the repository at this point in the history
Partial SGT
  • Loading branch information
blockchaindevsh authored Nov 1, 2024
2 parents e6895e7 + ba2b41e commit eb4eccc
Showing 1 changed file with 179 additions and 48 deletions.
227 changes: 179 additions & 48 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,19 @@ type StateTransition struct {
initialGas uint64
state vm.StateDB
evm *vm.EVM
gasFromSoul bool

// nil means SGT is not used at all
usedSGTBalance *uint256.Int
// should not be used if usedSGTBalance is nil;
// only set when: 1. usedSGTBalance is non-nil 2. used native balance is gt 0
usedNativeBalance *uint256.Int

// these are set once for checking gas formula only
boughtGas *uint256.Int
refundedGas *uint256.Int
tipFee *uint256.Int
baseFee *uint256.Int
l1Fee *uint256.Int
}

// NewStateTransition initialises and returns a new state transition object.
Expand All @@ -242,6 +254,78 @@ func NewStateTransition(evm *vm.EVM, msg *Message, gp *GasPool) *StateTransition
}
}

func (st *StateTransition) checkGasFormula() error {
if st.boughtGas.Cmp(
new(uint256.Int).Add(
st.refundedGas, new(uint256.Int).Add(
st.tipFee, new(uint256.Int).Add(
st.baseFee, st.l1Fee)))) != 0 {
return fmt.Errorf("gas formula doesn't hold: boughtGas(%v) != refundedGas(%v) + tipFee(%v) + baseFee(%v) + l1Fee(%v)", st.boughtGas, st.refundedGas, st.tipFee, st.baseFee, st.l1Fee)
}
return nil
}

func (st *StateTransition) collectableNativeBalance(amount *uint256.Int) *uint256.Int {
// we burn the token if gas is from SoulGasToken which is not backed by native
if st.usedSGTBalance != nil && st.evm.ChainConfig().IsOptimism() && !st.evm.ChainConfig().Optimism.IsSoulBackedByNative {
_, amount = st.distributeGas(amount, st.usedSGTBalance, st.usedNativeBalance)
}
return amount
}

// distributeGas distributes the gas according to the priority:
//
// first pool1, then pool2.
//
// In more detail:
// split amount among two pools, first pool1, then pool2, where poolx means max amount for pool x.
// quotax is the amount distributed to pool x.
//
// note: the returned values are always non-nil.
func (st *StateTransition) distributeGas(amount, pool1, pool2 *uint256.Int) (quota1, quota2 *uint256.Int) {
if amount == nil {
panic("amount should not be nil")
}
if st.usedSGTBalance == nil {
panic("should not happen when usedSGTBalance is nil")
}
if pool1 == nil {
// pool1 empty, all to pool2
quota1 = new(uint256.Int)
quota2 = amount.Clone()

pool2.Sub(pool2, quota2)
return
}
if pool2 == nil {
// pool2 empty, all to pool1
quota1 = amount.Clone()
quota2 = new(uint256.Int)

pool1.Sub(pool1, quota1)
return
}

// from here, both pool1 and pool2 are non-nil

if amount.Cmp(pool1) >= 0 {
// partial pool1, remaining to pool2
quota1 = pool1.Clone()
quota2 = new(uint256.Int).Sub(amount, quota1)

pool1.Clear()
pool2.Sub(pool2, quota2)
} else {
// all to pool1
quota1 = amount.Clone()
quota2 = new(uint256.Int)

pool1.Sub(pool1, quota1)
}

return
}

// to returns the recipient of the message.
func (st *StateTransition) to() common.Address {
if st.msg == nil || st.msg.To == nil /* contract creation */ {
Expand Down Expand Up @@ -314,28 +398,31 @@ func GetGasBalancesInBig(state vm.StateDB, chainconfig *params.ChainConfig, acco
return bal.ToBig(), sgtBal.ToBig()
}

func (st *StateTransition) SubSoulBalance(account common.Address, amount *big.Int, reason tracing.BalanceChangeReason) (err error) {
current := st.GetSoulBalance(account).ToBig()
// called by buyGas
func (st *StateTransition) subSoulBalance(account common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) (err error) {
current := st.GetSoulBalance(account)
if current.Cmp(amount) < 0 {
return fmt.Errorf("soul balance not enough, current:%v, expect:%v", current, amount)
}

value := uint256.MustFromBig(current.Sub(current, amount)).Bytes32()
value := current.Sub(current, amount).Bytes32()
st.state.SetState(types.SoulGasTokenAddr, TargetSGTBalanceSlot(account), value)

if st.evm.ChainConfig().IsOptimism() && st.evm.ChainConfig().Optimism.IsSoulBackedByNative {
st.state.SubBalance(types.SoulGasTokenAddr, uint256.MustFromBig(amount), reason)
st.state.SubBalance(types.SoulGasTokenAddr, amount, reason)
}

return
}

func (st *StateTransition) AddSoulBalance(account common.Address, amount *big.Int, reason tracing.BalanceChangeReason) {
current := st.GetSoulBalance(account).ToBig()
value := uint256.MustFromBig(current.Add(current, amount)).Bytes32()
// called by refundGas
func (st *StateTransition) addSoulBalance(account common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) {
current := st.GetSoulBalance(account)
value := current.Add(current, amount).Bytes32()
st.state.SetState(types.SoulGasTokenAddr, TargetSGTBalanceSlot(account), value)

if st.evm.ChainConfig().IsOptimism() && st.evm.ChainConfig().Optimism.IsSoulBackedByNative {
st.state.AddBalance(types.SoulGasTokenAddr, uint256.MustFromBig(amount), reason)
st.state.AddBalance(types.SoulGasTokenAddr, amount, reason)
}
}

Expand Down Expand Up @@ -377,22 +464,23 @@ func (st *StateTransition) buyGas() error {
return fmt.Errorf("%w: address %v required balance exceeds 256 bits", ErrInsufficientFunds, st.msg.From.Hex())
}

st.gasFromSoul = false

nativeBalance := st.state.GetBalance(st.msg.From)
var soulBalance *uint256.Int
if st.evm.ChainConfig().IsOptimism() && st.evm.ChainConfig().Optimism.UseSoulGasToken {
have := st.GetSoulBalance(st.msg.From)
if have, want := have.ToBig(), new(big.Int).Sub(balanceCheck, st.msg.Value); have.Cmp(want) >= 0 {
if have, want := st.state.GetBalance(st.msg.From).ToBig(), st.msg.Value; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
}
st.gasFromSoul = true
if have, want := nativeBalance.ToBig(), st.msg.Value; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have native balance %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
}
}
if !st.gasFromSoul {

soulBalance = st.GetSoulBalance(st.msg.From)
if have, want := new(uint256.Int).Add(nativeBalance, soulBalance), balanceCheckU256; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have total balance %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
}
} else {
if have, want := st.state.GetBalance(st.msg.From), balanceCheckU256; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
}
}

if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err
}
Expand All @@ -404,12 +492,33 @@ func (st *StateTransition) buyGas() error {

st.initialGas = st.msg.GasLimit

if st.gasFromSoul {
return st.SubSoulBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
} else {
mgvalU256, _ := uint256.FromBig(mgval)
mgvalU256, _ := uint256.FromBig(mgval)
st.boughtGas = mgvalU256.Clone()
if soulBalance == nil {
st.state.SubBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
} else {
if mgvalU256.Cmp(soulBalance) <= 0 {
err := st.subSoulBalance(st.msg.From, mgvalU256, tracing.BalanceDecreaseGasBuy)
if err != nil {
return err
}
st.usedSGTBalance = mgvalU256
} else {
err := st.subSoulBalance(st.msg.From, soulBalance, tracing.BalanceDecreaseGasBuy)
if err != nil {
return err
}
st.usedSGTBalance = soulBalance
// when both SGT and native balance are used, we record both amounts for refund.
// the priority for refund is: first native, then SGT
usedNativeBalance := new(uint256.Int).Sub(mgvalU256, soulBalance)
if usedNativeBalance.Sign() > 0 {
st.state.SubBalance(st.msg.From, usedNativeBalance, tracing.BalanceDecreaseGasBuy)
st.usedNativeBalance = usedNativeBalance
}
}
}

return nil
}

Expand Down Expand Up @@ -682,52 +791,67 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
}
effectiveTipU256, _ := uint256.FromBig(effectiveTip)

shouldCheckGasFormula := true
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.
shouldCheckGasFormula = false
} else {

fee := new(uint256.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTipU256)

// we burn the token if gas is from SoulGasToken which is not backed by native;
// otherwise we add to the native balance
if st.gasFromSoul && st.evm.ChainConfig().IsOptimism() && !st.evm.ChainConfig().Optimism.IsSoulBackedByNative {
} else {
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
st.tipFee = fee.Clone()

// add the coinbase to the witness iff the fee is greater than 0
if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true)
}
fee = st.collectableNativeBalance(fee)
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.AddAccount(st.evm.Context.Coinbase, true)
}
}

// 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)
// Note optimismConfig will not be nil if rules.IsOptimismBedrock is true
if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil && rules.IsOptimismBedrock && !st.msg.IsDepositTx {
gasCost := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.evm.Context.BaseFee)

if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
gasCost.Add(gasCost, new(big.Int).Mul(new(big.Int).SetUint64(st.blobGasUsed()), st.evm.Context.BlobBaseFee))
}

amtU256, overflow := uint256.FromBig(gasCost)
if overflow {
return nil, fmt.Errorf("optimism gas cost overflows U256: %d", gasCost)
}
// we burn the token if gas is from SoulGasToken which is not backed by native;
// otherwise we add to the native balance
if st.gasFromSoul && st.evm.ChainConfig().IsOptimism() && !st.evm.ChainConfig().Optimism.IsSoulBackedByNative {
} else {
st.state.AddBalance(params.OptimismBaseFeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
if shouldCheckGasFormula {
st.baseFee = amtU256.Clone()
}

amtU256 = st.collectableNativeBalance(amtU256)
st.state.AddBalance(params.OptimismBaseFeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
if l1Cost := st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time); l1Cost != nil {
amtU256, overflow = uint256.FromBig(l1Cost)
if overflow {
return nil, fmt.Errorf("optimism l1 cost overflows U256: %d", l1Cost)
}
// we burn the token if gas is from SoulGasToken which is not backed by native;
// otherwise we add to the native balance
if st.gasFromSoul && st.evm.ChainConfig().IsOptimism() && !st.evm.ChainConfig().Optimism.IsSoulBackedByNative {
} else {
st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)

if shouldCheckGasFormula {
st.l1Fee = amtU256.Clone()
}

amtU256 = st.collectableNativeBalance(amtU256)
st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
}

if shouldCheckGasFormula {
if st.l1Fee == nil {
st.l1Fee = new(uint256.Int)
}
if err := st.checkGasFormula(); err != nil {
return nil, err
}
}
}
Expand All @@ -754,13 +878,20 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
st.gasRemaining += refund

// Return ETH for remaining gas, exchanged at the original rate.
if st.gasFromSoul {
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gasRemaining), st.msg.GasPrice)
st.AddSoulBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
} else {
remaining := uint256.NewInt(st.gasRemaining)
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
remaining := uint256.NewInt(st.gasRemaining)
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
st.refundedGas = remaining.Clone()
if st.usedSGTBalance == nil {
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
} else {
native, sgt := st.distributeGas(remaining, st.usedNativeBalance, st.usedSGTBalance)
if native.Sign() > 0 {
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
}
if sgt.Sign() > 0 {
st.addSoulBalance(st.msg.From, sgt, tracing.BalanceIncreaseGasReturn)
st.usedSGTBalance.Sub(st.usedSGTBalance, sgt)
}
}

if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
Expand Down

0 comments on commit eb4eccc

Please sign in to comment.