From 9229ba44804b2495876e7e234d2f73f073ac31b0 Mon Sep 17 00:00:00 2001 From: blockchaindevsh Date: Wed, 18 Mar 2026 21:41:48 +0800 Subject: [PATCH 1/2] feat(op-reth): add Soul Gas Token (SGT) support with acceptance tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add SGT support to the Rust OP Stack implementation and execution-client agnostic acceptance tests, enabling alternative gas payment using Soul Gas Tokens from a predeploy contract. Rust changes (under rust/): - Patch revm/alloy-evm with forked SGT-enabled versions (revm 34.0.0 based) - Add SgtConfig type and SGT constants (contract addr, balance slot) - Parse SGT config from genesis config.optimism (soulGasTokenTime, isSoulBackedByNative) — same format as op-geth - Thread SGT config through OpBlockExecutionCtx → EVM via configure_sgt() - Add OpHardforks trait methods: is_sgt_active_at_timestamp(), is_sgt_native_backed() - RPC: dual HTTP server support (--http.sgt.addr/port) returning native+SGT combined balance on eth_getBalance - TxPool: check native+SGT combined balance for tx validation (only when SGT is active, zero overhead on non-SGT chains) - CLI: --http.sgt.addr and --http.sgt.port arguments Acceptance tests (20/20 passing): - Deposit: basic, accumulation, multi-account independence, native backing - Gas payment: native-only, full SGT, full SGT+native, partial SGT - Gas payment + tx value: full SGT, partial SGT - Insufficient funds: SGT-only, native-only, combined, with tx value - Balance invariant: preSGT + preNative == postSGT + postNative + gasCost + txValue - Genesis: contract deployment, balanceOf returns zero - Smoke: native transfer, balance query Gas payment priority (enforced by forked revm handler): - Deduction: SGT first → native second - Refund: native first → SGT second No MSRV bump (stays 1.88), no reth version bump (stays v1.11.0). Co-Authored-By: Claude Opus 4.6 (1M context) --- op-acceptance-tests/acceptance-tests.yaml | 6 + op-acceptance-tests/justfile | 3 + op-acceptance-tests/tests/sgt/deposit_test.go | 125 +++++++ .../tests/sgt/gas_payment_test.go | 347 ++++++++++++++++++ op-acceptance-tests/tests/sgt/genesis_test.go | 50 +++ op-acceptance-tests/tests/sgt/helper.go | 158 ++++++++ op-acceptance-tests/tests/sgt/init_test.go | 19 + .../tests/sgt/sgt_smoke_test.go | 62 ++++ op-devstack/presets/soulgas.go | 14 + op-devstack/sysgo/deployer.go | 15 + rust/Cargo.lock | 45 +-- rust/Cargo.toml | 16 + rust/alloy-op-evm/src/block/mod.rs | 7 +- rust/alloy-op-evm/src/lib.rs | 18 + rust/alloy-op-evm/src/sgt.rs | 52 +++ rust/alloy-op-hardforks/src/lib.rs | 15 + .../crates/proof/executor/src/builder/core.rs | 1 + rust/op-reth/crates/chainspec/src/base.rs | 2 + .../crates/chainspec/src/base_sepolia.rs | 2 + rust/op-reth/crates/chainspec/src/basefee.rs | 2 + rust/op-reth/crates/chainspec/src/dev.rs | 2 + rust/op-reth/crates/chainspec/src/lib.rs | 56 ++- rust/op-reth/crates/chainspec/src/op.rs | 2 + .../crates/chainspec/src/op_sepolia.rs | 2 + .../crates/consensus/src/validation/mod.rs | 2 + rust/op-reth/crates/evm/Cargo.toml | 1 + rust/op-reth/crates/evm/src/lib.rs | 27 +- rust/op-reth/crates/node/Cargo.toml | 1 + rust/op-reth/crates/node/src/args.rs | 13 + rust/op-reth/crates/node/src/node.rs | 86 +++++ rust/op-reth/crates/rpc/Cargo.toml | 1 + rust/op-reth/crates/rpc/src/eth/mod.rs | 81 +++- rust/op-reth/crates/rpc/src/eth/sgt.rs | 21 ++ rust/op-reth/crates/rpc/src/lib.rs | 2 +- rust/op-reth/crates/txpool/src/validator.rs | 44 ++- .../examples/custom-node/src/evm/config.rs | 2 + 36 files changed, 1261 insertions(+), 41 deletions(-) create mode 100644 op-acceptance-tests/tests/sgt/deposit_test.go create mode 100644 op-acceptance-tests/tests/sgt/gas_payment_test.go create mode 100644 op-acceptance-tests/tests/sgt/genesis_test.go create mode 100644 op-acceptance-tests/tests/sgt/helper.go create mode 100644 op-acceptance-tests/tests/sgt/init_test.go create mode 100644 op-acceptance-tests/tests/sgt/sgt_smoke_test.go create mode 100644 op-devstack/presets/soulgas.go create mode 100644 rust/alloy-op-evm/src/sgt.rs create mode 100644 rust/op-reth/crates/rpc/src/eth/sgt.rs diff --git a/op-acceptance-tests/acceptance-tests.yaml b/op-acceptance-tests/acceptance-tests.yaml index f6f6c5237ec1b..75b15c3f5c15f 100644 --- a/op-acceptance-tests/acceptance-tests.yaml +++ b/op-acceptance-tests/acceptance-tests.yaml @@ -185,6 +185,12 @@ gates: - package: github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/custom_gas_token timeout: 10m + - id: sgt + description: "Soul Gas Token (SGT) tests." + tests: + - package: github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/sgt + timeout: 10m + - id: supernode description: "Supernode tests - tests for the op-supernode multi-chain consensus layer." tests: diff --git a/op-acceptance-tests/justfile b/op-acceptance-tests/justfile index 34faa2e14f808..fb523eab0af77 100644 --- a/op-acceptance-tests/justfile +++ b/op-acceptance-tests/justfile @@ -17,6 +17,9 @@ interop: cgt: @just acceptance-test "" cgt +sgt: + @just acceptance-test "" sgt + # Run acceptance tests with mise-managed binary # Usage: just acceptance-test [devnet] [gate] diff --git a/op-acceptance-tests/tests/sgt/deposit_test.go b/op-acceptance-tests/tests/sgt/deposit_test.go new file mode 100644 index 0000000000000..f8a6701cc80c0 --- /dev/null +++ b/op-acceptance-tests/tests/sgt/deposit_test.go @@ -0,0 +1,125 @@ +package sgt + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txplan" +) + +// TestSGT_DepositVia_BatchDepositForAll verifies that SGT can be deposited +// using the batchDepositForAll function and the balance is correctly reflected. +func TestSGT_DepositFunctionSuccess(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + // Create a funder with native balance + funder := sys.FunderL2.NewFundedEOA(eth.Ether(1)) + recipient := sys.Wallet.NewEOA(sys.L2EL) + + // Check recipient starts with zero SGT + preSGT := h.GetSGTBalance(recipient.Address()) + t.Require().Equal(0, preSGT.Sign(), "recipient should start with zero SGT") + + // Deposit SGT to recipient + depositAmount := big.NewInt(10000) + h.DepositSGT(funder, recipient.Address(), depositAmount) + + // Verify SGT balance after deposit + postSGT := h.GetSGTBalance(recipient.Address()) + t.Require().Equal(0, depositAmount.Cmp(postSGT), + "SGT balance should equal deposit amount: got %s, want %s", postSGT, depositAmount) +} + +// TestSGT_DepositAccumulation verifies that multiple SGT deposits accumulate correctly. +func TestSGT_DepositAccumulation(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + funder := sys.FunderL2.NewFundedEOA(eth.Ether(1)) + recipient := sys.Wallet.NewEOA(sys.L2EL) + + firstDeposit := big.NewInt(5000) + secondDeposit := big.NewInt(3000) + + // First deposit + h.DepositSGT(funder, recipient.Address(), firstDeposit) + bal1 := h.GetSGTBalance(recipient.Address()) + t.Require().Equal(0, firstDeposit.Cmp(bal1), "after first deposit") + + // Second deposit + h.DepositSGT(funder, recipient.Address(), secondDeposit) + bal2 := h.GetSGTBalance(recipient.Address()) + expected := new(big.Int).Add(firstDeposit, secondDeposit) + t.Require().Equal(0, expected.Cmp(bal2), "after second deposit: got %s, want %s", bal2, expected) +} + +// TestSGT_MultiAccountIndependence verifies that SGT deposits to different +// accounts are independent and do not affect each other's balances. +func TestSGT_MultiAccountIndependence(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + funder := sys.FunderL2.NewFundedEOA(eth.Ether(1)) + alice := sys.Wallet.NewEOA(sys.L2EL) + bob := sys.Wallet.NewEOA(sys.L2EL) + + aliceDeposit := big.NewInt(7000) + bobDeposit := big.NewInt(3000) + + // Deposit different amounts to each account + h.DepositSGT(funder, alice.Address(), aliceDeposit) + h.DepositSGT(funder, bob.Address(), bobDeposit) + + // Verify each account has the correct balance + aliceBal := h.GetSGTBalance(alice.Address()) + bobBal := h.GetSGTBalance(bob.Address()) + + t.Require().Equal(0, aliceDeposit.Cmp(aliceBal), "Alice SGT balance mismatch") + t.Require().Equal(0, bobDeposit.Cmp(bobBal), "Bob SGT balance mismatch") + + // Deposit more to Alice; Bob should be unaffected + h.DepositSGT(funder, alice.Address(), aliceDeposit) + + aliceBal2 := h.GetSGTBalance(alice.Address()) + bobBal2 := h.GetSGTBalance(bob.Address()) + + expectedAlice := new(big.Int).Add(aliceDeposit, aliceDeposit) + t.Require().Equal(0, expectedAlice.Cmp(aliceBal2), "Alice SGT balance after second deposit") + t.Require().Equal(0, bobDeposit.Cmp(bobBal2), "Bob SGT balance should be unchanged") +} + +// TestSGT_DepositCreatesNativeBackedBalance verifies that depositing SGT also locks +// native ETH in the SGT contract (since nativeBacked=true in the preset). +func TestSGT_DepositNativeBackingVerification(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + funder := sys.FunderL2.NewFundedEOA(eth.Ether(1)) + + // Get SGT contract's native balance before deposit + preContractBalance := h.GetNativeBalance(sgtAddr) + + depositAmount := big.NewInt(100000) + calldata := encodeBatchDepositForAll(funder.Address(), depositAmount) + funder.Transact( + funder.Plan(), + txplan.WithTo(&sgtAddr), + txplan.WithEth(depositAmount), + txplan.WithData(calldata), + txplan.WithGasLimit(200000), + ) + + // The SGT contract should have received the native ETH + postContractBalance := h.GetNativeBalance(sgtAddr) + diff := new(big.Int).Sub(postContractBalance, preContractBalance) + t.Require().Equal(0, depositAmount.Cmp(diff), + "SGT contract native balance should increase by deposit amount: got %s, want %s", diff, depositAmount) +} diff --git a/op-acceptance-tests/tests/sgt/gas_payment_test.go b/op-acceptance-tests/tests/sgt/gas_payment_test.go new file mode 100644 index 0000000000000..af3f0e92a2354 --- /dev/null +++ b/op-acceptance-tests/tests/sgt/gas_payment_test.go @@ -0,0 +1,347 @@ +package sgt + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txplan" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// largeSGT is a value large enough to fully cover gas costs on devnet (including L1 data fees). +var largeSGT = new(big.Int).Mul(big.NewInt(10), big.NewInt(1e18)) // 10 ETH + +// largeNative is a value large enough to fully cover gas costs on devnet. +var largeNative = new(big.Int).Mul(big.NewInt(5), big.NewInt(1e18)) // 5 ETH + +// smallBalance is too small to cover gas by itself. +var smallBalance = big.NewInt(10_000) + +// TestSGT_NativaGasPaymentWithoutSGTSuccess verifies gas is paid entirely from native +// balance when the account has no SGT. +func TestSGT_NativaGasPaymentWithoutSGTSuccess(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + account := setupAccountWithBalances(t, h, big.NewInt(0), largeNative) + + preSGT := h.GetSGTBalance(account.Address()) + preNative := h.GetNativeBalance(account.Address()) + vaultBefore := h.CalcVaultBalance() + + receipt := executeTransfer(t, h, account, dummyAddr, big.NewInt(0)) + gasCost := CalcGasFee(receipt) + + postSGT := h.GetSGTBalance(account.Address()) + vaultAfter := h.CalcVaultBalance() + + // Vault increased by gas cost + vaultDiff := new(big.Int).Sub(vaultAfter, vaultBefore) + t.Require().Equal(0, vaultDiff.Cmp(gasCost), "vault balance diff should equal gas cost") + + // SGT should remain zero + t.Require().Equal(0, postSGT.Sign(), "SGT balance should remain zero") + + // Balance invariant + h.InvariantBalanceCheck(account.Address(), gasCost, big.NewInt(0), preSGT, preNative, postSGT, false) +} + +// TestSGT_FullSGTGasPaymentWithoutNativeBalanceSuccess verifies gas is paid entirely +// from SGT when the account has no native balance. +func TestSGT_FullSGTGasPaymentWithoutNativeBalanceSuccess(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + account := setupAccountWithBalances(t, h, largeSGT, big.NewInt(0)) + + preSGT := h.GetSGTBalance(account.Address()) + preNative := h.GetNativeBalance(account.Address()) + vaultBefore := h.CalcVaultBalance() + + receipt := executeTransfer(t, h, account, dummyAddr, big.NewInt(0)) + gasCost := CalcGasFee(receipt) + + postSGT := h.GetSGTBalance(account.Address()) + vaultAfter := h.CalcVaultBalance() + + // Vault increased by gas cost + vaultDiff := new(big.Int).Sub(vaultAfter, vaultBefore) + t.Require().Equal(0, vaultDiff.Cmp(gasCost), "vault balance diff should equal gas cost") + + // SGT should decrease by gas cost + expectedSGT := new(big.Int).Sub(preSGT, gasCost) + t.Require().Equal(0, expectedSGT.Cmp(postSGT), "SGT should be reduced by gas cost") + + h.InvariantBalanceCheck(account.Address(), gasCost, big.NewInt(0), preSGT, preNative, postSGT, true) +} + +// TestSGT_FullSGTGasPaymentWithNativeBalanceSuccess verifies that when both SGT and +// native balances are available, SGT is used first for gas payment. +func TestSGT_FullSGTGasPaymentWithNativeBalanceSuccess(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + account := setupAccountWithBalances(t, h, largeSGT, largeNative) + + preSGT := h.GetSGTBalance(account.Address()) + preNative := h.GetNativeBalance(account.Address()) + vaultBefore := h.CalcVaultBalance() + + receipt := executeTransfer(t, h, account, dummyAddr, big.NewInt(0)) + gasCost := CalcGasFee(receipt) + + postSGT := h.GetSGTBalance(account.Address()) + postNative := h.GetNativeBalance(account.Address()) + vaultAfter := h.CalcVaultBalance() + + // Vault increased by gas cost + vaultDiff := new(big.Int).Sub(vaultAfter, vaultBefore) + t.Require().Equal(0, vaultDiff.Cmp(gasCost), "vault balance diff should equal gas cost") + + // SGT should be used first (deduction: SGT first -> native second) + expectedSGT := new(big.Int).Sub(preSGT, gasCost) + t.Require().Equal(0, expectedSGT.Cmp(postSGT), "SGT should be reduced by gas cost") + + // Native should remain unchanged (SGT covers all gas) + t.Require().Equal(0, preNative.Cmp(postNative), "native should not change (SGT priority)") + + h.InvariantBalanceCheck(account.Address(), gasCost, big.NewInt(0), preSGT, preNative, postSGT, true) +} + +// TestSGT_PartialSGTGasPaymentSuccess verifies that when SGT balance is less than gas +// cost, SGT is fully spent and the remainder is paid from native balance. +func TestSGT_PartialSGTGasPaymentSuccess(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + account := setupAccountWithBalances(t, h, big.NewInt(1000), largeNative) + + preSGT := h.GetSGTBalance(account.Address()) + preNative := h.GetNativeBalance(account.Address()) + vaultBefore := h.CalcVaultBalance() + + receipt := executeTransfer(t, h, account, dummyAddr, big.NewInt(0)) + gasCost := CalcGasFee(receipt) + + postSGT := h.GetSGTBalance(account.Address()) + vaultAfter := h.CalcVaultBalance() + + // Vault increased by gas cost + vaultDiff := new(big.Int).Sub(vaultAfter, vaultBefore) + t.Require().Equal(0, vaultDiff.Cmp(gasCost), "vault balance diff should equal gas cost") + + // SGT should be fully spent + t.Require().Equal(0, postSGT.Sign(), "SGT should be fully spent in partial payment") + + h.InvariantBalanceCheck(account.Address(), gasCost, big.NewInt(0), preSGT, preNative, postSGT, true) +} + +// TestSGT_FullSGTGasPaymentAndNonZeroTxValueWithSufficientNativeBalanceSuccess verifies +// that SGT covers gas while tx value is paid from native balance. +func TestSGT_FullSGTGasPaymentAndNonZeroTxValueWithSufficientNativeBalanceSuccess(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + txValue := big.NewInt(10000) + account := setupAccountWithBalances(t, h, largeSGT, largeNative) + + preSGT := h.GetSGTBalance(account.Address()) + preNative := h.GetNativeBalance(account.Address()) + vaultBefore := h.CalcVaultBalance() + + receipt := executeTransfer(t, h, account, dummyAddr, txValue) + gasCost := CalcGasFee(receipt) + + postSGT := h.GetSGTBalance(account.Address()) + vaultAfter := h.CalcVaultBalance() + + // Vault increased by gas cost + vaultDiff := new(big.Int).Sub(vaultAfter, vaultBefore) + t.Require().Equal(0, vaultDiff.Cmp(gasCost), "vault balance diff should equal gas cost") + + // SGT used for gas + expectedSGT := new(big.Int).Sub(preSGT, gasCost) + t.Require().Equal(0, expectedSGT.Cmp(postSGT), "SGT should be reduced by gas cost") + + h.InvariantBalanceCheck(account.Address(), gasCost, txValue, preSGT, preNative, postSGT, true) +} + +// TestSGT_PartialSGTGasPaymentAndNonZeroTxValueWithSufficientNativeBalanceSuccess verifies +// partial SGT gas payment with a non-zero tx value. +func TestSGT_PartialSGTGasPaymentAndNonZeroTxValueWithSufficientNativeBalanceSuccess(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + txValue := big.NewInt(10000) + account := setupAccountWithBalances(t, h, big.NewInt(1000), largeNative) + + preSGT := h.GetSGTBalance(account.Address()) + preNative := h.GetNativeBalance(account.Address()) + vaultBefore := h.CalcVaultBalance() + + receipt := executeTransfer(t, h, account, dummyAddr, txValue) + gasCost := CalcGasFee(receipt) + + postSGT := h.GetSGTBalance(account.Address()) + vaultAfter := h.CalcVaultBalance() + + // Vault increased by gas cost + vaultDiff := new(big.Int).Sub(vaultAfter, vaultBefore) + t.Require().Equal(0, vaultDiff.Cmp(gasCost), "vault balance diff should equal gas cost") + + // SGT fully spent + t.Require().Equal(0, postSGT.Sign(), "SGT should be fully spent") + + h.InvariantBalanceCheck(account.Address(), gasCost, txValue, preSGT, preNative, postSGT, true) +} + +// TestSGT_FullSGTInsufficientGasPaymentFail verifies that a transaction fails when +// the account has only a small SGT balance and no native balance. +func TestSGT_FullSGTInsufficientGasPaymentFail(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + account := setupAccountWithBalances(t, h, new(big.Int).Set(smallBalance), big.NewInt(0)) + assertTransferFails(t, account, dummyAddr, big.NewInt(0)) +} + +// TestSGT_FullNativeInsufficientGasPaymentFail verifies that a transaction fails when +// the account has only a small native balance and no SGT balance. +func TestSGT_FullNativeInsufficientGasPaymentFail(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + account := setupAccountWithBalances(t, h, big.NewInt(0), new(big.Int).Set(smallBalance)) + assertTransferFails(t, account, dummyAddr, big.NewInt(0)) +} + +// TestSGT_PartialSGTInsufficientGasPaymentFail verifies that a transaction fails +// when the combined SGT and native balance is insufficient to cover gas. +func TestSGT_PartialSGTInsufficientGasPaymentFail(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + account := setupAccountWithBalances(t, h, new(big.Int).Set(smallBalance), new(big.Int).Set(smallBalance)) + assertTransferFails(t, account, dummyAddr, big.NewInt(0)) +} + +// TestSGT_FullSGTGasPaymentAndNonZeroTxValueWithInsufficientNativeBalanceFail verifies +// failure when SGT covers gas but native balance is insufficient to cover tx value. +func TestSGT_FullSGTGasPaymentAndNonZeroTxValueWithInsufficientNativeBalanceFail(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + account := setupAccountWithBalances(t, h, largeSGT, new(big.Int).Set(smallBalance)) + txValue := new(big.Int).Add(smallBalance, big.NewInt(1)) + assertTransferFails(t, account, dummyAddr, txValue) +} + +// TestSGT_PartialSGTGasPaymentAndNonZeroTxValueWithInsufficientNativeBalanceFail verifies +// failure when partial SGT covers some gas but native is insufficient for tx value after gas remainder. +func TestSGT_PartialSGTGasPaymentAndNonZeroTxValueWithInsufficientNativeBalanceFail(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + account := setupAccountWithBalances(t, h, new(big.Int).Set(smallBalance), largeNative) + txValue := new(big.Int).Sub(largeNative, smallBalance) + assertTransferFails(t, account, dummyAddr, txValue) +} + +// TestSGT_BalanceInvariant explicitly tests the balance invariant: +// preSGT + preNative == postSGT + postNative + gasCost + txValue +func TestSGT_BalanceInvariant(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + txValue := big.NewInt(42000) + account := setupAccountWithBalances(t, h, largeSGT, largeNative) + + preSGT := h.GetSGTBalance(account.Address()) + preNative := h.GetNativeBalance(account.Address()) + + receipt := executeTransfer(t, h, account, dummyAddr, txValue) + gasCost := CalcGasFee(receipt) + + postSGT := h.GetSGTBalance(account.Address()) + postNative := h.GetNativeBalance(account.Address()) + + preBal := new(big.Int).Add(preSGT, preNative) + postBal := new(big.Int).Add(postSGT, postNative) + postBal = postBal.Add(postBal, gasCost) + postBal = postBal.Add(postBal, txValue) + + t.Require().Equal(0, preBal.Cmp(postBal), + "balance invariant: pre(%s) != post+gas+value(%s)", preBal, postBal) +} + +// setupAccountWithBalances creates a fresh account with exact SGT and native balances. +func setupAccountWithBalances(t devtest.T, h *SgtHelper, sgtDeposit, nativeDeposit *big.Int) *dsl.EOA { + setupGas := new(big.Int).Mul(big.NewInt(1), big.NewInt(1e18)) // 1 ETH for setup gas + totalNeeded := new(big.Int).Add(sgtDeposit, nativeDeposit) + totalNeeded = totalNeeded.Add(totalNeeded, setupGas) + + funder := h.sys.FunderL2.NewFundedEOA(eth.WeiBig(totalNeeded)) + + // Create the test account (fresh, zero balances) + testAccount := h.sys.Wallet.NewEOA(h.sys.L2EL) + + // Deposit SGT if needed (funder deposits to testAccount via batchDepositForAll) + if sgtDeposit.Sign() > 0 { + h.DepositSGT(funder, testAccount.Address(), sgtDeposit) + } + + // Transfer native if needed + if nativeDeposit.Sign() > 0 { + funder.Transfer(testAccount.Address(), eth.WeiBig(nativeDeposit)) + } + + // Verify SGT balance + actualSGT := h.GetSGTBalance(testAccount.Address()) + t.Require().Equal(0, sgtDeposit.Cmp(actualSGT), + "SGT deposit mismatch: got %s, want %s", actualSGT, sgtDeposit) + + // Verify native balance + actualNative := h.GetNativeBalance(testAccount.Address()) + t.Require().Equal(0, nativeDeposit.Cmp(actualNative), + "native deposit mismatch: got %s, want %s", actualNative, nativeDeposit) + + return testAccount +} + +// executeTransfer sends a native transfer from the account to the target. +func executeTransfer(t devtest.T, h *SgtHelper, account *dsl.EOA, target common.Address, txValue *big.Int) *types.Receipt { + plannedTx := account.Transact( + account.PlanTransfer(target, eth.WeiBig(txValue)), + ) + receipt, err := plannedTx.Included.Get() + t.Require().NoError(err, "failed to get receipt") + t.Require().Equal(uint64(1), receipt.Status, "tx should succeed") + return receipt +} + +// assertTransferFails verifies that a transfer tx fails (e.g., insufficient funds). +func assertTransferFails(t devtest.T, account *dsl.EOA, target common.Address, txValue *big.Int) { + tx := txplan.NewPlannedTx( + account.PlanTransfer(target, eth.WeiBig(txValue)), + ) + _, err := tx.Success.Eval(t.Ctx()) + t.Require().Error(err, "transaction should fail due to insufficient funds") +} diff --git a/op-acceptance-tests/tests/sgt/genesis_test.go b/op-acceptance-tests/tests/sgt/genesis_test.go new file mode 100644 index 0000000000000..af2392a48d7f2 --- /dev/null +++ b/op-acceptance-tests/tests/sgt/genesis_test.go @@ -0,0 +1,50 @@ +package sgt + +import ( + "context" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/rpc" +) + +// TestSGT_GenesisDeployment verifies that the SGT contract is deployed at +// genesis at the expected predeploy address. +func TestSGT_GenesisDeployment(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + + l2Client := sys.L2EL.Escape().L2EthClient() + + ctx, cancel := context.WithTimeout(t.Ctx(), 20*time.Second) + defer cancel() + + // Check that there is code at the SGT predeploy address + code, err := l2Client.CodeAtHash(ctx, sgtAddr, sys.L2EL.BlockRefByNumber(0).Hash) + t.Require().NoError(err, "failed to get code at SGT address") + t.Require().Greater(len(code), 0, "SGT contract should have code at genesis") +} + +// TestSGT_GenesisBalanceOfReturnsZero verifies that a fresh account has zero +// SGT balance, confirming the contract is functional at genesis. +func TestSGT_GenesisBalanceOfReturnsZero(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + + l2Client := sys.L2EL.Escape().L2EthClient() + + ctx, cancel := context.WithTimeout(t.Ctx(), 20*time.Second) + defer cancel() + + // Query balanceOf for a random address at genesis; should return 0 + data, err := balanceOfFunc.EncodeArgs(dummyAddr) + t.Require().NoError(err, "failed to encode balanceOf args") + + out, err := l2Client.Call(ctx, ethereum.CallMsg{To: &sgtAddr, Data: data}, rpc.BlockNumber(0)) + t.Require().NoError(err, "balanceOf call should succeed at genesis block") + t.Require().NotNil(out, "balanceOf should return data") +} diff --git a/op-acceptance-tests/tests/sgt/helper.go b/op-acceptance-tests/tests/sgt/helper.go new file mode 100644 index 0000000000000..e31430b8619c6 --- /dev/null +++ b/op-acceptance-tests/tests/sgt/helper.go @@ -0,0 +1,158 @@ +package sgt + +import ( + "context" + "math/big" + "time" + + "github.com/ethereum-optimism/optimism/op-core/predeploys" + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/dsl" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/txplan" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + "github.com/lmittmann/w3" +) + +var ( + sgtAddr = predeploys.SoulGasTokenAddr + seqVault = predeploys.SequencerFeeVaultAddr + baseVault = predeploys.BaseFeeVaultAddr + l1Vault = predeploys.L1FeeVaultAddr + dummyAddr = common.Address{0xff, 0xff} + + balanceOfFunc = w3.MustNewFunc("balanceOf(address)", "uint256") + batchDepositSelector = common.FromHex("0x84e08810") // batchDepositForAll(address[],uint256) +) + +// SgtHelper provides utilities for SGT acceptance tests. +type SgtHelper struct { + t devtest.T + sys *presets.Minimal +} + +// NewSgtHelper creates a new SGT test helper. +func NewSgtHelper(t devtest.T, sys *presets.Minimal) *SgtHelper { + return &SgtHelper{ + t: t, + sys: sys, + } +} + +// GetSGTBalance returns the SGT balance of the given address. +func (h *SgtHelper) GetSGTBalance(addr common.Address) *big.Int { + l2Client := h.sys.L2EL.Escape().L2EthClient() + + ctx, cancel := context.WithTimeout(h.t.Ctx(), 20*time.Second) + defer cancel() + + data, err := balanceOfFunc.EncodeArgs(addr) + h.t.Require().NoError(err, "failed to encode balanceOf args") + + out, err := l2Client.Call(ctx, ethereum.CallMsg{To: &sgtAddr, Data: data}, rpc.LatestBlockNumber) + h.t.Require().NoError(err, "failed to call balanceOf on SGT contract") + + var balance *big.Int + err = balanceOfFunc.DecodeReturns(out, &balance) + h.t.Require().NoError(err, "failed to decode balanceOf return") + + return balance +} + +// GetNativeBalance returns the native (ETH) balance of the given address. +func (h *SgtHelper) GetNativeBalance(addr common.Address) *big.Int { + l2Client := h.sys.L2EL.Escape().L2EthClient() + + ctx, cancel := context.WithTimeout(h.t.Ctx(), 20*time.Second) + defer cancel() + + balance, err := l2Client.BalanceAt(ctx, addr, nil) + h.t.Require().NoError(err, "failed to get native balance") + + return balance +} + +// DepositSGT deposits SGT to the target address using batchDepositForAll via a funder EOA. +// The funder must have enough native balance to cover msg.value (= sgtAmount). +func (h *SgtHelper) DepositSGT(funder *dsl.EOA, target common.Address, sgtAmount *big.Int) { + calldata := encodeBatchDepositForAll(target, sgtAmount) + funder.Transact( + funder.Plan(), + txplan.WithTo(&sgtAddr), + txplan.WithEth(sgtAmount), + txplan.WithData(calldata), + txplan.WithGasLimit(200000), + ) +} + +// encodeBatchDepositForAll ABI-encodes batchDepositForAll(address[], uint256). +// Layout: selector(4) + offset(32) + value(32) + array_length(32) + address(32) +func encodeBatchDepositForAll(target common.Address, value *big.Int) []byte { + data := make([]byte, 0, 4+32*4) + data = append(data, batchDepositSelector...) + + // offset to dynamic array: 0x40 (64 bytes, after the value param) + offset := common.LeftPadBytes(big.NewInt(64).Bytes(), 32) + data = append(data, offset...) + + // value (uint256) + valBytes := common.LeftPadBytes(value.Bytes(), 32) + data = append(data, valBytes...) + + // array length = 1 + lenBytes := common.LeftPadBytes(big.NewInt(1).Bytes(), 32) + data = append(data, lenBytes...) + + // address element (left-padded to 32 bytes) + addrBytes := common.LeftPadBytes(target.Bytes(), 32) + data = append(data, addrBytes...) + + return data +} + +// CalcVaultBalance returns the sum of fee vault balances (sequencer + base + L1 fee vaults). +func (h *SgtHelper) CalcVaultBalance() *big.Int { + seqBal := h.GetNativeBalance(seqVault) + baseBal := h.GetNativeBalance(baseVault) + l1Bal := h.GetNativeBalance(l1Vault) + total := new(big.Int).Add(seqBal, baseBal) + return total.Add(total, l1Bal) +} + +// CalcGasFee computes the total gas cost from a receipt: effectiveGasPrice * gasUsed + L1Fee. +func CalcGasFee(receipt *types.Receipt) *big.Int { + fees := new(big.Int).Mul(receipt.EffectiveGasPrice, big.NewInt(int64(receipt.GasUsed))) + fees = fees.Add(fees, receipt.L1Fee) + return fees +} + +// InvariantBalanceCheck verifies: preSGT + preNative = postSGT + postNative + gasCost + txValue +func (h *SgtHelper) InvariantBalanceCheck( + addr common.Address, + gasCost *big.Int, + txValue *big.Int, + preSGT *big.Int, + preNative *big.Int, + postSGT *big.Int, + sgtShouldChange bool, +) { + if sgtShouldChange { + h.t.Require().NotEqual(0, preSGT.Cmp(postSGT), "SGT balance should have changed") + } else { + h.t.Require().Equal(0, preSGT.Cmp(postSGT), "SGT balance should not have changed") + } + + postNative := h.GetNativeBalance(addr) + preBal := new(big.Int).Add(preSGT, preNative) + postBal := new(big.Int).Add(postSGT, postNative) + postBal = postBal.Add(postBal, gasCost) + postBal = postBal.Add(postBal, txValue) + + h.t.Require().Equal(0, preBal.Cmp(postBal), + "balance invariant violated: pre(%s) != post(%s) + gas(%s) + value(%s)", + preBal, postBal, gasCost, txValue) +} diff --git a/op-acceptance-tests/tests/sgt/init_test.go b/op-acceptance-tests/tests/sgt/init_test.go new file mode 100644 index 0000000000000..0eb8b6acad634 --- /dev/null +++ b/op-acceptance-tests/tests/sgt/init_test.go @@ -0,0 +1,19 @@ +package sgt + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-devstack/presets" +) + +// TestMain creates the test-setups for SGT E2E tests. +// +// SGT (Soul Gas Token) is enabled via genesis deployment. +// The WithSGT preset configures the system to deploy the SGT contract +// at address 0x4200000000000000000000000000000000000800 during genesis. +func TestMain(m *testing.M) { + presets.DoMain(m, + presets.WithMinimal(), + presets.WithSGT(true, true), // Enable SGT with native backing + ) +} diff --git a/op-acceptance-tests/tests/sgt/sgt_smoke_test.go b/op-acceptance-tests/tests/sgt/sgt_smoke_test.go new file mode 100644 index 0000000000000..711bb9a8e46c0 --- /dev/null +++ b/op-acceptance-tests/tests/sgt/sgt_smoke_test.go @@ -0,0 +1,62 @@ +package sgt + +import ( + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/op-devstack/devtest" + "github.com/ethereum-optimism/optimism/op-devstack/presets" + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +// TestSGT_SmokeNativeTransfer verifies that a simple native ETH transfer works +// on an SGT-enabled chain. The sender pays gas (via SGT if it has SGT balance, +// otherwise via native) and the recipient receives the correct amount. +func TestSGT_SmokeNativeTransfer(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + sender := sys.FunderL2.NewFundedEOA(eth.Ether(1)) + recipient := sys.Wallet.NewEOA(sys.L2EL) + + amount := eth.OneHundredthEther + beforeR := recipient.GetBalance() + beforeS := sender.GetBalance() + + sender.Transfer(recipient.Address(), amount) + + // Recipient should have received exactly the amount + recipient.WaitForBalance(beforeR.Add(amount)) + afterR := recipient.GetBalance() + t.Require().Equal(beforeR.Add(amount), afterR, "recipient balance mismatch") + + // Sender should have lost at least the amount (plus gas) + afterS := sender.GetBalance() + t.Require().True(beforeS.Sub(afterS).Gt(amount), + "sender must have paid more than the transfer amount (gas was charged)") + + // SGT balance of sender should be 0 since we didn't deposit SGT + sgtBal := h.GetSGTBalance(sender.Address()) + t.Require().Equal(0, sgtBal.Sign(), "sender SGT balance should be zero without deposit") +} + +// TestSGT_SmokeBalanceQuery verifies that SGT balance can be queried for fresh accounts. +func TestSGT_SmokeBalanceQuery(gt *testing.T) { + t := devtest.SerialT(gt) + sys := presets.NewMinimal(t) + h := NewSgtHelper(t, sys) + + // A fresh EOA should have zero SGT balance + fresh := sys.Wallet.NewEOA(sys.L2EL) + bal := h.GetSGTBalance(fresh.Address()) + t.Require().Equal(0, bal.Sign(), "fresh account should have zero SGT") + + // After depositing, balance should be non-zero + funder := sys.FunderL2.NewFundedEOA(eth.Ether(1)) + depositAmount := big.NewInt(50000) + h.DepositSGT(funder, fresh.Address(), depositAmount) + + bal = h.GetSGTBalance(fresh.Address()) + t.Require().Equal(0, depositAmount.Cmp(bal), "SGT balance should match deposit") +} diff --git a/op-devstack/presets/soulgas.go b/op-devstack/presets/soulgas.go new file mode 100644 index 0000000000000..60faf9526cb24 --- /dev/null +++ b/op-devstack/presets/soulgas.go @@ -0,0 +1,14 @@ +package presets + +import ( + "github.com/ethereum-optimism/optimism/op-devstack/stack" + "github.com/ethereum-optimism/optimism/op-devstack/sysgo" +) + +// WithSGT configures the system to deploy the Soul Gas Token (SGT) contract. +// enabled controls whether SGT is deployed; nativeBacked controls whether SGT is 1:1 backed by native token. +func WithSGT(enabled bool, nativeBacked bool) stack.CommonOption { + return stack.MakeCommon(sysgo.WithDeployerOptions( + sysgo.WithSoulGasToken(enabled, nativeBacked), + )) +} diff --git a/op-devstack/sysgo/deployer.go b/op-devstack/sysgo/deployer.go index 94627337386f7..4db9ae1af4542 100644 --- a/op-devstack/sysgo/deployer.go +++ b/op-devstack/sysgo/deployer.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testreq" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -97,6 +98,20 @@ func WithDAFootprintGasScalar(scalar uint16, l2IDs ...stack.L2NetworkID) Deploye } } +// WithSoulGasToken enables Soul Gas Token deployment for L2 networks. +// If enabled is true, SGT contract will be deployed in genesis. +// nativeBacked determines if SGT is 1:1 backed by native token. +func WithSoulGasToken(enabled bool, nativeBacked bool) DeployerOption { + return func(p devtest.P, _ devkeys.Keys, builder intentbuilder.Builder) { + builder.WithGlobalOverride("deploySoulGasToken", enabled) + builder.WithGlobalOverride("isSoulBackedByNative", nativeBacked) + if enabled { + // Activate SGT at genesis (offset 0) + builder.WithGlobalOverride("soulGasTokenTimeOffset", hexutil.Uint64(0)) + } + } +} + func WithDeployerPipelineOption(opt DeployerPipelineOption) stack.Option[*Orchestrator] { return stack.BeforeDeploy(func(o *Orchestrator) { o.deployerPipelineOptions = append(o.deployerPipelineOptions, opt) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 86c0a2a92b3c7..1815186cd519d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -268,8 +268,7 @@ dependencies = [ [[package]] name = "alloy-evm" version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ccfe6d724ceabd5518350cfb34f17dd3a6c3cc33579eee94d98101d3a511ff" +source = "git+https://github.com/blockchaindevsh/evm?branch=op-fork-0.27.2#e92f642d25c37d3b57b53dc4183b824c286b9da9" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8024,8 +8023,7 @@ dependencies = [ [[package]] name = "op-revm" version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c92b75162c2ed1661849fa51683b11254a5b661798360a2c24be918edafd40" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "auto_impl", "revm", @@ -11368,6 +11366,7 @@ dependencies = [ "reth-storage-errors", "revm", "thiserror 2.0.18", + "tracing", ] [[package]] @@ -11468,6 +11467,7 @@ dependencies = [ "futures", "futures-util", "humantime", + "jsonrpsee", "op-alloy-consensus", "op-alloy-network", "op-alloy-rpc-types-engine", @@ -11592,6 +11592,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-json-rpc", + "alloy-op-evm", "alloy-op-hardforks", "alloy-primitives", "alloy-rlp", @@ -12723,8 +12724,7 @@ dependencies = [ [[package]] name = "revm" version = "34.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2aabdebaa535b3575231a88d72b642897ae8106cf6b0d12eafc6bfdf50abfc7" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "revm-bytecode", "revm-context", @@ -12742,8 +12742,7 @@ dependencies = [ [[package]] name = "revm-bytecode" version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d1e5c1eaa44d39d537f668bc5c3409dc01e5c8be954da6c83370bbdf006457" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "bitvec", "phf", @@ -12754,8 +12753,7 @@ dependencies = [ [[package]] name = "revm-context" version = "13.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "892ff3e6a566cf8d72ffb627fdced3becebbd9ba64089c25975b9b028af326a5" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "bitvec", "cfg-if", @@ -12771,8 +12769,7 @@ dependencies = [ [[package]] name = "revm-context-interface" version = "14.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f61cc6d23678c4840af895b19f8acfbbd546142ec8028b6526c53cc1c16c98" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -12787,8 +12784,7 @@ dependencies = [ [[package]] name = "revm-database" version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "529528d0b05fe646be86223032c3e77aa8b05caa2a35447d538c55965956a511" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "alloy-eips", "revm-bytecode", @@ -12801,8 +12797,7 @@ dependencies = [ [[package]] name = "revm-database-interface" version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7bf93ac5b91347c057610c0d96e923db8c62807e03f036762d03e981feddc1d" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "auto_impl", "either", @@ -12815,8 +12810,7 @@ dependencies = [ [[package]] name = "revm-handler" version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd0e43e815a85eded249df886c4badec869195e70cdd808a13cfca2794622d2" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "auto_impl", "derive-where", @@ -12834,8 +12828,7 @@ dependencies = [ [[package]] name = "revm-inspector" version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3ccad59db91ef93696536a0dbaf2f6f17cfe20d4d8843ae118edb7e97947ef" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "auto_impl", "either", @@ -12872,8 +12865,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "32.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11406408597bc249392d39295831c4b641b3a6f5c471a7c41104a7a1e3564c07" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -12885,8 +12877,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "32.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c1285c848d240678bf69cb0f6179ff5a4aee6fc8e921d89708087197a0aff3" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -12910,8 +12901,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "22.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba580c56a8ec824a64f8a1683577876c2e1dbe5247044199e9b881421ad5dcf9" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "alloy-primitives", "num_enum", @@ -12922,8 +12912,7 @@ dependencies = [ [[package]] name = "revm-state" version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311720d4f0f239b041375e7ddafdbd20032a33b7bae718562ea188e188ed9fd3" +source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" dependencies = [ "alloy-eip7928", "bitflags 2.10.0", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index a2e0e80b9d75b..c77daaefd9b92 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -670,3 +670,19 @@ op-alloy-rpc-types = { path = "op-alloy/crates/rpc-types" } op-alloy = { path = "op-alloy/crates/op-alloy" } # Duplicated by: alloy-evm (crates.io) alloy-op-hardforks = { path = "alloy-op-hardforks/" } + +# Forked revm with SGT support (based on revm 34.0.0) +revm = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-bytecode = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-database = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-state = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-primitives = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-interpreter = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-context = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-handler = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-precompile = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-inspector = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-context-interface = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +revm-database-interface = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +op-revm = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } +alloy-evm = { git = "https://github.com/blockchaindevsh/evm", branch = "op-fork-0.27.2" } diff --git a/rust/alloy-op-evm/src/block/mod.rs b/rust/alloy-op-evm/src/block/mod.rs index 1c1f48527677e..ab0c8a68a801e 100644 --- a/rust/alloy-op-evm/src/block/mod.rs +++ b/rust/alloy-op-evm/src/block/mod.rs @@ -55,6 +55,8 @@ pub struct OpBlockExecutionCtx { pub parent_beacon_block_root: Option, /// The block's extra data. pub extra_data: Bytes, + /// SGT configuration for this block. + pub sgt_config: crate::sgt::SgtConfig, } /// The result of executing an OP transaction. @@ -460,13 +462,16 @@ where fn create_executor<'a, DB, I>( &'a self, - evm: EvmF::Evm<&'a mut State, I>, + mut evm: EvmF::Evm<&'a mut State, I>, ctx: Self::ExecutionCtx<'a>, ) -> impl BlockExecutorFor<'a, Self, DB, I> where DB: Database + 'a, I: Inspector>> + 'a, { + // Configure SGT settings for OP Stack + evm.configure_sgt(ctx.sgt_config.enabled, ctx.sgt_config.is_native_backed); + OpBlockExecutor::new(evm, ctx, &self.spec, &self.receipt_builder) } } diff --git a/rust/alloy-op-evm/src/lib.rs b/rust/alloy-op-evm/src/lib.rs index b9880781d4c1a..e5a8f37330cc9 100644 --- a/rust/alloy-op-evm/src/lib.rs +++ b/rust/alloy-op-evm/src/lib.rs @@ -33,6 +33,9 @@ use revm::{ pub mod block; pub use block::{OpBlockExecutionCtx, OpBlockExecutor, OpBlockExecutorFactory}; +pub mod sgt; +pub use sgt::{SgtConfig, sgt_balance_slot, SGT_CONTRACT, SGT_BALANCE_SLOT}; + /// OP EVM implementation. /// /// This is a wrapper type around the `revm` evm with optional [`Inspector`] (tracing) @@ -149,6 +152,21 @@ where &mut self.inner.0.precompiles, ) } + + fn configure_sgt(&mut self, enabled: bool, is_native_backed: bool) { + OpEvm::configure_sgt(self, enabled, is_native_backed); + } +} + +impl OpEvm { + /// Set SGT configuration on the OP EVM context. + /// + /// This initializes the SGT configuration in L1BlockInfo before transaction execution. + /// The handler will read and preserve these values when fetching L1 block info from storage. + pub fn configure_sgt(&mut self, enabled: bool, is_native_backed: bool) { + self.ctx_mut().chain.sgt_enabled = enabled; + self.ctx_mut().chain.sgt_is_native_backed = is_native_backed; + } } /// Factory producing [`OpEvm`]s. diff --git a/rust/alloy-op-evm/src/sgt.rs b/rust/alloy-op-evm/src/sgt.rs new file mode 100644 index 0000000000000..c8929ad1199d7 --- /dev/null +++ b/rust/alloy-op-evm/src/sgt.rs @@ -0,0 +1,52 @@ +//! Soul Gas Token (SGT) support for OP Stack +//! +//! This module provides functionality for alternative gas payment using Soul Gas Tokens (SGT). +//! SGT enables transactions to pay gas fees using tokens from a predeploy contract instead of +//! native ETH. +//! +//! # Gas Payment Priority +//! +//! - **Deduction**: SGT first → Native balance second +//! - **Refund**: Native balance first → SGT second (reverse order) +//! +//! # Two Modes +//! +//! 1. **Native-backed**: SGT 1:1 with native token (backed by native balance) +//! 2. **Independent**: SGT is separate currency (minted/burned) + +use alloy_primitives::{Address, address}; + +// Re-export from op-revm to avoid duplication +pub use op_revm::sgt::sgt_balance_slot; + +/// SGT contract predeploy address +pub const SGT_CONTRACT: Address = + address!("4200000000000000000000000000000000000800"); + +/// Balance mapping base slot (must match Solidity contract) +/// +/// The SGT contract stores balances in a mapping at slot 51: +/// ```solidity +/// mapping(address => uint256) public balances; // slot 51 +/// ``` +pub const SGT_BALANCE_SLOT: u64 = 51; + +/// SGT configuration +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SgtConfig { + /// Whether SGT is enabled + pub enabled: bool, + + /// Whether SGT is backed 1:1 by native token + pub is_native_backed: bool, +} + +impl Default for SgtConfig { + fn default() -> Self { + Self { + enabled: false, + is_native_backed: true, + } + } +} + diff --git a/rust/alloy-op-hardforks/src/lib.rs b/rust/alloy-op-hardforks/src/lib.rs index c4dffbcde530b..20ae305f9c360 100644 --- a/rust/alloy-op-hardforks/src/lib.rs +++ b/rust/alloy-op-hardforks/src/lib.rs @@ -247,6 +247,21 @@ pub trait OpHardforks: EthereumHardforks { fn is_interop_active_at_timestamp(&self, timestamp: u64) -> bool { self.op_fork_activation(OpHardfork::Interop).active_at_timestamp(timestamp) } + + /// Returns `true` if Soul Gas Token (SGT) is active at given block timestamp. + /// + /// Default implementation returns `false`. Override in chain-specific implementations + /// (e.g., `OpChainSpec`) to enable SGT based on configuration. + fn is_sgt_active_at_timestamp(&self, _timestamp: u64) -> bool { + false + } + + /// Returns whether SGT is backed 1:1 by native token. + /// + /// Default implementation returns `true` (conservative default). + fn is_sgt_native_backed(&self) -> bool { + true + } } /// A type allowing to configure activation [`ForkCondition`]s for a given list of diff --git a/rust/kona/crates/proof/executor/src/builder/core.rs b/rust/kona/crates/proof/executor/src/builder/core.rs index dc141dee1e160..55590e2a7022c 100644 --- a/rust/kona/crates/proof/executor/src/builder/core.rs +++ b/rust/kona/crates/proof/executor/src/builder/core.rs @@ -259,6 +259,7 @@ where parent_beacon_block_root: attrs.payload_attributes.parent_beacon_block_root, // This field is unused for individual block building jobs. extra_data: Default::default(), + sgt_config: Default::default(), }; let executor = self.factory.create_executor(evm, ctx); diff --git a/rust/op-reth/crates/chainspec/src/base.rs b/rust/op-reth/crates/chainspec/src/base.rs index 7505e9f590cb3..a9370752e3360 100644 --- a/rust/op-reth/crates/chainspec/src/base.rs +++ b/rust/op-reth/crates/chainspec/src/base.rs @@ -35,6 +35,8 @@ pub static BASE_MAINNET: LazyLock> = LazyLock::new(|| { ), ..Default::default() }, + sgt_activation_timestamp: None, + sgt_is_native_backed: true, } .into() }); diff --git a/rust/op-reth/crates/chainspec/src/base_sepolia.rs b/rust/op-reth/crates/chainspec/src/base_sepolia.rs index 62984e675147d..672cfc8970c78 100644 --- a/rust/op-reth/crates/chainspec/src/base_sepolia.rs +++ b/rust/op-reth/crates/chainspec/src/base_sepolia.rs @@ -36,6 +36,8 @@ pub static BASE_SEPOLIA: LazyLock> = LazyLock::new(|| { prune_delete_limit: 10000, ..Default::default() }, + sgt_activation_timestamp: None, + sgt_is_native_backed: true, } .into() }); diff --git a/rust/op-reth/crates/chainspec/src/basefee.rs b/rust/op-reth/crates/chainspec/src/basefee.rs index 83ffca8ec03d6..d4880abffa8b2 100644 --- a/rust/op-reth/crates/chainspec/src/basefee.rs +++ b/rust/op-reth/crates/chainspec/src/basefee.rs @@ -100,6 +100,8 @@ mod tests { genesis_header: base_sepolia_spec.genesis_header, ..Default::default() }, + sgt_activation_timestamp: None, + sgt_is_native_backed: true, }) } diff --git a/rust/op-reth/crates/chainspec/src/dev.rs b/rust/op-reth/crates/chainspec/src/dev.rs index 5abf93aa5d245..34c2df3908322 100644 --- a/rust/op-reth/crates/chainspec/src/dev.rs +++ b/rust/op-reth/crates/chainspec/src/dev.rs @@ -29,6 +29,8 @@ pub static OP_DEV: LazyLock> = LazyLock::new(|| { base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), ..Default::default() }, + sgt_activation_timestamp: None, + sgt_is_native_backed: true, } .into() }); diff --git a/rust/op-reth/crates/chainspec/src/lib.rs b/rust/op-reth/crates/chainspec/src/lib.rs index cfa40ff5732aa..6ed855fe4bab5 100644 --- a/rust/op-reth/crates/chainspec/src/lib.rs +++ b/rust/op-reth/crates/chainspec/src/lib.rs @@ -63,7 +63,7 @@ use alloy_eips::eip7840::BlobParams; use alloy_genesis::Genesis; use alloy_hardforks::Hardfork; use alloy_primitives::{B256, U256}; -use derive_more::{Constructor, Deref, From, Into}; +use derive_more::{Deref, From}; use reth_chainspec::{ BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, DepositContract, DisplayHardforks, EthChainSpec, EthereumHardforks, ForkFilter, ForkId, Hardforks, Head, @@ -218,18 +218,32 @@ impl OpChainSpecBuilder { inner.genesis_header = SealedHeader::seal_slow(make_op_genesis_header(&inner.genesis, &inner.hardforks)); - OpChainSpec { inner } + OpChainSpec { + inner, + sgt_activation_timestamp: None, + sgt_is_native_backed: true, + } } } /// OP stack chain spec type. -#[derive(Debug, Clone, Deref, Into, Constructor, PartialEq, Eq)] +#[derive(Debug, Clone, Deref, PartialEq, Eq)] pub struct OpChainSpec { /// [`ChainSpec`]. + #[deref] pub inner: ChainSpec, + /// SGT activation timestamp from genesis config.optimism.soulGasTokenTime + pub sgt_activation_timestamp: Option, + /// Whether SGT is backed by native (from config.optimism.isSoulBackedByNative) + pub sgt_is_native_backed: bool, } impl OpChainSpec { + /// Constructs a new [`OpChainSpec`] from the given inner [`ChainSpec`]. + pub fn new(inner: ChainSpec) -> Self { + Self { inner, sgt_activation_timestamp: None, sgt_is_native_backed: true } + } + /// Converts the given [`Genesis`] into a [`OpChainSpec`]. pub fn from_genesis(genesis: Genesis) -> Self { genesis.into() @@ -335,6 +349,16 @@ impl OpHardforks for OpChainSpec { fn op_fork_activation(&self, fork: OpHardfork) -> ForkCondition { self.fork(fork) } + + fn is_sgt_active_at_timestamp(&self, timestamp: u64) -> bool { + self.sgt_activation_timestamp + .map(|activation| timestamp >= activation) + .unwrap_or(false) + } + + fn is_sgt_native_backed(&self) -> bool { + self.sgt_is_native_backed + } } impl From for OpChainSpec { @@ -422,6 +446,22 @@ impl From for OpChainSpec { let hardforks = ChainHardforks::new(ordered_hardforks); let genesis_header = SealedHeader::seal_slow(make_op_genesis_header(&genesis, &hardforks)); + // Parse SGT config from optimism extra field (same as op-geth genesis format) + let (sgt_activation_timestamp, sgt_is_native_backed) = genesis + .config + .extra_fields + .get("optimism") + .and_then(|v| v.as_object()) + .map(|obj| { + let timestamp = obj.get("soulGasTokenTime").and_then(|v| v.as_u64()); + let native_backed = obj + .get("isSoulBackedByNative") + .and_then(|v| v.as_bool()) + .unwrap_or(true); + (timestamp, native_backed) + }) + .unwrap_or((None, true)); + Self { inner: ChainSpec { chain: genesis.config.chain_id.into(), @@ -434,13 +474,21 @@ impl From for OpChainSpec { base_fee_params: optimism_genesis_info.base_fee_params, ..Default::default() }, + sgt_activation_timestamp, + sgt_is_native_backed, } } } impl From for OpChainSpec { fn from(value: ChainSpec) -> Self { - Self { inner: value } + Self { inner: value, sgt_activation_timestamp: None, sgt_is_native_backed: true } + } +} + +impl From for ChainSpec { + fn from(value: OpChainSpec) -> Self { + value.inner } } diff --git a/rust/op-reth/crates/chainspec/src/op.rs b/rust/op-reth/crates/chainspec/src/op.rs index 7b463aecf5cf7..8b3b190f4f862 100644 --- a/rust/op-reth/crates/chainspec/src/op.rs +++ b/rust/op-reth/crates/chainspec/src/op.rs @@ -36,6 +36,8 @@ pub static OP_MAINNET: LazyLock> = LazyLock::new(|| { prune_delete_limit: 10000, ..Default::default() }, + sgt_activation_timestamp: None, + sgt_is_native_backed: true, } .into() }); diff --git a/rust/op-reth/crates/chainspec/src/op_sepolia.rs b/rust/op-reth/crates/chainspec/src/op_sepolia.rs index d792fbe3004b2..5340330506b26 100644 --- a/rust/op-reth/crates/chainspec/src/op_sepolia.rs +++ b/rust/op-reth/crates/chainspec/src/op_sepolia.rs @@ -34,6 +34,8 @@ pub static OP_SEPOLIA: LazyLock> = LazyLock::new(|| { prune_delete_limit: 10000, ..Default::default() }, + sgt_activation_timestamp: None, + sgt_is_native_backed: true, } .into() }); diff --git a/rust/op-reth/crates/consensus/src/validation/mod.rs b/rust/op-reth/crates/consensus/src/validation/mod.rs index 10861f5b9c02d..7025066adc207 100644 --- a/rust/op-reth/crates/consensus/src/validation/mod.rs +++ b/rust/op-reth/crates/consensus/src/validation/mod.rs @@ -237,6 +237,8 @@ mod tests { prune_delete_limit: 10000, ..Default::default() }, + sgt_activation_timestamp: None, + sgt_is_native_backed: true, }) } diff --git a/rust/op-reth/crates/evm/Cargo.toml b/rust/op-reth/crates/evm/Cargo.toml index cb8f589c1ef20..a2d75d6b768e4 100644 --- a/rust/op-reth/crates/evm/Cargo.toml +++ b/rust/op-reth/crates/evm/Cargo.toml @@ -42,6 +42,7 @@ op-revm.workspace = true # misc thiserror.workspace = true +tracing.workspace = true [dev-dependencies] reth-evm = { workspace = true, features = ["test-utils"] } diff --git a/rust/op-reth/crates/evm/src/lib.rs b/rust/op-reth/crates/evm/src/lib.rs index 5ac1adaa35d38..4e97027da70f5 100644 --- a/rust/op-reth/crates/evm/src/lib.rs +++ b/rust/op-reth/crates/evm/src/lib.rs @@ -22,6 +22,7 @@ use reth_chainspec::EthChainSpec; use reth_evm::{ ConfigureEvm, EvmEnv, TransactionEnv, eth::NextEvmEnvAttributes, precompiles::PrecompilesMap, }; +use tracing::debug; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::{DepositReceipt, OpPrimitives}; @@ -120,6 +121,19 @@ where pub const fn chain_spec(&self) -> &Arc { self.executor_factory.spec() } + + /// Extract SGT configuration for a given timestamp. + fn extract_sgt_config(&self, timestamp: u64) -> alloy_op_evm::sgt::SgtConfig { + let chain_spec = self.chain_spec(); + let enabled = chain_spec.is_sgt_active_at_timestamp(timestamp); + let is_native_backed = chain_spec.is_sgt_native_backed(); + + if enabled { + debug!(target: "evm", timestamp, is_native_backed, "SGT enabled for block"); + } + + alloy_op_evm::sgt::SgtConfig { enabled, is_native_backed } + } } impl ConfigureEvm for OpEvmConfig @@ -186,10 +200,14 @@ where &self, block: &'_ SealedBlock, ) -> Result { + let sgt_config = + self.extract_sgt_config(block.header().timestamp()); + Ok(OpBlockExecutionCtx { parent_hash: block.header().parent_hash(), parent_beacon_block_root: block.header().parent_beacon_block_root(), extra_data: block.header().extra_data().clone(), + sgt_config, }) } @@ -198,10 +216,13 @@ where parent: &SealedHeader, attributes: Self::NextBlockEnvCtx, ) -> Result { + let sgt_config = self.extract_sgt_config(attributes.timestamp); + Ok(OpBlockExecutionCtx { parent_hash: parent.hash(), parent_beacon_block_root: attributes.parent_beacon_block_root, extra_data: attributes.extra_data, + sgt_config, }) } } @@ -263,10 +284,14 @@ where &self, payload: &'a OpExecutionData, ) -> Result, Self::Error> { + let sgt_config = + self.extract_sgt_config(payload.payload.timestamp()); + Ok(OpBlockExecutionCtx { parent_hash: payload.parent_hash(), parent_beacon_block_root: payload.sidecar.parent_beacon_block_root(), extra_data: payload.payload.as_v1().extra_data.clone(), + sgt_config, }) } @@ -337,7 +362,7 @@ mod tests { // Use the `OpEvmConfig` to create the `cfg_env` and `block_env` based on the ChainSpec, // Header, and total difficulty let EvmEnv { cfg_env, .. } = - OpEvmConfig::optimism(Arc::new(OpChainSpec { inner: chain_spec.clone() })) + OpEvmConfig::optimism(Arc::new(OpChainSpec::new(chain_spec.clone()))) .evm_env(&header) .unwrap(); diff --git a/rust/op-reth/crates/node/Cargo.toml b/rust/op-reth/crates/node/Cargo.toml index ca8684a9969eb..8c5eff75c2817 100644 --- a/rust/op-reth/crates/node/Cargo.toml +++ b/rust/op-reth/crates/node/Cargo.toml @@ -80,6 +80,7 @@ url.workspace = true humantime.workspace = true futures-util.workspace = true tracing.workspace = true +jsonrpsee = { workspace = true, features = ["server"] } # test-utils dependencies reth-e2e-test-utils = { workspace = true, optional = true } diff --git a/rust/op-reth/crates/node/src/args.rs b/rust/op-reth/crates/node/src/args.rs index 4f6e1cc84b58b..960305c065523 100644 --- a/rust/op-reth/crates/node/src/args.rs +++ b/rust/op-reth/crates/node/src/args.rs @@ -144,6 +144,17 @@ pub struct RollupArgs { default_value_t = 0 )] pub proofs_history_verification_interval: u64, + + /// HTTP server address for SGT-enabled eth_getBalance endpoint + /// + /// When set, starts a second HTTP RPC server that returns native + SGT + /// combined balance for eth_getBalance calls. + #[arg(long = "http.sgt.addr", value_name = "SGT_HTTP_ADDR")] + pub http_sgt_addr: Option, + + /// HTTP server port for SGT-enabled eth_getBalance endpoint + #[arg(long = "http.sgt.port", value_name = "SGT_HTTP_PORT", default_value_t = 8546)] + pub http_sgt_port: u16, } impl Default for RollupArgs { @@ -166,6 +177,8 @@ impl Default for RollupArgs { proofs_history_window: 1_296_000, proofs_history_prune_interval: Duration::from_secs(15), proofs_history_verification_interval: 0, + http_sgt_addr: None, + http_sgt_port: 8546, } } } diff --git a/rust/op-reth/crates/node/src/node.rs b/rust/op-reth/crates/node/src/node.rs index cbcd898f40931..6c397408529ea 100644 --- a/rust/op-reth/crates/node/src/node.rs +++ b/rust/op-reth/crates/node/src/node.rs @@ -195,6 +195,7 @@ impl OpNode { .with_historical_rpc(self.args.historical_rpc.clone()) .with_flashblocks(self.args.flashblocks_url.clone()) .with_flashblock_consensus(self.args.flashblock_consensus) + .with_sgt_http(self.args.http_sgt_addr.clone(), self.args.http_sgt_port) } /// Instantiates the [`ProviderFactoryBuilder`] for an opstack node. @@ -323,6 +324,10 @@ pub struct OpAddOns< /// Enable transaction conditionals. enable_tx_conditional: bool, min_suggested_priority_fee: u64, + /// HTTP server address for SGT-enabled eth_getBalance endpoint + http_sgt_addr: Option, + /// HTTP server port for SGT-enabled eth_getBalance endpoint + http_sgt_port: u16, } impl OpAddOns @@ -341,6 +346,8 @@ where historical_rpc: Option, enable_tx_conditional: bool, min_suggested_priority_fee: u64, + http_sgt_addr: Option, + http_sgt_port: u16, ) -> Self { Self { rpc_add_ons, @@ -351,6 +358,8 @@ where historical_rpc, enable_tx_conditional, min_suggested_priority_fee, + http_sgt_addr, + http_sgt_port, } } } @@ -402,6 +411,8 @@ where historical_rpc, enable_tx_conditional, min_suggested_priority_fee, + http_sgt_addr, + http_sgt_port, .. } = self; OpAddOns::new( @@ -413,6 +424,8 @@ where historical_rpc, enable_tx_conditional, min_suggested_priority_fee, + http_sgt_addr, + http_sgt_port, ) } @@ -430,6 +443,8 @@ where enable_tx_conditional, min_suggested_priority_fee, historical_rpc, + http_sgt_addr, + http_sgt_port, .. } = self; OpAddOns::new( @@ -441,6 +456,8 @@ where historical_rpc, enable_tx_conditional, min_suggested_priority_fee, + http_sgt_addr, + http_sgt_port, ) } @@ -461,6 +478,8 @@ where enable_tx_conditional, min_suggested_priority_fee, historical_rpc, + http_sgt_addr, + http_sgt_port, .. } = self; OpAddOns::new( @@ -472,6 +491,8 @@ where historical_rpc, enable_tx_conditional, min_suggested_priority_fee, + http_sgt_addr, + http_sgt_port, ) } @@ -515,6 +536,7 @@ where Pool: TransactionPool, >, EthB: EthApiBuilder, + EthB::EthApi: reth_optimism_rpc::EthApiWithSgtMode, PVB: Send, EB: EngineApiBuilder, EVB: EngineValidatorBuilder, @@ -535,6 +557,8 @@ where sequencer_headers, enable_tx_conditional, historical_rpc, + http_sgt_addr, + http_sgt_port, .. } = self; @@ -585,6 +609,8 @@ where ctx.node.provider().clone(), ); + let sgt_config = http_sgt_addr.map(|addr| (addr, http_sgt_port)); + rpc_add_ons .launch_add_ons_with(ctx, move |container| { let reth_node_builder::rpc::RpcModuleContainer { modules, auth_module, registry } = @@ -619,6 +645,43 @@ where )?; } + // Spawn SGT HTTP server if configured + if let Some((sgt_addr, sgt_port)) = sgt_config { + use reth_rpc_api::EthApiServer; + use jsonrpsee::RpcModule; + use reth_optimism_rpc::EthApiWithSgtMode; + + info!(target: "reth::cli", sgt_addr = %sgt_addr, sgt_port = %sgt_port, + "Starting SGT HTTP server on separate port"); + + let socket: std::net::SocketAddr = format!("{}:{}", sgt_addr, sgt_port) + .parse() + .map_err(|e| eyre::eyre!("Invalid SGT socket address: {}", e))?; + + let standard_eth_api = registry.eth_api(); + let sgt_eth_api = standard_eth_api.clone_with_sgt_mode(true); + + let mut module = RpcModule::new(()); + module.merge(sgt_eth_api.into_rpc()) + .map_err(|e| eyre::eyre!("Failed to merge SGT eth RPC methods: {}", e))?; + + let socket_for_log = socket; + tokio::spawn(async move { + match jsonrpsee::server::ServerBuilder::default().build(socket).await { + Ok(server) => { + info!(target: "reth::cli", %socket_for_log, "SGT HTTP RPC server started"); + let handle = server.start(module); + handle.stopped().await; + info!(target: "reth::cli", "SGT HTTP RPC server stopped"); + } + Err(e) => { + tracing::error!(target: "reth::cli", %socket_for_log, error = %e, + "Failed to start SGT HTTP server"); + } + } + }); + } + Ok(()) }) .await @@ -644,6 +707,7 @@ where >, <::Pool as TransactionPool>::Transaction: OpPooledTx, EthB: EthApiBuilder, + EthB::EthApi: reth_optimism_rpc::EthApiWithSgtMode, PVB: PayloadValidatorBuilder, EB: EngineApiBuilder, EVB: EngineValidatorBuilder, @@ -662,6 +726,7 @@ impl EngineValidatorAddOn where N: FullNodeComponents, EthB: EthApiBuilder, + EthB::EthApi: reth_optimism_rpc::EthApiWithSgtMode, PVB: Send, EB: EngineApiBuilder, EVB: EngineValidatorBuilder, @@ -703,6 +768,10 @@ pub struct OpAddOnsBuilder { flashblocks_url: Option, /// Enable flashblock consensus client to drive chain forward. flashblock_consensus: bool, + /// HTTP server address for SGT-enabled eth_getBalance endpoint + http_sgt_addr: Option, + /// HTTP server port for SGT-enabled eth_getBalance endpoint + http_sgt_port: u16, } impl Default for OpAddOnsBuilder { @@ -720,6 +789,8 @@ impl Default for OpAddOnsBuilder { tokio_runtime: None, flashblocks_url: None, flashblock_consensus: false, + http_sgt_addr: None, + http_sgt_port: 8546, } } } @@ -789,6 +860,8 @@ impl OpAddOnsBuilder { _nt, flashblocks_url, flashblock_consensus, + http_sgt_addr, + http_sgt_port, .. } = self; OpAddOnsBuilder { @@ -804,6 +877,8 @@ impl OpAddOnsBuilder { tokio_runtime, flashblocks_url, flashblock_consensus, + http_sgt_addr, + http_sgt_port, } } @@ -818,6 +893,13 @@ impl OpAddOnsBuilder { self.flashblock_consensus = flashblock_consensus; self } + + /// Configure the SGT HTTP server address and port. + pub fn with_sgt_http(mut self, addr: Option, port: u16) -> Self { + self.http_sgt_addr = addr; + self.http_sgt_port = port; + self + } } impl OpAddOnsBuilder { @@ -844,6 +926,8 @@ impl OpAddOnsBuilder { tokio_runtime, flashblocks_url, flashblock_consensus, + http_sgt_addr, + http_sgt_port, .. } = self; @@ -868,6 +952,8 @@ impl OpAddOnsBuilder { historical_rpc, enable_tx_conditional, min_suggested_priority_fee, + http_sgt_addr, + http_sgt_port, ) } } diff --git a/rust/op-reth/crates/rpc/Cargo.toml b/rust/op-reth/crates/rpc/Cargo.toml index d807407f2e6f1..ecc05436e139b 100644 --- a/rust/op-reth/crates/rpc/Cargo.toml +++ b/rust/op-reth/crates/rpc/Cargo.toml @@ -44,6 +44,7 @@ reth-optimism-forks.workspace = true reth-optimism-trie.workspace = true # ethereum +alloy-op-evm.workspace = true alloy-eips.workspace = true alloy-json-rpc.workspace = true alloy-primitives.workspace = true diff --git a/rust/op-reth/crates/rpc/src/eth/mod.rs b/rust/op-reth/crates/rpc/src/eth/mod.rs index d53b11d8bf0d9..0e50d6bea2cc2 100644 --- a/rust/op-reth/crates/rpc/src/eth/mod.rs +++ b/rust/op-reth/crates/rpc/src/eth/mod.rs @@ -3,6 +3,7 @@ pub mod ext; pub mod proofs; pub mod receipt; +pub mod sgt; pub mod transaction; mod block; @@ -34,7 +35,7 @@ use reth_optimism_flashblocks::{ use reth_primitives_traits::NodePrimitives; use reth_rpc::eth::core::EthApiInner; use reth_rpc_eth_api::{ - EthApiTypes, FromEvmError, FullEthApiServer, RpcConvert, RpcConverter, RpcNodeCore, + EthApiTypes, FromEthApiError, FromEvmError, FullEthApiServer, RpcConvert, RpcConverter, RpcNodeCore, RpcNodeCoreExt, RpcTypes, helpers::{ EthApiSpec, EthFees, EthState, LoadFee, LoadPendingBlock, LoadState, SpawnBlocking, Trace, @@ -78,11 +79,30 @@ pub type EthApiNodeBackend = EthApiInner; pub struct OpEthApi { /// Gateway to node's core components. inner: Arc>, + /// SGT mode flag (per-instance, not shared). + /// When true, eth_getBalance returns native + SGT combined balance. + sgt_mode: bool, } impl Clone for OpEthApi { fn clone(&self) -> Self { - Self { inner: self.inner.clone() } + Self { inner: self.inner.clone(), sgt_mode: self.sgt_mode } + } +} + +/// Trait for creating a clone of an EthApi with modified SGT mode. +/// +/// This trait enables type-safe cloning of EthApi implementations with different +/// SGT mode configurations, which is necessary for running dual HTTP servers where +/// one returns native balance only and the other returns native + SGT combined. +pub trait EthApiWithSgtMode: Clone { + /// Creates a clone of this API with the specified SGT mode. + fn clone_with_sgt_mode(&self, sgt_mode: bool) -> Self; +} + +impl EthApiWithSgtMode for OpEthApi { + fn clone_with_sgt_mode(&self, sgt_mode: bool) -> Self { + self.with_sgt_mode_changed(sgt_mode) } } @@ -93,6 +113,7 @@ impl OpEthApi { sequencer_client: Option, min_suggested_priority_fee: U256, flashblocks: Option>, + sgt_mode: bool, ) -> Self { let inner = Arc::new(OpEthApiInner { eth_api, @@ -100,7 +121,12 @@ impl OpEthApi { min_suggested_priority_fee, flashblocks, }); - Self { inner } + Self { inner, sgt_mode } + } + + /// Returns whether SGT mode is enabled. + pub fn sgt_mode(&self) -> bool { + self.sgt_mode } /// Build a [`OpEthApi`] using [`OpEthApiBuilder`]. @@ -112,11 +138,17 @@ impl OpEthApi { pub fn eth_api(&self) -> &EthApiNodeBackend { self.inner.eth_api() } + /// Returns the configured sequencer client, if any. pub fn sequencer_client(&self) -> Option<&SequencerClient> { self.inner.sequencer_client() } + /// Creates a new `OpEthApi` instance sharing the same underlying components but with different sgt_mode. + pub fn with_sgt_mode_changed(&self, sgt_mode: bool) -> Self { + Self { inner: self.inner.clone(), sgt_mode } + } + /// Returns a cloned pending block receiver, if any. pub fn pending_block_rx(&self) -> Option> { self.inner.flashblocks.as_ref().map(|f| f.pending_block_rx.clone()) @@ -368,6 +400,37 @@ where fn max_proof_window(&self) -> u64 { self.inner.eth_api.eth_proof_window() } + + /// Returns balance of given account, at given blocknumber. + /// + /// If SGT mode is enabled, returns native + SGT combined balance. + /// Otherwise returns native balance only. + fn balance( + &self, + address: alloy_primitives::Address, + block_id: Option, + ) -> impl std::future::Future> + Send { + let sgt_mode = self.sgt_mode(); + + self.spawn_blocking_io_fut(move |this| async move { + let state = this.state_at_block_id_or_latest(block_id).await?; + + let native_balance = state + .account_balance(&address) + .map_err(Self::Error::from_eth_err)? + .unwrap_or_default(); + + if !sgt_mode { + return Ok(native_balance); + } + + // SGT mode: return native + SGT combined + let sgt_balance = sgt::read_sgt_balance(&state, address) + .map_err(Self::Error::from_eth_err)?; + + Ok(native_balance.saturating_add(sgt_balance)) + }) + } } impl EthFees for OpEthApi @@ -456,6 +519,8 @@ pub struct OpEthApiBuilder { /// `newPayload` and `forkchoiceUpdated` calls, advancing the canonical chain state. /// Requires `flashblocks_url` to be set. flashblock_consensus: bool, + /// SGT mode flag. + sgt_mode: bool, /// Marker for network types. _nt: PhantomData, } @@ -468,6 +533,7 @@ impl Default for OpEthApiBuilder { min_suggested_priority_fee: 1_000_000, flashblocks_url: None, flashblock_consensus: false, + sgt_mode: false, _nt: PhantomData, } } @@ -482,6 +548,7 @@ impl OpEthApiBuilder { min_suggested_priority_fee: 1_000_000, flashblocks_url: None, flashblock_consensus: false, + sgt_mode: false, _nt: PhantomData, } } @@ -515,6 +582,12 @@ impl OpEthApiBuilder { self.flashblock_consensus = flashblock_consensus; self } + + /// With SGT mode enabled for eth_getBalance + pub const fn with_sgt_mode(mut self, sgt_mode: bool) -> Self { + self.sgt_mode = sgt_mode; + self + } } impl EthApiBuilder for OpEthApiBuilder @@ -550,6 +623,7 @@ where min_suggested_priority_fee, flashblocks_url, flashblock_consensus, + sgt_mode, .. } = self; let rpc_converter = @@ -611,6 +685,7 @@ where sequencer_client, U256::from(min_suggested_priority_fee), flashblocks, + sgt_mode, )) } } diff --git a/rust/op-reth/crates/rpc/src/eth/sgt.rs b/rust/op-reth/crates/rpc/src/eth/sgt.rs new file mode 100644 index 0000000000000..48dd055ad0cd1 --- /dev/null +++ b/rust/op-reth/crates/rpc/src/eth/sgt.rs @@ -0,0 +1,21 @@ +//! SGT (Soul Gas Token) balance reading utilities for RPC + +use alloy_op_evm::sgt::{sgt_balance_slot, SGT_CONTRACT}; +use alloy_primitives::{Address, U256}; +use reth_provider::{ProviderError, StateProvider}; + +/// Read SGT balance for an account from contract storage. +/// +/// Returns the SGT balance stored at slot 51 in the SGT predeploy contract. +pub fn read_sgt_balance( + state: &SP, + address: Address, +) -> Result +where + SP: StateProvider, +{ + let slot = sgt_balance_slot(address); + let value = state.storage(SGT_CONTRACT, slot.into())? + .unwrap_or_default(); + Ok(value) +} diff --git a/rust/op-reth/crates/rpc/src/lib.rs b/rust/op-reth/crates/rpc/src/lib.rs index 475322c3b3cf9..e32de6656c166 100644 --- a/rust/op-reth/crates/rpc/src/lib.rs +++ b/rust/op-reth/crates/rpc/src/lib.rs @@ -23,6 +23,6 @@ pub mod witness; pub use engine::OpEngineApiClient; pub use engine::{OP_ENGINE_CAPABILITIES, OpEngineApi, OpEngineApiServer}; pub use error::{OpEthApiError, OpInvalidTransactionError, SequencerClientError}; -pub use eth::{OpEthApi, OpEthApiBuilder, OpReceiptBuilder}; +pub use eth::{EthApiWithSgtMode, OpEthApi, OpEthApiBuilder, OpReceiptBuilder}; pub use metrics::{EthApiExtMetrics, SequencerMetrics}; pub use sequencer::SequencerClient; diff --git a/rust/op-reth/crates/txpool/src/validator.rs b/rust/op-reth/crates/txpool/src/validator.rs index c972cd95d6fd7..ab1f860753329 100644 --- a/rust/op-reth/crates/txpool/src/validator.rs +++ b/rust/op-reth/crates/txpool/src/validator.rs @@ -10,7 +10,7 @@ use reth_primitives_traits::{ Block, BlockBody, BlockTy, GotExpected, SealedBlock, transaction::error::InvalidTransactionError, }; -use reth_storage_api::{AccountInfoReader, BlockReaderIdExt, StateProviderFactory}; +use reth_storage_api::{AccountInfoReader, BlockReaderIdExt, StateProvider, StateProviderFactory}; use reth_transaction_pool::{ EthPoolTransaction, EthTransactionValidator, TransactionOrigin, TransactionValidationOutcome, TransactionValidator, error::InvalidPoolTransactionError, @@ -251,12 +251,19 @@ where }; let cost = valid_tx.transaction().cost().saturating_add(cost_addition); + // Check combined balance (native + SGT) only when SGT is active + let effective_balance = if self.chain_spec().is_sgt_active_at_timestamp(self.block_timestamp()) { + balance.saturating_add(self.read_sgt_balance(valid_tx.transaction().sender())) + } else { + balance + }; + // Checks for max cost - if cost > balance { + if cost > effective_balance { return TransactionValidationOutcome::Invalid( valid_tx.into_transaction(), InvalidTransactionError::InsufficientFunds( - GotExpected { got: balance, expected: cost }.into(), + GotExpected { got: effective_balance, expected: cost }.into(), ) .into(), ); @@ -274,6 +281,37 @@ where outcome } + /// Reads the SGT balance for an account. + /// + /// Returns the SGT balance from storage, or 0 if unavailable. + fn read_sgt_balance(&self, address: alloy_primitives::Address) -> alloy_primitives::U256 + where + Client: StateProviderFactory, + { + use alloy_primitives::{address, keccak256, U256}; + + // Calculate storage slot for account's SGT balance + // Formula: keccak256(abi.encode(account, 51)) + let mut data = [0u8; 64]; + data[12..32].copy_from_slice(address.as_slice()); + data[32..64].copy_from_slice(&U256::from(51u64).to_be_bytes::<32>()); + let slot = keccak256(data); + + // SGT contract address + let sgt_contract = address!("4200000000000000000000000000000000000800"); + + // Read from state + let state_provider = match self.inner.client().latest() { + Ok(provider) => provider, + Err(_) => return U256::ZERO, + }; + + match state_provider.storage(sgt_contract, slot.into()) { + Ok(Some(value)) => value, + _ => U256::ZERO, + } + } + /// Wrapper for is valid cross tx pub async fn is_valid_cross_tx(&self, tx: &Tx) -> Option> { // We don't need to check for deposit transaction in here, because they won't come from diff --git a/rust/op-reth/examples/custom-node/src/evm/config.rs b/rust/op-reth/examples/custom-node/src/evm/config.rs index 094e0b48de593..2f6c4e091a106 100644 --- a/rust/op-reth/examples/custom-node/src/evm/config.rs +++ b/rust/op-reth/examples/custom-node/src/evm/config.rs @@ -84,6 +84,7 @@ impl ConfigureEvm for CustomEvmConfig { parent_hash: block.header().parent_hash(), parent_beacon_block_root: block.header().parent_beacon_block_root(), extra_data: block.header().extra_data().clone(), + sgt_config: Default::default(), }, extension: block.extension, }) @@ -99,6 +100,7 @@ impl ConfigureEvm for CustomEvmConfig { parent_hash: parent.hash(), parent_beacon_block_root: attributes.inner.parent_beacon_block_root, extra_data: attributes.inner.extra_data, + sgt_config: Default::default(), }, extension: attributes.extension, }) From b32d29641404ca2cf862489f691df596dc7116bc Mon Sep 17 00:00:00 2001 From: blockchaindevsh Date: Fri, 20 Mar 2026 18:34:22 +0800 Subject: [PATCH 2/2] refactor(op-reth): pass SGT config via CfgEnv instead of Evm trait Move SGT configuration from OpBlockExecutionCtx + Evm::configure_sgt() to CfgEnv fields, matching how OpSpecId passes hardfork info. This eliminates the alloy-evm fork entirely. Changes: - Remove configure_sgt() from Evm trait impl and OpEvm inherent method - Remove sgt_config from OpBlockExecutionCtx - Set CfgEnv.sgt_enabled/sgt_is_native_backed in evm_env(), next_evm_env(), evm_env_for_payload() - Switch revm fork from blockchaindevsh/revm to QuarkChain/revm@3a6aa085 which adds is_sgt_enabled()/is_sgt_native_backed() to Cfg trait - Drop alloy-evm fork (blockchaindevsh/evm) from [patch.crates-io] External dependency: QuarkChain/revm@3a6aa085 (only fork needed) Co-Authored-By: Claude Opus 4.6 (1M context) --- rust/Cargo.lock | 31 ++++++++++--------- rust/Cargo.toml | 27 ++++++++-------- rust/alloy-op-evm/src/block/mod.rs | 7 +---- rust/alloy-op-evm/src/lib.rs | 14 --------- .../crates/proof/executor/src/builder/core.rs | 1 - rust/op-reth/crates/evm/src/lib.rs | 30 +++++++++--------- .../examples/custom-node/src/evm/config.rs | 2 -- 7 files changed, 45 insertions(+), 67 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 1815186cd519d..3340cb6122fb1 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -267,8 +267,9 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.27.2" -source = "git+https://github.com/blockchaindevsh/evm?branch=op-fork-0.27.2#e92f642d25c37d3b57b53dc4183b824c286b9da9" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b991c370ce44e70a3a9e474087e3d65e42e66f967644ad729dc4cec09a21fd09" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8023,7 +8024,7 @@ dependencies = [ [[package]] name = "op-revm" version = "15.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "auto_impl", "revm", @@ -12724,7 +12725,7 @@ dependencies = [ [[package]] name = "revm" version = "34.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "revm-bytecode", "revm-context", @@ -12742,7 +12743,7 @@ dependencies = [ [[package]] name = "revm-bytecode" version = "8.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "bitvec", "phf", @@ -12753,7 +12754,7 @@ dependencies = [ [[package]] name = "revm-context" version = "13.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "bitvec", "cfg-if", @@ -12769,7 +12770,7 @@ dependencies = [ [[package]] name = "revm-context-interface" version = "14.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -12784,7 +12785,7 @@ dependencies = [ [[package]] name = "revm-database" version = "10.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "alloy-eips", "revm-bytecode", @@ -12797,7 +12798,7 @@ dependencies = [ [[package]] name = "revm-database-interface" version = "9.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "auto_impl", "either", @@ -12810,7 +12811,7 @@ dependencies = [ [[package]] name = "revm-handler" version = "15.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "auto_impl", "derive-where", @@ -12828,7 +12829,7 @@ dependencies = [ [[package]] name = "revm-inspector" version = "15.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "auto_impl", "either", @@ -12865,7 +12866,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "32.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -12877,7 +12878,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "32.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -12901,7 +12902,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "22.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "alloy-primitives", "num_enum", @@ -12912,7 +12913,7 @@ dependencies = [ [[package]] name = "revm-state" version = "9.0.0" -source = "git+https://github.com/blockchaindevsh/revm?branch=op-revm-sgt-support#79e76f2770e355ba007f6259d8f8b61a7b6ed5af" +source = "git+https://github.com/QuarkChain/revm?rev=3a6aa08522a25ef18f91f36ce97b9e5423dea2a3#3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" dependencies = [ "alloy-eip7928", "bitflags 2.10.0", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c77daaefd9b92..e6f44de89e26c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -672,17 +672,16 @@ op-alloy = { path = "op-alloy/crates/op-alloy" } alloy-op-hardforks = { path = "alloy-op-hardforks/" } # Forked revm with SGT support (based on revm 34.0.0) -revm = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-bytecode = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-database = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-state = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-primitives = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-interpreter = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-context = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-handler = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-precompile = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-inspector = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-context-interface = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -revm-database-interface = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -op-revm = { git = "https://github.com/blockchaindevsh/revm", branch = "op-revm-sgt-support" } -alloy-evm = { git = "https://github.com/blockchaindevsh/evm", branch = "op-fork-0.27.2" } +revm = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-bytecode = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-database = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-state = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-primitives = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-interpreter = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-context = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-handler = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-precompile = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-inspector = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-context-interface = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +revm-database-interface = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } +op-revm = { git = "https://github.com/QuarkChain/revm", rev = "3a6aa08522a25ef18f91f36ce97b9e5423dea2a3" } diff --git a/rust/alloy-op-evm/src/block/mod.rs b/rust/alloy-op-evm/src/block/mod.rs index ab0c8a68a801e..1c1f48527677e 100644 --- a/rust/alloy-op-evm/src/block/mod.rs +++ b/rust/alloy-op-evm/src/block/mod.rs @@ -55,8 +55,6 @@ pub struct OpBlockExecutionCtx { pub parent_beacon_block_root: Option, /// The block's extra data. pub extra_data: Bytes, - /// SGT configuration for this block. - pub sgt_config: crate::sgt::SgtConfig, } /// The result of executing an OP transaction. @@ -462,16 +460,13 @@ where fn create_executor<'a, DB, I>( &'a self, - mut evm: EvmF::Evm<&'a mut State, I>, + evm: EvmF::Evm<&'a mut State, I>, ctx: Self::ExecutionCtx<'a>, ) -> impl BlockExecutorFor<'a, Self, DB, I> where DB: Database + 'a, I: Inspector>> + 'a, { - // Configure SGT settings for OP Stack - evm.configure_sgt(ctx.sgt_config.enabled, ctx.sgt_config.is_native_backed); - OpBlockExecutor::new(evm, ctx, &self.spec, &self.receipt_builder) } } diff --git a/rust/alloy-op-evm/src/lib.rs b/rust/alloy-op-evm/src/lib.rs index e5a8f37330cc9..bb966ffff0041 100644 --- a/rust/alloy-op-evm/src/lib.rs +++ b/rust/alloy-op-evm/src/lib.rs @@ -153,20 +153,6 @@ where ) } - fn configure_sgt(&mut self, enabled: bool, is_native_backed: bool) { - OpEvm::configure_sgt(self, enabled, is_native_backed); - } -} - -impl OpEvm { - /// Set SGT configuration on the OP EVM context. - /// - /// This initializes the SGT configuration in L1BlockInfo before transaction execution. - /// The handler will read and preserve these values when fetching L1 block info from storage. - pub fn configure_sgt(&mut self, enabled: bool, is_native_backed: bool) { - self.ctx_mut().chain.sgt_enabled = enabled; - self.ctx_mut().chain.sgt_is_native_backed = is_native_backed; - } } /// Factory producing [`OpEvm`]s. diff --git a/rust/kona/crates/proof/executor/src/builder/core.rs b/rust/kona/crates/proof/executor/src/builder/core.rs index 55590e2a7022c..dc141dee1e160 100644 --- a/rust/kona/crates/proof/executor/src/builder/core.rs +++ b/rust/kona/crates/proof/executor/src/builder/core.rs @@ -259,7 +259,6 @@ where parent_beacon_block_root: attrs.payload_attributes.parent_beacon_block_root, // This field is unused for individual block building jobs. extra_data: Default::default(), - sgt_config: Default::default(), }; let executor = self.factory.create_executor(evm, ctx); diff --git a/rust/op-reth/crates/evm/src/lib.rs b/rust/op-reth/crates/evm/src/lib.rs index 4e97027da70f5..b5d55bf32a8d7 100644 --- a/rust/op-reth/crates/evm/src/lib.rs +++ b/rust/op-reth/crates/evm/src/lib.rs @@ -174,7 +174,11 @@ where } fn evm_env(&self, header: &Header) -> Result, Self::Error> { - Ok(EvmEnv::for_op_block(header, self.chain_spec(), self.chain_spec().chain().id())) + let mut env = EvmEnv::for_op_block(header, self.chain_spec(), self.chain_spec().chain().id()); + let sgt_config = self.extract_sgt_config(header.timestamp()); + env.cfg_env.sgt_enabled = sgt_config.enabled; + env.cfg_env.sgt_is_native_backed = sgt_config.is_native_backed; + Ok(env) } fn next_evm_env( @@ -182,7 +186,7 @@ where parent: &Header, attributes: &Self::NextBlockEnvCtx, ) -> Result, Self::Error> { - Ok(EvmEnv::for_op_next_block( + let mut env = EvmEnv::for_op_next_block( parent, NextEvmEnvAttributes { timestamp: attributes.timestamp, @@ -193,21 +197,21 @@ where self.chain_spec().next_block_base_fee(parent, attributes.timestamp).unwrap_or_default(), self.chain_spec(), self.chain_spec().chain().id(), - )) + ); + let sgt_config = self.extract_sgt_config(attributes.timestamp); + env.cfg_env.sgt_enabled = sgt_config.enabled; + env.cfg_env.sgt_is_native_backed = sgt_config.is_native_backed; + Ok(env) } fn context_for_block( &self, block: &'_ SealedBlock, ) -> Result { - let sgt_config = - self.extract_sgt_config(block.header().timestamp()); - Ok(OpBlockExecutionCtx { parent_hash: block.header().parent_hash(), parent_beacon_block_root: block.header().parent_beacon_block_root(), extra_data: block.header().extra_data().clone(), - sgt_config, }) } @@ -216,13 +220,10 @@ where parent: &SealedHeader, attributes: Self::NextBlockEnvCtx, ) -> Result { - let sgt_config = self.extract_sgt_config(attributes.timestamp); - Ok(OpBlockExecutionCtx { parent_hash: parent.hash(), parent_beacon_block_root: attributes.parent_beacon_block_root, extra_data: attributes.extra_data, - sgt_config, }) } } @@ -251,9 +252,12 @@ where let spec = revm_spec_by_timestamp_after_bedrock(self.chain_spec(), timestamp); - let cfg_env = CfgEnv::new() + let sgt_config = self.extract_sgt_config(timestamp); + let mut cfg_env = CfgEnv::new() .with_chain_id(self.chain_spec().chain().id()) .with_spec_and_mainnet_gas_params(spec); + cfg_env.sgt_enabled = sgt_config.enabled; + cfg_env.sgt_is_native_backed = sgt_config.is_native_backed; let blob_excess_gas_and_price = spec .into_eth_spec() @@ -284,14 +288,10 @@ where &self, payload: &'a OpExecutionData, ) -> Result, Self::Error> { - let sgt_config = - self.extract_sgt_config(payload.payload.timestamp()); - Ok(OpBlockExecutionCtx { parent_hash: payload.parent_hash(), parent_beacon_block_root: payload.sidecar.parent_beacon_block_root(), extra_data: payload.payload.as_v1().extra_data.clone(), - sgt_config, }) } diff --git a/rust/op-reth/examples/custom-node/src/evm/config.rs b/rust/op-reth/examples/custom-node/src/evm/config.rs index 2f6c4e091a106..094e0b48de593 100644 --- a/rust/op-reth/examples/custom-node/src/evm/config.rs +++ b/rust/op-reth/examples/custom-node/src/evm/config.rs @@ -84,7 +84,6 @@ impl ConfigureEvm for CustomEvmConfig { parent_hash: block.header().parent_hash(), parent_beacon_block_root: block.header().parent_beacon_block_root(), extra_data: block.header().extra_data().clone(), - sgt_config: Default::default(), }, extension: block.extension, }) @@ -100,7 +99,6 @@ impl ConfigureEvm for CustomEvmConfig { parent_hash: parent.hash(), parent_beacon_block_root: attributes.inner.parent_beacon_block_root, extra_data: attributes.inner.extra_data, - sgt_config: Default::default(), }, extension: attributes.extension, })