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

isthmus: operator fee #388

Draft
wants to merge 21 commits into
base: optimism
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions core/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,19 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
random = &header.MixDigest
}
return vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time,
Difficulty: new(big.Int).Set(header.Difficulty),
BaseFee: baseFee,
BlobBaseFee: blobBaseFee,
GasLimit: header.GasLimit,
Random: random,
L1CostFunc: types.NewL1CostFunc(config, statedb),
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: GetHashFn(header, chain),
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time,
Difficulty: new(big.Int).Set(header.Difficulty),
BaseFee: baseFee,
BlobBaseFee: blobBaseFee,
GasLimit: header.GasLimit,
Random: random,
L1CostFunc: types.NewL1CostFunc(config, statedb),
OperatorCostFunc: types.NewOperatorCostFunc(config, statedb),
Copy link
Contributor

Choose a reason for hiding this comment

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

I lean towards not adding a new hook and just making the L1 cost function more generic, a better name for it would be L2CostFunc which would imply it does the L2 specific fee calculations. Curious what other people think

Copy link
Author

Choose a reason for hiding this comment

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

I thought about this too. Currently, these are the function signatures for the L1CostFunc and the OperatorCostFunc

type L1CostFunc func(rcd RollupCostData, blockTime uint64) *big.Int

type OperatorCostFunc func(gasUsed *big.Int, isRefund bool, blockTime uint64) *big.Int

I could potentially package up the gasUsed and isRefund together into an OperatorCostData or something. Then, we'd have this type.

type L2CostFunc func(rcd RollupCostData, ocd OperatorCostData, blockTime uint64) (*big.Int, *big.Int)

which returns the l1 cost and operator cost as a pair.

This pattern seems clunkier to me -- though the L1 fee and Operator fee are conceptually similar, I don't like returning both in a single function. For example, when calculating refunds, we only need the operator fee but not the L1 fee.

I'd definitely be open to unifying the two with a cleaner function design though.

}
}

Expand Down
30 changes: 30 additions & 0 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,14 @@ func (st *StateTransition) buyGas() error {
mgval = mgval.Add(mgval, l1Cost)
}
}
var operatorCost *big.Int
if st.evm.Context.OperatorCostFunc != nil && !st.msg.SkipAccountChecks {
operatorCost = st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.msg.GasLimit), true, st.evm.Context.Time)
if operatorCost != nil {
mgval = mgval.Add(mgval, operatorCost)
}
}

balanceCheck := new(big.Int).Set(mgval)
if st.msg.GasFeeCap != nil {
balanceCheck.SetUint64(st.msg.GasLimit)
Expand All @@ -262,6 +270,9 @@ func (st *StateTransition) buyGas() error {
if l1Cost != nil {
balanceCheck.Add(balanceCheck, l1Cost)
}
if operatorCost != nil {
balanceCheck.Add(balanceCheck, operatorCost)
}

if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if blobGas := st.blobGasUsed(); blobGas > 0 {
Expand Down Expand Up @@ -595,6 +606,15 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) {
}
st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
}

// Additionally pay the coinbase according for the operator fee.
if operatorCost := st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.gasUsed()), true, st.evm.Context.Time); operatorCost != nil {
amtU256, overflow = uint256.FromBig(operatorCost)
if overflow {
return nil, fmt.Errorf("optimism operator cost overflows U256: %d", operatorCost)
}
st.state.AddBalance(params.OptimismOperatorFeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
}
}

return &ExecutionResult{
Expand Down Expand Up @@ -623,6 +643,16 @@ func (st *StateTransition) refundGas(refundQuotient uint64) uint64 {
remaining.Mul(remaining, uint256.MustFromBig(st.msg.GasPrice))
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)

if optimismConfig := st.evm.ChainConfig().Optimism; optimismConfig != nil && !st.msg.IsDepositTx {
// Return ETH to transaction sender for operator cost overcharge.
if operatorCost := st.evm.Context.OperatorCostFunc(new(big.Int).SetUint64(st.gasRemaining), false, st.evm.Context.Time); operatorCost != nil {
amtU256, overflow := uint256.FromBig(operatorCost)
if !overflow {
st.state.AddBalance(st.msg.From, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
}
}
}

if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
}
Expand Down
12 changes: 12 additions & 0 deletions core/types/gen_receipt_json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion core/types/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,16 @@ type Receipt struct {
BlockNumber *big.Int `json:"blockNumber,omitempty"`
TransactionIndex uint `json:"transactionIndex"`

// Optimism: extend receipts with L1 fee info
// Optimism: extend receipts with L1 and operator fee info
L1GasPrice *big.Int `json:"l1GasPrice,omitempty"` // Present from pre-bedrock. L1 Basefee after Bedrock
L1BlobBaseFee *big.Int `json:"l1BlobBaseFee,omitempty"` // Always nil prior to the Ecotone hardfork
L1GasUsed *big.Int `json:"l1GasUsed,omitempty"` // Present from pre-bedrock, deprecated as of Fjord
L1Fee *big.Int `json:"l1Fee,omitempty"` // Present from pre-bedrock
FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` // Present from pre-bedrock to Ecotone. Nil after Ecotone
L1BaseFeeScalar *uint64 `json:"l1BaseFeeScalar,omitempty"` // Always nil prior to the Ecotone hardfork
L1BlobBaseFeeScalar *uint64 `json:"l1BlobBaseFeeScalar,omitempty"` // Always nil prior to the Ecotone hardfork
OperatorFeeScalar *uint64 `json:"operatorFeeScalar,omitempty"` // Always nil prior to the Holocene hardfork
OperatorFeeConstant *uint64 `json:"operatorFeeConstant,omitempty"` // Always nil prior to the Holocene hardfork
}

type receiptMarshaling struct {
Expand All @@ -116,6 +118,8 @@ type receiptMarshaling struct {
L1BlobBaseFeeScalar *hexutil.Uint64
DepositNonce *hexutil.Uint64
DepositReceiptVersion *hexutil.Uint64
OperatorFeeScalar *hexutil.Uint64
OperatorFeeConstant *hexutil.Uint64
}

// receiptRLP is the consensus encoding of a receipt.
Expand Down Expand Up @@ -590,6 +594,8 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu
rs[i].FeeScalar = gasParams.feeScalar
rs[i].L1BaseFeeScalar = u32ptrTou64ptr(gasParams.l1BaseFeeScalar)
rs[i].L1BlobBaseFeeScalar = u32ptrTou64ptr(gasParams.l1BlobBaseFeeScalar)
rs[i].OperatorFeeScalar = u32ptrTou64ptr(gasParams.operatorFeeScalar)
rs[i].OperatorFeeConstant = gasParams.operatorFeeConstant
}
}
return nil
Expand Down
104 changes: 104 additions & 0 deletions core/types/receipt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ var (
conf.EcotoneTime = &time
return &conf
}()
holoceneTestConfig = func() *params.ChainConfig {
conf := *bedrockGenesisTestConfig // copy the config
time := uint64(0)
conf.HoloceneTime = &time
return &conf
}()

legacyReceipt = &Receipt{
Status: ReceiptStatusFailed,
Expand Down Expand Up @@ -768,6 +774,78 @@ func getOptimismEcotoneTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1Blob
return txs, receipts
}

func getOptimismHoloceneTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1BlobGasPrice, l1GasUsed, l1Fee *big.Int, baseFeeScalar, blobBaseFeeScalar, operatorFeeScalar, operatorFeeConstant *uint64) ([]*Transaction, []*Receipt) {
// Create a few transactions to have receipts for
txs := Transactions{
NewTx(&DepositTx{
To: nil, // contract creation
Value: big.NewInt(6),
Gas: 50,
Data: l1AttributesPayload,
}),
emptyTx,
}

// Create the corresponding receipts
receipts := Receipts{
&Receipt{
Type: DepositTxType,
PostState: common.Hash{5}.Bytes(),
CumulativeGasUsed: 50 + 15,
Logs: []*Log{
{
Address: common.BytesToAddress([]byte{0x33}),
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 0,
},
{
Address: common.BytesToAddress([]byte{0x03, 0x33}),
// derived fields:
BlockNumber: blockNumber.Uint64(),
TxHash: txs[0].Hash(),
TxIndex: 0,
BlockHash: blockHash,
Index: 1,
},
},
TxHash: txs[0].Hash(),
ContractAddress: common.HexToAddress("0x3bb898b4bbe24f68a4e9be46cfe72d1787fd74f4"),
GasUsed: 65,
EffectiveGasPrice: big.NewInt(0),
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 0,
DepositNonce: &depNonce1,
},
&Receipt{
Type: LegacyTxType,
EffectiveGasPrice: big.NewInt(0),
PostState: common.Hash{4}.Bytes(),
CumulativeGasUsed: 10,
Logs: []*Log{},
// derived fields:
TxHash: txs[1].Hash(),
GasUsed: 18446744073709551561,
BlockHash: blockHash,
BlockNumber: blockNumber,
TransactionIndex: 1,
L1GasPrice: l1GasPrice,
L1BlobBaseFee: l1BlobGasPrice,
L1GasUsed: l1GasUsed,
L1Fee: l1Fee,
L1BaseFeeScalar: baseFeeScalar,
L1BlobBaseFeeScalar: blobBaseFeeScalar,
OperatorFeeScalar: operatorFeeScalar,
OperatorFeeConstant: operatorFeeConstant,
},
}
return txs, receipts
}

func getOptimismTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1GasUsed, l1Fee *big.Int, feeScalar *big.Float) ([]*Transaction, []*Receipt) {
// Create a few transactions to have receipts for
txs := Transactions{
Expand Down Expand Up @@ -888,6 +966,32 @@ func TestDeriveOptimismEcotoneTxReceipts(t *testing.T) {
diffReceipts(t, receipts, derivedReceipts)
}

func TestDeriveOptimismHoloceneTxReceipts(t *testing.T) {
// Holocene style l1 attributes with baseFeeScalar=2, blobBaseFeeScalar=3, baseFee=1000*1e6, blobBaseFee=10*1e6, operatorFeeScalar=7, operatorFeeConstant=9
payload := common.Hex2Bytes("d1fbe15b000000020000000300000000000004d200000000000004d200000000000004d2000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000004d2000000070000000000000009")
// the parameters we use below are defined in rollup_test.go
baseFeeScalarUint64 := baseFeeScalar.Uint64()
blobBaseFeeScalarUint64 := blobBaseFeeScalar.Uint64()
operatorFeeScalarUint64 := operatorFeeScalar.Uint64()
operatorFeeConstantUint64 := operatorFeeConstant.Uint64()
txs, receipts := getOptimismHoloceneTxReceipts(payload, baseFee, blobBaseFee, minimumFjordGas, fjordFee, &baseFeeScalarUint64, &blobBaseFeeScalarUint64, &operatorFeeScalarUint64, &operatorFeeConstantUint64)

// Re-derive receipts.
baseFee := big.NewInt(1000)
derivedReceipts := clearComputedFieldsOnReceipts(receipts)
// Should error out if we try to process this with a pre-Holocene config
err := Receipts(derivedReceipts).DeriveFields(bedrockGenesisTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs)
if err == nil {
t.Fatalf("expected error from deriving holocene receipts with pre-holocene config, got none")
}

err = Receipts(derivedReceipts).DeriveFields(holoceneTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs)
if err != nil {
t.Fatalf("DeriveFields(...) = %v, want <nil>", err)
}
diffReceipts(t, receipts, derivedReceipts)
}

func diffReceipts(t *testing.T, receipts, derivedReceipts []*Receipt) {
// Check diff of receipts against derivedReceipts.
r1, err := json.MarshalIndent(receipts, "", " ")
Expand Down
Loading