Skip to content

Commit e136827

Browse files
yuwen01leruaarefcell
authored
isthmus: operator fee (#388)
* added types + tests * added logic to state_transition, passes all existing tests * fix small typo, add scaling factor * clean up some comments * reset testdata * add HoloceneL1AttributesSelector * reset testdata * fixed test * fix to rollup_cost_test * fees go to operatorFeeVault * moved operator fee scalars into separate slot * start change to isthmus + new design * finish merge * Update to Isthmus * Update to Isthmus * Allow to set IsthmusTime * update setL1BlockValuesIsthmus function selector include Isthmus in the banner Revert "include Isthmus in the banner" This reverts commit 0f997c3. * fix operator fee params storage ordering * fix operator fee params decoding * fix: rename test func * fix: merge * fix: handle feedbacks * fix: duplicate add l1Cost * feat: streamline operator fee calculation * fix: add back operator fee refund * fix: use original gasUsed to refund operator fee * nit: typo Co-authored-by: refcell <[email protected]> * fix: OperatorCostFunc doesn't return nil * fix: don't add operatorCost if GasFeeCap is nil * fix: don't access state if Isthmus not activated * fix: handle sebastianst feedbacks * fix: handle more feedbacks * fix: handle sebastianst feedbacks part 3 * fix: handle sebastian feedback part 4 * fix: handle more feedbacks * fix: call AddBalance even if diff is 0 * fix: TestTransientStorageReset test --------- Co-authored-by: Aurélien <[email protected]> Co-authored-by: leruaa <[email protected]> Co-authored-by: refcell <[email protected]>
1 parent 31738ca commit e136827

File tree

10 files changed

+446
-20
lines changed

10 files changed

+446
-20
lines changed

core/evm.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ type ChainContext interface {
4545
// NewEVMBlockContext creates a new context for use in the EVM.
4646
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, config *params.ChainConfig, statedb types.StateGetter) vm.BlockContext {
4747
var (
48-
beneficiary common.Address
49-
baseFee *big.Int
50-
blobBaseFee *big.Int
51-
random *common.Hash
48+
beneficiary common.Address
49+
baseFee *big.Int
50+
blobBaseFee *big.Int
51+
random *common.Hash
52+
operatorCostFn types.OperatorCostFunc
5253
)
5354

5455
// If we don't have an explicit author (i.e. not mining), extract from the header
@@ -66,6 +67,9 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
6667
if header.Difficulty.Sign() == 0 {
6768
random = &header.MixDigest
6869
}
70+
if config.IsOptimismIsthmus(header.Time) {
71+
operatorCostFn = types.NewOperatorCostFunc(config, statedb)
72+
}
6973
return vm.BlockContext{
7074
CanTransfer: CanTransfer,
7175
Transfer: Transfer,
@@ -78,7 +82,10 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
7882
BlobBaseFee: blobBaseFee,
7983
GasLimit: header.GasLimit,
8084
Random: random,
81-
L1CostFunc: types.NewL1CostFunc(config, statedb),
85+
86+
// OP-Stack additions
87+
L1CostFunc: types.NewL1CostFunc(config, statedb),
88+
OperatorCostFunc: operatorCostFn,
8289
}
8390
}
8491

core/state_transition.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,17 @@ func (st *stateTransition) buyGas() error {
275275
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
276276
mgval.Mul(mgval, st.msg.GasPrice)
277277
var l1Cost *big.Int
278-
if st.evm.Context.L1CostFunc != nil && !st.msg.SkipNonceChecks && !st.msg.SkipFromEOACheck {
279-
l1Cost = st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time)
280-
if l1Cost != nil {
281-
mgval = mgval.Add(mgval, l1Cost)
278+
var operatorCost *uint256.Int
279+
if !st.msg.SkipNonceChecks && !st.msg.SkipFromEOACheck {
280+
if st.evm.Context.L1CostFunc != nil {
281+
l1Cost = st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time)
282+
if l1Cost != nil {
283+
mgval = mgval.Add(mgval, l1Cost)
284+
}
285+
}
286+
if st.evm.Context.OperatorCostFunc != nil {
287+
operatorCost = st.evm.Context.OperatorCostFunc(st.msg.GasLimit, st.evm.Context.Time)
288+
mgval = mgval.Add(mgval, operatorCost.ToBig())
282289
}
283290
}
284291
balanceCheck := new(big.Int).Set(mgval)
@@ -288,6 +295,9 @@ func (st *stateTransition) buyGas() error {
288295
if l1Cost != nil {
289296
balanceCheck.Add(balanceCheck, l1Cost)
290297
}
298+
if operatorCost != nil {
299+
balanceCheck.Add(balanceCheck, operatorCost.ToBig())
300+
}
291301
}
292302
balanceCheck.Add(balanceCheck, st.msg.Value)
293303

@@ -628,6 +638,11 @@ func (st *stateTransition) innerExecute() (*ExecutionResult, error) {
628638
}
629639
}
630640
}
641+
if rules.IsOptimismIsthmus {
642+
// Calling st.refundOperatorCost() after st.gasRemaining is updated above,
643+
// so that state refunds are taken into account when calculating operator fees.
644+
st.refundIsthmusOperatorCost()
645+
}
631646
st.returnGas()
632647

633648
// OP-Stack: Note for deposit tx there is no ETH refunded for unused gas, but that's taken care of by the fact that gasPrice
@@ -682,6 +697,10 @@ func (st *stateTransition) innerExecute() (*ExecutionResult, error) {
682697
}
683698
st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256, tracing.BalanceIncreaseRewardTransactionFee)
684699
}
700+
if rules.IsOptimismIsthmus {
701+
operatorFeeCost := st.evm.Context.OperatorCostFunc(st.gasUsed(), st.evm.Context.Time)
702+
st.state.AddBalance(params.OptimismOperatorFeeRecipient, operatorFeeCost, tracing.BalanceIncreaseRewardTransactionFee)
703+
}
685704
}
686705
}
687706

@@ -786,6 +805,18 @@ func (st *stateTransition) returnGas() {
786805
st.gp.AddGas(st.gasRemaining)
787806
}
788807

808+
func (st *stateTransition) refundIsthmusOperatorCost() {
809+
// Return ETH to transaction sender for operator cost overcharge.
810+
operatorCostGasLimit := st.evm.Context.OperatorCostFunc(st.msg.GasLimit, st.evm.Context.Time)
811+
operatorCostGasUsed := st.evm.Context.OperatorCostFunc(st.gasUsed(), st.evm.Context.Time)
812+
813+
if operatorCostGasUsed.Cmp(operatorCostGasLimit) > 0 { // Sanity check.
814+
panic(fmt.Sprintf("operator cost gas used (%d) > operator cost gas limit (%d)", operatorCostGasUsed, operatorCostGasLimit))
815+
}
816+
817+
st.state.AddBalance(st.msg.From, new(uint256.Int).Sub(operatorCostGasLimit, operatorCostGasUsed), tracing.BalanceIncreaseGasReturn)
818+
}
819+
789820
// gasUsed returns the amount of gas used up by the state transition.
790821
func (st *stateTransition) gasUsed() uint64 {
791822
return st.initialGas - st.gasRemaining

core/types/gen_receipt_json.go

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/types/receipt.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,16 @@ type Receipt struct {
8484
BlockNumber *big.Int `json:"blockNumber,omitempty"`
8585
TransactionIndex uint `json:"transactionIndex"`
8686

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

9799
type receiptMarshaling struct {
@@ -116,6 +118,8 @@ type receiptMarshaling struct {
116118
L1BlobBaseFeeScalar *hexutil.Uint64
117119
DepositNonce *hexutil.Uint64
118120
DepositReceiptVersion *hexutil.Uint64
121+
OperatorFeeScalar *hexutil.Uint64
122+
OperatorFeeConstant *hexutil.Uint64
119123
}
120124

121125
// receiptRLP is the consensus encoding of a receipt.
@@ -590,6 +594,10 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu
590594
rs[i].FeeScalar = gasParams.feeScalar
591595
rs[i].L1BaseFeeScalar = u32ptrTou64ptr(gasParams.l1BaseFeeScalar)
592596
rs[i].L1BlobBaseFeeScalar = u32ptrTou64ptr(gasParams.l1BlobBaseFeeScalar)
597+
if gasParams.operatorFeeScalar != nil && gasParams.operatorFeeConstant != nil && (*gasParams.operatorFeeScalar != 0 || *gasParams.operatorFeeConstant != 0) {
598+
rs[i].OperatorFeeScalar = u32ptrTou64ptr(gasParams.operatorFeeScalar)
599+
rs[i].OperatorFeeConstant = gasParams.operatorFeeConstant
600+
}
593601
}
594602
}
595603
return nil

core/types/receipt_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ var (
4747
conf.EcotoneTime = &time
4848
return &conf
4949
}()
50+
isthmusTestConfig = func() *params.ChainConfig {
51+
conf := *bedrockGenesisTestConfig // copy the config
52+
time := uint64(0)
53+
conf.IsthmusTime = &time
54+
return &conf
55+
}()
5056

5157
legacyReceipt = &Receipt{
5258
Status: ReceiptStatusFailed,
@@ -767,6 +773,78 @@ func getOptimismEcotoneTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1Blob
767773
return txs, receipts
768774
}
769775

776+
func getOptimismIsthmusTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1BlobGasPrice, l1GasUsed, l1Fee *big.Int, baseFeeScalar, blobBaseFeeScalar, operatorFeeScalar, operatorFeeConstant *uint64) ([]*Transaction, []*Receipt) {
777+
// Create a few transactions to have receipts for
778+
txs := Transactions{
779+
NewTx(&DepositTx{
780+
To: nil, // contract creation
781+
Value: big.NewInt(6),
782+
Gas: 50,
783+
Data: l1AttributesPayload,
784+
}),
785+
emptyTx,
786+
}
787+
788+
// Create the corresponding receipts
789+
receipts := Receipts{
790+
&Receipt{
791+
Type: DepositTxType,
792+
PostState: common.Hash{5}.Bytes(),
793+
CumulativeGasUsed: 50 + 15,
794+
Logs: []*Log{
795+
{
796+
Address: common.BytesToAddress([]byte{0x33}),
797+
// derived fields:
798+
BlockNumber: blockNumber.Uint64(),
799+
TxHash: txs[0].Hash(),
800+
TxIndex: 0,
801+
BlockHash: blockHash,
802+
Index: 0,
803+
},
804+
{
805+
Address: common.BytesToAddress([]byte{0x03, 0x33}),
806+
// derived fields:
807+
BlockNumber: blockNumber.Uint64(),
808+
TxHash: txs[0].Hash(),
809+
TxIndex: 0,
810+
BlockHash: blockHash,
811+
Index: 1,
812+
},
813+
},
814+
TxHash: txs[0].Hash(),
815+
ContractAddress: common.HexToAddress("0x3bb898b4bbe24f68a4e9be46cfe72d1787fd74f4"),
816+
GasUsed: 65,
817+
EffectiveGasPrice: big.NewInt(0),
818+
BlockHash: blockHash,
819+
BlockNumber: blockNumber,
820+
TransactionIndex: 0,
821+
DepositNonce: &depNonce1,
822+
},
823+
&Receipt{
824+
Type: LegacyTxType,
825+
EffectiveGasPrice: big.NewInt(0),
826+
PostState: common.Hash{4}.Bytes(),
827+
CumulativeGasUsed: 10,
828+
Logs: []*Log{},
829+
// derived fields:
830+
TxHash: txs[1].Hash(),
831+
GasUsed: 18446744073709551561,
832+
BlockHash: blockHash,
833+
BlockNumber: blockNumber,
834+
TransactionIndex: 1,
835+
L1GasPrice: l1GasPrice,
836+
L1BlobBaseFee: l1BlobGasPrice,
837+
L1GasUsed: l1GasUsed,
838+
L1Fee: l1Fee,
839+
L1BaseFeeScalar: baseFeeScalar,
840+
L1BlobBaseFeeScalar: blobBaseFeeScalar,
841+
OperatorFeeScalar: operatorFeeScalar,
842+
OperatorFeeConstant: operatorFeeConstant,
843+
},
844+
}
845+
return txs, receipts
846+
}
847+
770848
func getOptimismTxReceipts(l1AttributesPayload []byte, l1GasPrice, l1GasUsed, l1Fee *big.Int, feeScalar *big.Float) ([]*Transaction, []*Receipt) {
771849
// Create a few transactions to have receipts for
772850
txs := Transactions{
@@ -887,6 +965,56 @@ func TestDeriveOptimismEcotoneTxReceipts(t *testing.T) {
887965
diffReceipts(t, receipts, derivedReceipts)
888966
}
889967

968+
func TestDeriveOptimismIsthmusTxReceipts(t *testing.T) {
969+
// Isthmus style l1 attributes with baseFeeScalar=2, blobBaseFeeScalar=3, baseFee=1000*1e6, blobBaseFee=10*1e6, operatorFeeScalar=7, operatorFeeConstant=9
970+
payload := common.Hex2Bytes("098999be000000020000000300000000000004d200000000000004d200000000000004d2000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000004d255c6fb7c116fb15b44847d04")
971+
// the parameters we use below are defined in rollup_test.go
972+
baseFeeScalarUint64 := baseFeeScalar.Uint64()
973+
blobBaseFeeScalarUint64 := blobBaseFeeScalar.Uint64()
974+
operatorFeeScalarUint64 := operatorFeeScalar.Uint64()
975+
operatorFeeConstantUint64 := operatorFeeConstant.Uint64()
976+
txs, receipts := getOptimismIsthmusTxReceipts(payload, baseFee, blobBaseFee, minimumFjordGas, fjordFee, &baseFeeScalarUint64, &blobBaseFeeScalarUint64, &operatorFeeScalarUint64, &operatorFeeConstantUint64)
977+
978+
// Re-derive receipts.
979+
baseFee := big.NewInt(1000)
980+
derivedReceipts := clearComputedFieldsOnReceipts(receipts)
981+
// Should error out if we try to process this with a pre-Isthmus config
982+
err := Receipts(derivedReceipts).DeriveFields(bedrockGenesisTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs)
983+
if err == nil {
984+
t.Fatalf("expected error from deriving isthmus receipts with pre-isthmus config, got none")
985+
}
986+
987+
err = Receipts(derivedReceipts).DeriveFields(isthmusTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs)
988+
if err != nil {
989+
t.Fatalf("DeriveFields(...) = %v, want <nil>", err)
990+
}
991+
diffReceipts(t, receipts, derivedReceipts)
992+
}
993+
994+
func TestDeriveOptimismIsthmusTxReceiptsNoOperatorFee(t *testing.T) {
995+
// Isthmus style l1 attributes with baseFeeScalar=2, blobBaseFeeScalar=3, baseFee=1000*1e6, blobBaseFee=10*1e6, operatorFeeScalar=7, operatorFeeConstant=9
996+
payload := common.Hex2Bytes("098999be000000020000000300000000000004d200000000000004d200000000000004d2000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000098968000000000000000000000000000000000000000000000000000000000000004d200000000000000000000000000000000000000000000000000000000000004d2000000000000000000000000")
997+
// the parameters we use below are defined in rollup_test.go
998+
baseFeeScalarUint64 := baseFeeScalar.Uint64()
999+
blobBaseFeeScalarUint64 := blobBaseFeeScalar.Uint64()
1000+
txs, receipts := getOptimismIsthmusTxReceipts(payload, baseFee, blobBaseFee, minimumFjordGas, fjordFee, &baseFeeScalarUint64, &blobBaseFeeScalarUint64, nil, nil)
1001+
1002+
// Re-derive receipts.
1003+
baseFee := big.NewInt(1000)
1004+
derivedReceipts := clearComputedFieldsOnReceipts(receipts)
1005+
// Should error out if we try to process this with a pre-Isthmus config
1006+
err := Receipts(derivedReceipts).DeriveFields(bedrockGenesisTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs)
1007+
if err == nil {
1008+
t.Fatalf("expected error from deriving isthmus receipts with pre-isthmus config, got none")
1009+
}
1010+
1011+
err = Receipts(derivedReceipts).DeriveFields(isthmusTestConfig, blockHash, blockNumber.Uint64(), 0, baseFee, nil, txs)
1012+
if err != nil {
1013+
t.Fatalf("DeriveFields(...) = %v, want <nil>", err)
1014+
}
1015+
diffReceipts(t, receipts, derivedReceipts)
1016+
}
1017+
8901018
func diffReceipts(t *testing.T, receipts, derivedReceipts []*Receipt) {
8911019
// Check diff of receipts against derivedReceipts.
8921020
r1, err := json.MarshalIndent(receipts, "", " ")

0 commit comments

Comments
 (0)