Skip to content
Closed
20 changes: 12 additions & 8 deletions op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,20 +273,24 @@ func (d *GasPriceOracleDeployConfig) OperatorFeeParams() [32]byte {

// GasTokenDeployConfig configures the optional custom gas token functionality.
type GasTokenDeployConfig struct {
// UseCustomGasToken is a flag to indicate that a custom gas token should be used
UseCustomGasToken bool `json:"useCustomGasToken"`
// CustomGasTokenAddress is the address of the ERC20 token to be used to pay for gas on L2.
CustomGasTokenAddress common.Address `json:"customGasTokenAddress"`
// IsCustomGasToken is a flag to indicate that a custom gas token should be used
IsCustomGasToken bool `json:"isCustomGasToken"`
// GasPayingTokenName represents the custom gas token name.
GasPayingTokenName string `json:"gasPayingTokenName"`
// GasPayingTokenSymbol represents the custom gas token symbol.
GasPayingTokenSymbol string `json:"gasPayingTokenSymbol"`
}

var _ ConfigChecker = (*GasTokenDeployConfig)(nil)

func (d *GasTokenDeployConfig) Check(log log.Logger) error {
if d.UseCustomGasToken {
if d.CustomGasTokenAddress == (common.Address{}) {
return fmt.Errorf("%w: CustomGasTokenAddress cannot be address(0)", ErrInvalidDeployConfig)
if d.IsCustomGasToken {
if d.GasPayingTokenName == "" {
return fmt.Errorf("%w: GasPayingTokenName cannot be empty", ErrInvalidDeployConfig)
}
if d.GasPayingTokenSymbol == "" {
return fmt.Errorf("%w: GasPayingTokenSymbol cannot be empty", ErrInvalidDeployConfig)
}
log.Info("Using custom gas token", "address", d.CustomGasTokenAddress)
}
return nil
}
Expand Down
5 changes: 3 additions & 2 deletions op-chain-ops/genesis/testdata/test-deploy-config-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"maxSequencerDrift": 20,
"sequencerWindowSize": 100,
"channelTimeout": 30,
"customGasTokenAddress": "0x0000000000000000000000000000000000000000",
"p2pSequencerAddress": "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc",
"batchInboxAddress": "0x42000000000000000000000000000000000000ff",
"batchSenderAddress": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc",
Expand Down Expand Up @@ -84,7 +83,9 @@
"proofMaturityDelaySeconds": 12,
"disputeGameFinalityDelaySeconds": 6,
"respectedGameType": 0,
"useCustomGasToken": false,
"isCustomGasToken": false,
"gasPayingTokenName": "",
"gasPayingTokenSymbol": "",
"useFaultProofs": false,
"useAltDA": false,
"daBondSize": 0,
Expand Down
4 changes: 4 additions & 0 deletions op-chain-ops/interopgen/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme
AllowCustomDisputeParameters: true,
OperatorFeeScalar: cfg.GasPriceOracleOperatorFeeScalar,
OperatorFeeConstant: cfg.GasPriceOracleOperatorFeeConstant,
IsCustomGasToken: cfg.IsCustomGasToken,
})
if err != nil {
return nil, fmt.Errorf("failed to deploy L2 OP chain: %w", err)
Expand Down Expand Up @@ -324,6 +325,9 @@ func GenesisL2(l2Host *script.Host, cfg *L2Config, deployment *L2Deployment, mul
DeployCrossL2Inbox: multichainDepSet,
EnableGovernance: cfg.EnableGovernance,
FundDevAccounts: cfg.FundDevAccounts,
IsCustomGasToken: cfg.IsCustomGasToken,
GasPayingTokenName: cfg.GasPayingTokenName,
GasPayingTokenSymbol: cfg.GasPayingTokenSymbol,
}); err != nil {
return fmt.Errorf("failed L2 genesis: %w", err)
}
Expand Down
4 changes: 3 additions & 1 deletion op-chain-ops/interopgen/recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,9 @@ func (r *InteropDevL2Recipe) build(l1ChainID uint64, addrs devkeys.Addresses) (*
GasPriceOracleBlobBaseFeeScalar: 810949,
},
GasTokenDeployConfig: genesis.GasTokenDeployConfig{
UseCustomGasToken: false,
IsCustomGasToken: false,
GasPayingTokenName: "",
GasPayingTokenSymbol: "",
},
OperatorDeployConfig: genesis.OperatorDeployConfig{
P2PSequencerAddress: sequencerP2P,
Expand Down
5 changes: 5 additions & 0 deletions op-deployer/pkg/deployer/integration_test/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,11 @@ func newChainIntent(t *testing.T, dk *devkeys.MnemonicDevKeys, l1ChainID *big.In
Proposer: addrFor(t, dk, devkeys.ProposerRole.Key(l1ChainID)),
Challenger: addrFor(t, dk, devkeys.ChallengerRole.Key(l1ChainID)),
},
CustomGasToken: &state.CustomGasToken{
Enabled: standard.CustomGasTokenEnabled,
Name: standard.CustomGasTokenName,
Symbol: standard.CustomGasTokenSymbol,
},
}
}

Expand Down
3 changes: 3 additions & 0 deletions op-deployer/pkg/deployer/opcm/l2genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ type L2GenesisInput struct {
DeployCrossL2Inbox bool
EnableGovernance bool
FundDevAccounts bool
IsCustomGasToken bool
GasPayingTokenName string
GasPayingTokenSymbol string
}

type L2GenesisScript script.DeployScriptWithoutOutput[L2GenesisInput]
Expand Down
1 change: 1 addition & 0 deletions op-deployer/pkg/deployer/opcm/opchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type DeployOPChainInput struct {
DisputeClockExtension uint64
DisputeMaxClockDuration uint64
AllowCustomDisputeParameters bool
IsCustomGasToken bool

OperatorFeeScalar uint32
OperatorFeeConstant uint64
Expand Down
17 changes: 17 additions & 0 deletions op-deployer/pkg/deployer/pipeline/l2genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
)

type l2GenesisOverrides struct {
IsCustomGasToken bool `json:"isCustomGasToken"`
GasPayingTokenName string `json:"gasPayingTokenName"`
GasPayingTokenSymbol string `json:"gasPayingTokenSymbol"`
FundDevAccounts bool `json:"fundDevAccounts"`
BaseFeeVaultMinimumWithdrawalAmount *hexutil.Big `json:"baseFeeVaultMinimumWithdrawalAmount"`
L1FeeVaultMinimumWithdrawalAmount *hexutil.Big `json:"l1FeeVaultMinimumWithdrawalAmount"`
Expand Down Expand Up @@ -94,6 +97,9 @@ func GenerateL2Genesis(pEnv *Env, intent *state.Intent, bundle ArtifactsBundle,
DeployCrossL2Inbox: len(intent.Chains) > 1,
EnableGovernance: overrides.EnableGovernance,
FundDevAccounts: overrides.FundDevAccounts,
IsCustomGasToken: thisIntent.CustomGasToken.Enabled,
GasPayingTokenName: thisIntent.CustomGasToken.Name,
GasPayingTokenSymbol: thisIntent.CustomGasToken.Symbol,
}); err != nil {
return fmt.Errorf("failed to call L2Genesis script: %w", err)
}
Expand Down Expand Up @@ -142,6 +148,14 @@ func calculateL2GenesisOverrides(intent *state.Intent, thisIntent *state.ChainIn
}
}

if thisIntent.CustomGasToken == nil {
thisIntent.CustomGasToken = &state.CustomGasToken{
Enabled: overrides.IsCustomGasToken,
Name: overrides.GasPayingTokenName,
Symbol: overrides.GasPayingTokenSymbol,
}
}

return overrides, schedule, nil
}

Expand All @@ -156,6 +170,9 @@ func wdNetworkToBig(wd genesis.WithdrawalNetwork) *big.Int {

func defaultOverrides() l2GenesisOverrides {
return l2GenesisOverrides{
IsCustomGasToken: false,
GasPayingTokenName: "",
GasPayingTokenSymbol: "",
FundDevAccounts: false,
BaseFeeVaultMinimumWithdrawalAmount: standard.VaultMinWithdrawalAmount,
L1FeeVaultMinimumWithdrawalAmount: standard.VaultMinWithdrawalAmount,
Expand Down
15 changes: 15 additions & 0 deletions op-deployer/pkg/deployer/pipeline/l2genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ func TestCalculateL2GenesisOverrides(t *testing.T) {
SequencerFeeVaultWithdrawalNetwork: "local",
EnableGovernance: false,
GovernanceTokenOwner: standard.GovernanceTokenOwner,
IsCustomGasToken: false,
GasPayingTokenName: "",
GasPayingTokenSymbol: "",
},
expectedSchedule: func() *genesis.UpgradeScheduleDeployConfig {
return standard.DefaultHardforkScheduleForTag("")
Expand All @@ -73,6 +76,9 @@ func TestCalculateL2GenesisOverrides(t *testing.T) {
"enableGovernance": true,
"governanceTokenOwner": "0x1111111111111111111111111111111111111111",
"l2GenesisInteropTimeOffset": "0x1234",
"isCustomGasToken": false,
"gasPayingTokenName": "",
"gasPayingTokenSymbol": "",
},
},
chainIntent: &state.ChainIntent{},
Expand All @@ -87,6 +93,9 @@ func TestCalculateL2GenesisOverrides(t *testing.T) {
SequencerFeeVaultWithdrawalNetwork: "remote",
EnableGovernance: true,
GovernanceTokenOwner: common.HexToAddress("0x1111111111111111111111111111111111111111"),
IsCustomGasToken: false,
GasPayingTokenName: "",
GasPayingTokenSymbol: "",
},
expectedSchedule: func() *genesis.UpgradeScheduleDeployConfig {
sched := standard.DefaultHardforkScheduleForTag("")
Expand Down Expand Up @@ -114,6 +123,9 @@ func TestCalculateL2GenesisOverrides(t *testing.T) {
"enableGovernance": true,
"governanceTokenOwner": "0x1111111111111111111111111111111111111111",
"l2GenesisInteropTimeOffset": "0x1234",
"isCustomGasToken": false,
"gasPayingTokenName": "",
"gasPayingTokenSymbol": "",
},
},
expectError: false,
Expand All @@ -127,6 +139,9 @@ func TestCalculateL2GenesisOverrides(t *testing.T) {
SequencerFeeVaultWithdrawalNetwork: "remote",
EnableGovernance: true,
GovernanceTokenOwner: common.HexToAddress("0x1111111111111111111111111111111111111111"),
IsCustomGasToken: false,
GasPayingTokenName: "",
GasPayingTokenSymbol: "",
},
expectedSchedule: func() *genesis.UpgradeScheduleDeployConfig {
sched := standard.DefaultHardforkScheduleForTag("")
Expand Down
1 change: 1 addition & 0 deletions op-deployer/pkg/deployer/pipeline/opchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func makeDCI(intent *state.Intent, thisIntent *state.ChainIntent, chainID common
DisputeClockExtension: proofParams.DisputeClockExtension, // 3 hours (input in seconds)
DisputeMaxClockDuration: proofParams.DisputeMaxClockDuration, // 3.5 days (input in seconds)
AllowCustomDisputeParameters: proofParams.DangerouslyAllowCustomDisputeParameters,
IsCustomGasToken: thisIntent.CustomGasToken != nil && thisIntent.CustomGasToken.Enabled,
OperatorFeeScalar: thisIntent.OperatorFeeScalar,
OperatorFeeConstant: thisIntent.OperatorFeeConstant,
}, nil
Expand Down
3 changes: 3 additions & 0 deletions op-deployer/pkg/deployer/standard/standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ const (
Eip1559DenominatorCanyon uint64 = 250
Eip1559Denominator uint64 = 50
Eip1559Elasticity uint64 = 6
CustomGasTokenEnabled bool = false
CustomGasTokenName string = ""
CustomGasTokenSymbol string = ""

ContractsV160Tag = "op-contracts/v1.6.0"
ContractsV180Tag = "op-contracts/v1.8.0-rc.4"
Expand Down
17 changes: 16 additions & 1 deletion op-deployer/pkg/deployer/state/chain_intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ type L2DevGenesisParams struct {
Prefund map[common.Address]*hexutil.U256 `json:"prefund" toml:"prefund"`
}

type CustomGasToken struct {
Enabled bool `json:"enabled" toml:"enabled"`
Name string `json:"name" toml:"name"`
Symbol string `json:"symbol" toml:"symbol"`
}

type ChainIntent struct {
ID common.Hash `json:"id" toml:"id"`
BaseFeeVaultRecipient common.Address `json:"baseFeeVaultRecipient" toml:"baseFeeVaultRecipient"`
Expand All @@ -72,7 +78,7 @@ type ChainIntent struct {
OperatorFeeScalar uint32 `json:"operatorFeeScalar,omitempty" toml:"operatorFeeScalar,omitempty"`
OperatorFeeConstant uint64 `json:"operatorFeeConstant,omitempty" toml:"operatorFeeConstant,omitempty"`
L1StartBlockHash *common.Hash `json:"l1StartBlockHash,omitempty" toml:"l1StartBlockHash,omitempty"`

CustomGasToken *CustomGasToken `json:"customGasToken" toml:"customGasToken"`
// Optional. For development purposes only. Only enabled if the operation mode targets a genesis-file output.
L2DevGenesisParams *L2DevGenesisParams `json:"l2DevGenesisParams,omitempty" toml:"l2DevGenesisParams,omitempty"`
}
Expand Down Expand Up @@ -118,6 +124,15 @@ func (c *ChainIntent) Check() error {
return fmt.Errorf("%w: chainId=%s", ErrFeeVaultZeroAddress, c.ID)
}

if c.CustomGasToken != nil && c.CustomGasToken.Enabled {
if c.CustomGasToken.Name == "" {
return fmt.Errorf("%w: CustomGasToken.Name cannot be empty when enabled, chainId=%s", ErrIncompatibleValue, c.ID)
}
if c.CustomGasToken.Symbol == "" {
return fmt.Errorf("%w: CustomGasToken.Symbol cannot be empty when enabled, chainId=%s", ErrIncompatibleValue, c.ID)
}
}

if c.DangerousAltDAConfig.UseAltDA {
return c.DangerousAltDAConfig.Check(nil)
}
Expand Down
6 changes: 6 additions & 0 deletions op-deployer/pkg/deployer/state/deploy_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ func CombineDeployConfig(intent *Intent, chainIntent *ChainIntent, state *State,
EIP1559Elasticity: chainIntent.Eip1559Elasticity,
},

GasTokenDeployConfig: genesis.GasTokenDeployConfig{
IsCustomGasToken: chainIntent.CustomGasToken.Enabled,
GasPayingTokenName: chainIntent.CustomGasToken.Name,
GasPayingTokenSymbol: chainIntent.CustomGasToken.Symbol,
},

// STOP! This struct sets the _default_ upgrade schedule for all chains.
// Any upgrades you enable here will be enabled for all new deployments.
// In-development hardforks should never be activated here. Instead, they
Expand Down
5 changes: 5 additions & 0 deletions op-deployer/pkg/deployer/state/deploy_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ func TestCombineDeployConfig(t *testing.T) {
UnsafeBlockSigner: common.HexToAddress("0xabc"),
Batcher: common.HexToAddress("0xdef"),
},
CustomGasToken: &CustomGasToken{
Enabled: false,
Name: "Test",
Copy link
Contributor

@blockchaindevsh blockchaindevsh Sep 7, 2025

Choose a reason for hiding this comment

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

Name/Symbol should be empty since Enabled is false?

Symbol: "TEST",
},
}
state := State{
SuperchainDeployment: &addresses.SuperchainContracts{ProtocolVersionsProxy: common.HexToAddress("0x123")},
Expand Down
13 changes: 13 additions & 0 deletions op-deployer/pkg/deployer/state/intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ func (c *Intent) validateStandardValues() error {
if len(chain.AdditionalDisputeGames) > 0 {
return fmt.Errorf("%w: chainId=%s additionalDisputeGames must be nil", ErrNonStandardValue, chain.ID)
}
if chain.CustomGasToken != nil && (chain.CustomGasToken.Enabled != standard.CustomGasTokenEnabled) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is something that we may need to revisit

return fmt.Errorf("%w: chainId=%s custom gas token not allowed in standard configuration", ErrNonStandardValue, chain.ID)
}
}

challenger, _ := standard.ChallengerAddressFor(c.L1ChainID)
Expand Down Expand Up @@ -298,6 +301,11 @@ func NewIntentCustom(l1ChainId uint64, l2ChainIds []common.Hash) (Intent, error)
intent.Chains = append(intent.Chains, &ChainIntent{
ID: l2ChainID,
GasLimit: standard.GasLimit,
CustomGasToken: &CustomGasToken{
Enabled: standard.CustomGasTokenEnabled,
Name: standard.CustomGasTokenName,
Symbol: standard.CustomGasTokenSymbol,
},
})
}
return intent, nil
Expand Down Expand Up @@ -342,6 +350,11 @@ func NewIntentStandard(l1ChainId uint64, l2ChainIds []common.Hash) (Intent, erro
L1ProxyAdminOwner: l1ProxyAdminOwner,
L2ProxyAdminOwner: l2ProxyAdminOwner,
},
CustomGasToken: &CustomGasToken{
Enabled: standard.CustomGasTokenEnabled,
Name: standard.CustomGasTokenName,
Symbol: standard.CustomGasTokenSymbol,
},
})
}
return intent, nil
Expand Down
45 changes: 45 additions & 0 deletions op-deployer/pkg/deployer/state/intent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ func TestValidateStandardValues(t *testing.T) {
},
ErrIncompatibleValue,
},
{
"CustomGasToken",
func(intent *Intent) {
intent.Chains[0].CustomGasToken = &CustomGasToken{
Enabled: true,
Name: "Custom Gas Token",
Symbol: "CGT",
}
},
ErrNonStandardValue,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -131,6 +142,10 @@ func TestValidateCustomValues(t *testing.T) {
err = intent.Check()
require.NoError(t, err)

setCustomGasToken(&intent)
err = intent.Check()
require.NoError(t, err)

tests := []struct {
name string
mutator func(intent *Intent)
Expand All @@ -155,6 +170,28 @@ func TestValidateCustomValues(t *testing.T) {
},
ErrIncompatibleValue,
},
{
"empty custom gas token name when enabled",
func(intent *Intent) {
intent.Chains[0].CustomGasToken = &CustomGasToken{
Enabled: true,
Name: "",
Symbol: "CGT",
}
},
ErrIncompatibleValue,
},
{
"empty custom gas token symbol when enabled",
func(intent *Intent) {
intent.Chains[0].CustomGasToken = &CustomGasToken{
Enabled: true,
Name: "Custom Gas Token",
Symbol: "",
}
},
ErrIncompatibleValue,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -211,3 +248,11 @@ func setFeeAddresses(intent *Intent) {
intent.Chains[0].L1FeeVaultRecipient = common.HexToAddress("0x09")
intent.Chains[0].SequencerFeeVaultRecipient = common.HexToAddress("0x0A")
}

func setCustomGasToken(intent *Intent) {
intent.Chains[0].CustomGasToken = &CustomGasToken{
Enabled: true,
Name: "Custom Gas Token",
Symbol: "CGT",
}
}
Loading