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 12 commits into
base: optimism
Choose a base branch
from
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),
}
}

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