Commit a6074a7d authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

feat: custom gas token (#10143)

* contracts-bedrock: custom gas token L1 contracts

Implement the L1 contract diff of the custom gas token spec
Co-authored-by: default avatarDiego <105765223+0xfuturistic@users.noreply.github.com>

* contracts-bedrock: custom gas token L2 contracts

Implement the L2 contract diff of the custom gas token spec
Co-authored-by: default avatarDiego <105765223+0xfuturistic@users.noreply.github.com>

* contracts-bedrock: custom gas token contracts

Libraries and universal contracts in the custom gas token spec
Co-authored-by: default avatarDiego <105765223+0xfuturistic@users.noreply.github.com>

* contracts-bedrock: custom gas token scripts

Implement the scripts for the custom gas token spec
Co-authored-by: default avatarDiego <105765223+0xfuturistic@users.noreply.github.com>

* contracts-bedrock: custom gas token tests

Implement tests for the custom gas token spec
Co-authored-by: default avatarDiego <105765223+0xfuturistic@users.noreply.github.com>

* op-bindings: update with custom gas token
Co-authored-by: default avatarDiego <105765223+0xfuturistic@users.noreply.github.com>

* op-chain-ops: fixup tests
Co-authored-by: default avatarDiego <105765223+0xfuturistic@users.noreply.github.com>

* contracts-bedrock: snapshots

Update snapshots
Co-authored-by: default avatarDiego <105765223+0xfuturistic@users.noreply.github.com>

* cleanup: get tests passing

* contracts-bedrock: cleanup tests

* contracts-bedrock: cleanup custom gas token

* op-bindings: regenerate

* codesize: fix

* contracts-bedrock: custom gas token cleanup
Co-authored-by: default avatarDiego <105765223+0xfuturistic@users.noreply.github.com>

* build: fix

* custom-gas-token: fix build

* custom-gas-token: cleanup, final spec nits

* gas-snapshot: fix

* contracts-bedrock: cleanup

* contracts-bedrock: semver lock

* invariant-docs: fixup

* storage-layout: address

* bindings: remove preview

* bindings: add back bindingspreview

* weth: remove weth9

Migrate to WETH based on weth98

* contracts-bedrock: cleanup

* custom-gas-token: more cleanup

Fix abi

* contracts-bedrock: test case

* custom-gas-token: cleanup, tests

* lint: fix

* custom-gas-token: address review comments

* snapshots: regenerate

* lint: fix

* contracts-bedrock: clean up semantics of cgt

Custom gas token semantics are strengthened and
cleaned up to ensure invariants in spec are held
true.

* snapshots: regenerate

* snapshots: gas snapshot

* semver-lock: regenerate

* comments: address

* semver: calculate

* ctb: revert in test

* tests: fix flake

* portal: better comment

* test: fixup

* lint: fix

* snapshot: gas

---------
Co-authored-by: default avatarDiego <105765223+0xfuturistic@users.noreply.github.com>
parent e21d8fd4
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
...@@ -8,7 +8,7 @@ import "github.com/ethereum/go-ethereum/common" ...@@ -8,7 +8,7 @@ import "github.com/ethereum/go-ethereum/common"
const ( const (
L2ToL1MessagePasser = "0x4200000000000000000000000000000000000016" L2ToL1MessagePasser = "0x4200000000000000000000000000000000000016"
DeployerWhitelist = "0x4200000000000000000000000000000000000002" DeployerWhitelist = "0x4200000000000000000000000000000000000002"
WETH9 = "0x4200000000000000000000000000000000000006" WETH = "0x4200000000000000000000000000000000000006"
L2CrossDomainMessenger = "0x4200000000000000000000000000000000000007" L2CrossDomainMessenger = "0x4200000000000000000000000000000000000007"
L2StandardBridge = "0x4200000000000000000000000000000000000010" L2StandardBridge = "0x4200000000000000000000000000000000000010"
SequencerFeeVault = "0x4200000000000000000000000000000000000011" SequencerFeeVault = "0x4200000000000000000000000000000000000011"
...@@ -41,7 +41,7 @@ const ( ...@@ -41,7 +41,7 @@ const (
var ( var (
L2ToL1MessagePasserAddr = common.HexToAddress(L2ToL1MessagePasser) L2ToL1MessagePasserAddr = common.HexToAddress(L2ToL1MessagePasser)
DeployerWhitelistAddr = common.HexToAddress(DeployerWhitelist) DeployerWhitelistAddr = common.HexToAddress(DeployerWhitelist)
WETH9Addr = common.HexToAddress(WETH9) WETHAddr = common.HexToAddress(WETH)
L2CrossDomainMessengerAddr = common.HexToAddress(L2CrossDomainMessenger) L2CrossDomainMessengerAddr = common.HexToAddress(L2CrossDomainMessenger)
L2StandardBridgeAddr = common.HexToAddress(L2StandardBridge) L2StandardBridgeAddr = common.HexToAddress(L2StandardBridge)
SequencerFeeVaultAddr = common.HexToAddress(SequencerFeeVault) SequencerFeeVaultAddr = common.HexToAddress(SequencerFeeVault)
...@@ -77,7 +77,7 @@ var ( ...@@ -77,7 +77,7 @@ var (
func init() { func init() {
Predeploys["L2ToL1MessagePasser"] = &Predeploy{Address: L2ToL1MessagePasserAddr} Predeploys["L2ToL1MessagePasser"] = &Predeploy{Address: L2ToL1MessagePasserAddr}
Predeploys["DeployerWhitelist"] = &Predeploy{Address: DeployerWhitelistAddr} Predeploys["DeployerWhitelist"] = &Predeploy{Address: DeployerWhitelistAddr}
Predeploys["WETH9"] = &Predeploy{Address: WETH9Addr, ProxyDisabled: true} Predeploys["WETH"] = &Predeploy{Address: WETHAddr, ProxyDisabled: true}
Predeploys["L2CrossDomainMessenger"] = &Predeploy{Address: L2CrossDomainMessengerAddr} Predeploys["L2CrossDomainMessenger"] = &Predeploy{Address: L2CrossDomainMessengerAddr}
Predeploys["L2StandardBridge"] = &Predeploy{Address: L2StandardBridgeAddr} Predeploys["L2StandardBridge"] = &Predeploy{Address: L2StandardBridgeAddr}
Predeploys["SequencerFeeVault"] = &Predeploy{Address: SequencerFeeVaultAddr} Predeploys["SequencerFeeVault"] = &Predeploy{Address: SequencerFeeVaultAddr}
......
...@@ -248,7 +248,10 @@ type DeployConfig struct { ...@@ -248,7 +248,10 @@ type DeployConfig struct {
// UseFaultProofs is a flag that indicates if the system is using fault // UseFaultProofs is a flag that indicates if the system is using fault
// proofs instead of the older output oracle mechanism. // proofs instead of the older output oracle mechanism.
UseFaultProofs bool `json:"useFaultProofs"` UseFaultProofs bool `json:"useFaultProofs"`
// 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"`
// UsePlasma is a flag that indicates if the system is using op-plasma // UsePlasma is a flag that indicates if the system is using op-plasma
UsePlasma bool `json:"usePlasma"` UsePlasma bool `json:"usePlasma"`
// DAChallengeWindow represents the block interval during which the availability of a data commitment can be challenged. // DAChallengeWindow represents the block interval during which the availability of a data commitment can be challenged.
...@@ -427,6 +430,12 @@ func (d *DeployConfig) Check() error { ...@@ -427,6 +430,12 @@ func (d *DeployConfig) Check() error {
return fmt.Errorf("%w: DAResolveWindow cannot be 0 when using plasma mode", ErrInvalidDeployConfig) return fmt.Errorf("%w: DAResolveWindow cannot be 0 when using plasma mode", ErrInvalidDeployConfig)
} }
} }
if d.UseCustomGasToken {
if d.CustomGasTokenAddress == (common.Address{}) {
return fmt.Errorf("%w: CustomGasTokenAddress cannot be address(0)", ErrInvalidDeployConfig)
}
log.Info("Using custom gas token", "address", d.CustomGasTokenAddress)
}
// checkFork checks that fork A is before or at the same time as fork B // checkFork checks that fork A is before or at the same time as fork B
checkFork := func(a, b *hexutil.Uint64, aName, bName string) error { checkFork := func(a, b *hexutil.Uint64, aName, bName string) error {
if a == nil && b == nil { if a == nil && b == nil {
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
"channelTimeout": 30, "channelTimeout": 30,
"l1UseClique": false, "l1UseClique": false,
"cliqueSignerAddress": "0x0000000000000000000000000000000000000000", "cliqueSignerAddress": "0x0000000000000000000000000000000000000000",
"customGasTokenAddress": "0x0000000000000000000000000000000000000000",
"p2pSequencerAddress": "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc", "p2pSequencerAddress": "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc",
"batchInboxAddress": "0x42000000000000000000000000000000000000ff", "batchInboxAddress": "0x42000000000000000000000000000000000000ff",
"batchSenderAddress": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc", "batchSenderAddress": "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc",
...@@ -83,6 +84,7 @@ ...@@ -83,6 +84,7 @@
"proofMaturityDelaySeconds": 12, "proofMaturityDelaySeconds": 12,
"disputeGameFinalityDelaySeconds": 6, "disputeGameFinalityDelaySeconds": 6,
"respectedGameType": 0, "respectedGameType": 0,
"useCustomGasToken": false,
"useFaultProofs": false, "useFaultProofs": false,
"usePlasma": false, "usePlasma": false,
"daBondSize": 0, "daBondSize": 0,
......
...@@ -40,29 +40,29 @@ func TestERC20BridgeDeposits(t *testing.T) { ...@@ -40,29 +40,29 @@ func TestERC20BridgeDeposits(t *testing.T) {
opts, err := bind.NewKeyedTransactorWithChainID(sys.Cfg.Secrets.Alice, cfg.L1ChainIDBig()) opts, err := bind.NewKeyedTransactorWithChainID(sys.Cfg.Secrets.Alice, cfg.L1ChainIDBig())
require.Nil(t, err) require.Nil(t, err)
// Deploy WETH9 // Deploy WETH
weth9Address, tx, WETH9, err := bindings.DeployWETH9(opts, l1Client) wethAddress, tx, WETH, err := bindings.DeployWETH(opts, l1Client)
require.NoError(t, err) require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), l1Client, tx.Hash()) _, err = wait.ForReceiptOK(context.Background(), l1Client, tx.Hash())
require.NoError(t, err, "Waiting for deposit tx on L1") require.NoError(t, err, "Waiting for deposit tx on L1")
// Get some WETH // Get some WETH
opts.Value = big.NewInt(params.Ether) opts.Value = big.NewInt(params.Ether)
tx, err = WETH9.Deposit(opts) tx, err = WETH.Deposit(opts)
require.NoError(t, err) require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), l1Client, tx.Hash()) _, err = wait.ForReceiptOK(context.Background(), l1Client, tx.Hash())
require.NoError(t, err) require.NoError(t, err)
opts.Value = nil opts.Value = nil
wethBalance, err := WETH9.BalanceOf(&bind.CallOpts{}, opts.From) wethBalance, err := WETH.BalanceOf(&bind.CallOpts{}, opts.From)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, big.NewInt(params.Ether), wethBalance) require.Equal(t, big.NewInt(params.Ether), wethBalance)
// Deploy L2 WETH9 // Deploy L2 WETH
l2Opts, err := bind.NewKeyedTransactorWithChainID(sys.Cfg.Secrets.Alice, cfg.L2ChainIDBig()) l2Opts, err := bind.NewKeyedTransactorWithChainID(sys.Cfg.Secrets.Alice, cfg.L2ChainIDBig())
require.NoError(t, err) require.NoError(t, err)
optimismMintableTokenFactory, err := bindings.NewOptimismMintableERC20Factory(predeploys.OptimismMintableERC20FactoryAddr, l2Client) optimismMintableTokenFactory, err := bindings.NewOptimismMintableERC20Factory(predeploys.OptimismMintableERC20FactoryAddr, l2Client)
require.NoError(t, err) require.NoError(t, err)
tx, err = optimismMintableTokenFactory.CreateOptimismMintableERC20(l2Opts, weth9Address, "L2-WETH", "L2-WETH") tx, err = optimismMintableTokenFactory.CreateOptimismMintableERC20(l2Opts, wethAddress, "L2-WETH", "L2-WETH")
require.NoError(t, err) require.NoError(t, err)
rcpt, err := wait.ForReceiptOK(context.Background(), l2Client, tx.Hash()) rcpt, err := wait.ForReceiptOK(context.Background(), l2Client, tx.Hash())
require.NoError(t, err) require.NoError(t, err)
...@@ -70,17 +70,17 @@ func TestERC20BridgeDeposits(t *testing.T) { ...@@ -70,17 +70,17 @@ func TestERC20BridgeDeposits(t *testing.T) {
event, err := receipts.FindLog(rcpt.Logs, optimismMintableTokenFactory.ParseOptimismMintableERC20Created) event, err := receipts.FindLog(rcpt.Logs, optimismMintableTokenFactory.ParseOptimismMintableERC20Created)
require.NoError(t, err, "Should emit ERC20Created event") require.NoError(t, err, "Should emit ERC20Created event")
// Approve WETH9 with the bridge // Approve WETH with the bridge
tx, err = WETH9.Approve(opts, cfg.L1Deployments.L1StandardBridgeProxy, new(big.Int).SetUint64(math.MaxUint64)) tx, err = WETH.Approve(opts, cfg.L1Deployments.L1StandardBridgeProxy, new(big.Int).SetUint64(math.MaxUint64))
require.NoError(t, err) require.NoError(t, err)
_, err = wait.ForReceiptOK(context.Background(), l1Client, tx.Hash()) _, err = wait.ForReceiptOK(context.Background(), l1Client, tx.Hash())
require.NoError(t, err) require.NoError(t, err)
// Bridge the WETH9 // Bridge the WETH
l1StandardBridge, err := bindings.NewL1StandardBridge(cfg.L1Deployments.L1StandardBridgeProxy, l1Client) l1StandardBridge, err := bindings.NewL1StandardBridge(cfg.L1Deployments.L1StandardBridgeProxy, l1Client)
require.NoError(t, err) require.NoError(t, err)
tx, err = transactions.PadGasEstimate(opts, 1.1, func(opts *bind.TransactOpts) (*types.Transaction, error) { tx, err = transactions.PadGasEstimate(opts, 1.1, func(opts *bind.TransactOpts) (*types.Transaction, error) {
return l1StandardBridge.BridgeERC20(opts, weth9Address, event.LocalToken, big.NewInt(100), 100000, []byte{}) return l1StandardBridge.BridgeERC20(opts, wethAddress, event.LocalToken, big.NewInt(100), 100000, []byte{})
}) })
require.NoError(t, err) require.NoError(t, err)
depositReceipt, err := wait.ForReceiptOK(context.Background(), l1Client, tx.Hash()) depositReceipt, err := wait.ForReceiptOK(context.Background(), l1Client, tx.Hash())
......
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 356538) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369398)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2954723) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967433)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 549153) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 562077)
GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4061129) GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4074053)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 450282) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467008)
GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3496031) GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512757)
GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 59809) GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72627)
GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92950) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68354) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68453)
GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 69018) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 68945)
GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155567) GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155567)
\ No newline at end of file
...@@ -52,7 +52,6 @@ line_length=120 ...@@ -52,7 +52,6 @@ line_length=120
multiline_func_header='all' multiline_func_header='all'
bracket_spacing=true bracket_spacing=true
wrap_comments=true wrap_comments=true
ignore = ['src/vendor/WETH9.sol']
################################################################ ################################################################
# PROFILE: CI # # PROFILE: CI #
......
# `SystemConfig` Invariants # `SystemConfig` Invariants
## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound. ## The gas limit of the `SystemConfig` contract can never be lower than the hard-coded lower bound.
**Test:** [`SystemConfig.t.sol#L68`](../test/invariants/SystemConfig.t.sol#L68) **Test:** [`SystemConfig.t.sol#L69`](../test/invariants/SystemConfig.t.sol#L69)
...@@ -129,8 +129,8 @@ abstract contract Artifacts { ...@@ -129,8 +129,8 @@ abstract contract Artifacts {
return payable(Predeploys.L1_MESSAGE_SENDER); return payable(Predeploys.L1_MESSAGE_SENDER);
} else if (digest == keccak256(bytes("DeployerWhitelist"))) { } else if (digest == keccak256(bytes("DeployerWhitelist"))) {
return payable(Predeploys.DEPLOYER_WHITELIST); return payable(Predeploys.DEPLOYER_WHITELIST);
} else if (digest == keccak256(bytes("WETH9"))) { } else if (digest == keccak256(bytes("WETH"))) {
return payable(Predeploys.WETH9); return payable(Predeploys.WETH);
} else if (digest == keccak256(bytes("LegacyERC20ETH"))) { } else if (digest == keccak256(bytes("LegacyERC20ETH"))) {
return payable(Predeploys.LEGACY_ERC20_ETH); return payable(Predeploys.LEGACY_ERC20_ETH);
} else if (digest == keccak256(bytes("L1BlockNumber"))) { } else if (digest == keccak256(bytes("L1BlockNumber"))) {
......
...@@ -380,16 +380,6 @@ contract Deploy is Deployer { ...@@ -380,16 +380,6 @@ contract Deploy is Deployer {
/// @notice Initialize all of the implementations /// @notice Initialize all of the implementations
function initializeImplementations() public { function initializeImplementations() public {
console.log("Initializing implementations"); console.log("Initializing implementations");
initializeSystemConfig();
initializeL1StandardBridge();
initializeL1ERC721Bridge();
initializeOptimismMintableERC20Factory();
initializeL1CrossDomainMessenger();
initializeL2OutputOracle();
initializeDisputeGameFactory();
initializeDelayedWETH();
initializeAnchorStateRegistry();
// Selectively initialize either the original OptimismPortal or the new OptimismPortal2. Since this will upgrade // Selectively initialize either the original OptimismPortal or the new OptimismPortal2. Since this will upgrade
// the proxy, we cannot initialize both. FPAC warning can be removed once we're done with the old OptimismPortal // the proxy, we cannot initialize both. FPAC warning can be removed once we're done with the old OptimismPortal
// contract. // contract.
...@@ -399,6 +389,16 @@ contract Deploy is Deployer { ...@@ -399,6 +389,16 @@ contract Deploy is Deployer {
} else { } else {
initializeOptimismPortal(); initializeOptimismPortal();
} }
initializeSystemConfig();
initializeL1StandardBridge();
initializeL1ERC721Bridge();
initializeOptimismMintableERC20Factory();
initializeL1CrossDomainMessenger();
initializeL2OutputOracle();
initializeDisputeGameFactory();
initializeDelayedWETH();
initializeAnchorStateRegistry();
} }
/// @notice Add Plasma setup to the OP chain /// @notice Add Plasma setup to the OP chain
...@@ -945,6 +945,11 @@ contract Deploy is Deployer { ...@@ -945,6 +945,11 @@ contract Deploy is Deployer {
bytes32 batcherHash = bytes32(uint256(uint160(cfg.batchSenderAddress()))); bytes32 batcherHash = bytes32(uint256(uint160(cfg.batchSenderAddress())));
address customGasTokenAddress = Constants.ETHER;
if (cfg.useCustomGasToken()) {
customGasTokenAddress = cfg.customGasTokenAddress();
}
_upgradeAndCallViaSafe({ _upgradeAndCallViaSafe({
_proxy: payable(systemConfigProxy), _proxy: payable(systemConfigProxy),
_implementation: systemConfig, _implementation: systemConfig,
...@@ -965,7 +970,8 @@ contract Deploy is Deployer { ...@@ -965,7 +970,8 @@ contract Deploy is Deployer {
l1StandardBridge: mustGetAddress("L1StandardBridgeProxy"), l1StandardBridge: mustGetAddress("L1StandardBridgeProxy"),
disputeGameFactory: mustGetAddress("DisputeGameFactoryProxy"), disputeGameFactory: mustGetAddress("DisputeGameFactoryProxy"),
optimismPortal: mustGetAddress("OptimismPortalProxy"), optimismPortal: mustGetAddress("OptimismPortalProxy"),
optimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy") optimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"),
gasPayingToken: customGasTokenAddress
}) })
) )
) )
...@@ -986,6 +992,7 @@ contract Deploy is Deployer { ...@@ -986,6 +992,7 @@ contract Deploy is Deployer {
address l1StandardBridge = mustGetAddress("L1StandardBridge"); address l1StandardBridge = mustGetAddress("L1StandardBridge");
address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy"); address l1CrossDomainMessengerProxy = mustGetAddress("L1CrossDomainMessengerProxy");
address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy");
address systemConfigProxy = mustGetAddress("SystemConfigProxy");
uint256 proxyType = uint256(proxyAdmin.proxyType(l1StandardBridgeProxy)); uint256 proxyType = uint256(proxyAdmin.proxyType(l1StandardBridgeProxy));
if (proxyType != uint256(ProxyAdmin.ProxyType.CHUGSPLASH)) { if (proxyType != uint256(ProxyAdmin.ProxyType.CHUGSPLASH)) {
...@@ -1001,7 +1008,11 @@ contract Deploy is Deployer { ...@@ -1001,7 +1008,11 @@ contract Deploy is Deployer {
_implementation: l1StandardBridge, _implementation: l1StandardBridge,
_innerCallData: abi.encodeCall( _innerCallData: abi.encodeCall(
L1StandardBridge.initialize, L1StandardBridge.initialize,
(L1CrossDomainMessenger(l1CrossDomainMessengerProxy), SuperchainConfig(superchainConfigProxy)) (
L1CrossDomainMessenger(l1CrossDomainMessengerProxy),
SuperchainConfig(superchainConfigProxy),
SystemConfig(systemConfigProxy)
)
) )
}); });
...@@ -1063,6 +1074,7 @@ contract Deploy is Deployer { ...@@ -1063,6 +1074,7 @@ contract Deploy is Deployer {
address l1CrossDomainMessenger = mustGetAddress("L1CrossDomainMessenger"); address l1CrossDomainMessenger = mustGetAddress("L1CrossDomainMessenger");
address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy"); address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy");
address optimismPortalProxy = mustGetAddress("OptimismPortalProxy"); address optimismPortalProxy = mustGetAddress("OptimismPortalProxy");
address systemConfigProxy = mustGetAddress("SystemConfigProxy");
uint256 proxyType = uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy)); uint256 proxyType = uint256(proxyAdmin.proxyType(l1CrossDomainMessengerProxy));
if (proxyType != uint256(ProxyAdmin.ProxyType.RESOLVED)) { if (proxyType != uint256(ProxyAdmin.ProxyType.RESOLVED)) {
...@@ -1091,7 +1103,11 @@ contract Deploy is Deployer { ...@@ -1091,7 +1103,11 @@ contract Deploy is Deployer {
_implementation: l1CrossDomainMessenger, _implementation: l1CrossDomainMessenger,
_innerCallData: abi.encodeCall( _innerCallData: abi.encodeCall(
L1CrossDomainMessenger.initialize, L1CrossDomainMessenger.initialize,
(SuperchainConfig(superchainConfigProxy), OptimismPortal(payable(optimismPortalProxy))) (
SuperchainConfig(superchainConfigProxy),
OptimismPortal(payable(optimismPortalProxy)),
SystemConfig(systemConfigProxy)
)
) )
}); });
......
...@@ -74,6 +74,9 @@ contract DeployConfig is Script { ...@@ -74,6 +74,9 @@ contract DeployConfig is Script {
uint256 public daBondSize; uint256 public daBondSize;
uint256 public daResolverRefundPercentage; uint256 public daResolverRefundPercentage;
bool public useCustomGasToken;
address public customGasTokenAddress;
function read(string memory _path) public { function read(string memory _path) public {
console.log("DeployConfig: reading file %s", _path); console.log("DeployConfig: reading file %s", _path);
try vm.readFile(_path) returns (string memory data) { try vm.readFile(_path) returns (string memory data) {
...@@ -145,6 +148,9 @@ contract DeployConfig is Script { ...@@ -145,6 +148,9 @@ contract DeployConfig is Script {
daResolveWindow = _readOr(_json, "$.daResolveWindow", 1000); daResolveWindow = _readOr(_json, "$.daResolveWindow", 1000);
daBondSize = _readOr(_json, "$.daBondSize", 1000000000); daBondSize = _readOr(_json, "$.daBondSize", 1000000000);
daResolverRefundPercentage = _readOr(_json, "$.daResolverRefundPercentage", 0); daResolverRefundPercentage = _readOr(_json, "$.daResolverRefundPercentage", 0);
useCustomGasToken = _readOr(_json, "$.useCustomGasToken", false);
customGasTokenAddress = _readOr(_json, "$.customGasTokenAddress", address(0));
} }
function l1StartingBlockTag() public returns (bytes32) { function l1StartingBlockTag() public returns (bytes32) {
...@@ -190,6 +196,12 @@ contract DeployConfig is Script { ...@@ -190,6 +196,12 @@ contract DeployConfig is Script {
fundDevAccounts = _fundDevAccounts; fundDevAccounts = _fundDevAccounts;
} }
/// @notice Allow the `useCustomGasToken` config to be overridden in testing environments
function setUseCustomGasToken(address _token) public {
useCustomGasToken = true;
customGasTokenAddress = _token;
}
function _getBlockByTag(string memory _tag) internal returns (bytes32) { function _getBlockByTag(string memory _tag) internal returns (bytes32) {
string[] memory cmd = new string[](3); string[] memory cmd = new string[](3);
cmd[0] = Executables.bash; cmd[0] = Executables.bash;
...@@ -206,4 +218,8 @@ contract DeployConfig is Script { ...@@ -206,4 +218,8 @@ contract DeployConfig is Script {
function _readOr(string memory json, string memory key, uint256 defaultValue) internal view returns (uint256) { function _readOr(string memory json, string memory key, uint256 defaultValue) internal view returns (uint256) {
return vm.keyExists(json, key) ? stdJson.readUint(json, key) : defaultValue; return vm.keyExists(json, key) ? stdJson.readUint(json, key) : defaultValue;
} }
function _readOr(string memory json, string memory key, address defaultValue) internal view returns (address) {
return vm.keyExists(json, key) ? stdJson.readAddress(json, key) : defaultValue;
}
} }
...@@ -216,7 +216,7 @@ contract L2Genesis is Deployer { ...@@ -216,7 +216,7 @@ contract L2Genesis is Deployer {
// 01: legacy, not used in OP-Stack // 01: legacy, not used in OP-Stack
setDeployerWhitelist(); // 2 setDeployerWhitelist(); // 2
// 3,4,5: legacy, not used in OP-Stack. // 3,4,5: legacy, not used in OP-Stack.
setWETH9(); // 6: WETH9 (not behind a proxy) setWETH(); // 6: WETH (not behind a proxy)
setL2CrossDomainMessenger(_l1Dependencies.l1CrossDomainMessengerProxy); // 7 setL2CrossDomainMessenger(_l1Dependencies.l1CrossDomainMessengerProxy); // 7
// 8,9,A,B,C,D,E: legacy, not used in OP-Stack. // 8,9,A,B,C,D,E: legacy, not used in OP-Stack.
setGasPriceOracle(); // f setGasPriceOracle(); // f
...@@ -346,31 +346,9 @@ contract L2Genesis is Deployer { ...@@ -346,31 +346,9 @@ contract L2Genesis is Deployer {
/// @notice This predeploy is following the safety invariant #1. /// @notice This predeploy is following the safety invariant #1.
/// This contract is NOT proxied and the state that is set /// This contract is NOT proxied and the state that is set
/// in the constructor is set manually. /// in the constructor is set manually.
function setWETH9() public { function setWETH() public {
console.log("Setting %s implementation at: %s", "WETH9", Predeploys.WETH9); console.log("Setting %s implementation at: %s", "WETH", Predeploys.WETH);
vm.etch(Predeploys.WETH9, vm.getDeployedCode("WETH9.sol:WETH9")); vm.etch(Predeploys.WETH, vm.getDeployedCode("WETH.sol:WETH"));
vm.store(
Predeploys.WETH9,
/// string public name
hex"0000000000000000000000000000000000000000000000000000000000000000",
/// "Wrapped Ether"
hex"577261707065642045746865720000000000000000000000000000000000001a"
);
vm.store(
Predeploys.WETH9,
/// string public symbol
hex"0000000000000000000000000000000000000000000000000000000000000001",
/// "WETH"
hex"5745544800000000000000000000000000000000000000000000000000000008"
);
vm.store(
Predeploys.WETH9,
// uint8 public decimals
hex"0000000000000000000000000000000000000000000000000000000000000002",
/// 18
hex"0000000000000000000000000000000000000000000000000000000000000012"
);
} }
/// @notice This predeploy is following the safety invariant #1. /// @notice This predeploy is following the safety invariant #1.
......
...@@ -16,24 +16,24 @@ ...@@ -16,24 +16,24 @@
"sourceCodeHash": "0xc59b8574531162e016d7342aeb6e79d05574e90dbea6c0e5ede35b65010ad894" "sourceCodeHash": "0xc59b8574531162e016d7342aeb6e79d05574e90dbea6c0e5ede35b65010ad894"
}, },
"src/L1/L1CrossDomainMessenger.sol": { "src/L1/L1CrossDomainMessenger.sol": {
"initCodeHash": "0xb0b3273999191e4ff616509e3a368a9d89f7967c20ba73c1bc5bd72bd13acb16", "initCodeHash": "0xff2c621c5ae8f1190779b13d8da4ee9fcd64f202d6e5ee309f0028bca17a8b8f",
"sourceCodeHash": "0x6c86ba9f20f54ac834ad846e6302f892b7576dd407db595ebaba017971471eb4" "sourceCodeHash": "0x56a2d3abed97eb7b292db758aac1e36fc08a12bfa44f7969824e26866a1417fa"
}, },
"src/L1/L1ERC721Bridge.sol": { "src/L1/L1ERC721Bridge.sol": {
"initCodeHash": "0xec73b46e68ea29298707f7b9709e7948afe303907b6c4e8b161e2ded2c85ee9c", "initCodeHash": "0xec73b46e68ea29298707f7b9709e7948afe303907b6c4e8b161e2ded2c85ee9c",
"sourceCodeHash": "0xd57acdbd001941e75cf4326ba7c1bdad809912f10b1e44ffaebe073917cdd296" "sourceCodeHash": "0xd57acdbd001941e75cf4326ba7c1bdad809912f10b1e44ffaebe073917cdd296"
}, },
"src/L1/L1StandardBridge.sol": { "src/L1/L1StandardBridge.sol": {
"initCodeHash": "0xb0166b741e0b6938c004c19de78f347171e2656056a0363434ebd0f34a6a32ca", "initCodeHash": "0xf46dc2d8e171bd5aebbafe31386e9d27323a124b1b99f378cb8f67ab22225a7b",
"sourceCodeHash": "0x654f9490dd2d0bc9e92dc31781e62f106f6b7d8b6fca0cfe718cd2a599c1b18b" "sourceCodeHash": "0x0d07e526c1891abb81c7e94f73642caa8ee386ab036b3b2a67f1b21ca85089c5"
}, },
"src/L1/L2OutputOracle.sol": { "src/L1/L2OutputOracle.sol": {
"initCodeHash": "0x14c3a582ca46ef2a6abad5590323f4de26ff4de54415c927c62e131ccbf8d9ba", "initCodeHash": "0x14c3a582ca46ef2a6abad5590323f4de26ff4de54415c927c62e131ccbf8d9ba",
"sourceCodeHash": "0xf5fcf570721e25459fadbb37e02f9efe349e1c8afcbf1e3b5fdb09c9f612cdc0" "sourceCodeHash": "0xf5fcf570721e25459fadbb37e02f9efe349e1c8afcbf1e3b5fdb09c9f612cdc0"
}, },
"src/L1/OptimismPortal.sol": { "src/L1/OptimismPortal.sol": {
"initCodeHash": "0xe7aa232aaf0826d0d9129e2fae35979e9d5ad8dbf0d8d23886a5ef851e3f3a81", "initCodeHash": "0x8ca0ac98c37ad221d679c744b295b1b38742bfebac050f789197d658951709f5",
"sourceCodeHash": "0xd3450653cecc14bf8fbc21f2fa9b4a5fde348c2b6313d72e74e08666201295a2" "sourceCodeHash": "0x8e9221539290a9b3cfb933d30ab77b137e2a76a1481671e41469ed1014ffbdc4"
}, },
"src/L1/OptimismPortal2.sol": { "src/L1/OptimismPortal2.sol": {
"initCodeHash": "0xea32d79e8297956d4f9a4c7985bb53ff8bb3735e5b307d4e118fea71f503a38e", "initCodeHash": "0xea32d79e8297956d4f9a4c7985bb53ff8bb3735e5b307d4e118fea71f503a38e",
...@@ -48,8 +48,8 @@ ...@@ -48,8 +48,8 @@
"sourceCodeHash": "0xd6a894e371c2c7182b5960c507491f81c3775dda0efedd29475f7c30ca07b004" "sourceCodeHash": "0xd6a894e371c2c7182b5960c507491f81c3775dda0efedd29475f7c30ca07b004"
}, },
"src/L1/SystemConfig.sol": { "src/L1/SystemConfig.sol": {
"initCodeHash": "0xa7419d85404d5ea842025ad8fdffb7a0edbb80ef3e6e454b593d50e6bb0ca6e4", "initCodeHash": "0xace1ef0e25a5416a686c7555acd20e9b0191ab2a8c8320d57606ed12092ccbb6",
"sourceCodeHash": "0xb29beac61b1f4d0ac7ef0f2b3201eb19b5e6f0c1c2b08892268f38fcb4166066" "sourceCodeHash": "0x1f35f55aa230a686f6457e42bd08acc8bd94d691f89ac23a4b2efd7cc1eefdc6"
}, },
"src/L2/BaseFeeVault.sol": { "src/L2/BaseFeeVault.sol": {
"initCodeHash": "0x2744d34573be83206d1b75d049d18a7bb37f9058e68c0803e5008c46b0dc2474", "initCodeHash": "0x2744d34573be83206d1b75d049d18a7bb37f9058e68c0803e5008c46b0dc2474",
...@@ -64,28 +64,28 @@ ...@@ -64,28 +64,28 @@
"sourceCodeHash": "0xde06becce9514f46ba78b4cb0732c7a714d49ba8f131258d56a5f5b22b51be7e" "sourceCodeHash": "0xde06becce9514f46ba78b4cb0732c7a714d49ba8f131258d56a5f5b22b51be7e"
}, },
"src/L2/L1Block.sol": { "src/L2/L1Block.sol": {
"initCodeHash": "0x42d17698899f06e6cf38823bea3dbaff32ba32ebcb2974f928f93d005a3ea91b", "initCodeHash": "0x00961e82f3ed7f7755115c897304063e283bff0bed1200d50e0abe5c59424069",
"sourceCodeHash": "0x723fb55d63110783697aa8e9bba017c267b9e0a24639ec684d3147e47b68ae28" "sourceCodeHash": "0x9c19260697d5ed5f85318098dead3f5882d77f1ee87233da1b47d1e87c88bce8"
}, },
"src/L2/L1BlockInterop.sol": { "src/L2/L1BlockInterop.sol": {
"initCodeHash": "0xddac768887de4053c8e8b92bc9bc621c9bb31df90018be4308dd4e21b1b1085f", "initCodeHash": "0x2929683ae3e4f76fca87462f0217b6e24c418bc964328f8d095f0e90343d2cae",
"sourceCodeHash": "0xc93a12fbf00ace5f92051f4a57bb3584b5b773c33fd3c40be771c3b4fbf878e9" "sourceCodeHash": "0x78ca58451d973e9896eeb9d19699943b7a4e96c36da2724f47d5aafdf243abcd"
}, },
"src/L2/L1FeeVault.sol": { "src/L2/L1FeeVault.sol": {
"initCodeHash": "0x2744d34573be83206d1b75d049d18a7bb37f9058e68c0803e5008c46b0dc2474", "initCodeHash": "0x2744d34573be83206d1b75d049d18a7bb37f9058e68c0803e5008c46b0dc2474",
"sourceCodeHash": "0x3a94f273937d8908fb37dd2c495a6a0b9c3941fe68ccea51723f84eb343ba225" "sourceCodeHash": "0x3a94f273937d8908fb37dd2c495a6a0b9c3941fe68ccea51723f84eb343ba225"
}, },
"src/L2/L2CrossDomainMessenger.sol": { "src/L2/L2CrossDomainMessenger.sol": {
"initCodeHash": "0xfeedd51cc3159f814f6aee9e54cbba1c9a2fa4079fa40c00871889bc1c98c902", "initCodeHash": "0x5d1d1c0c3cc2171832438cbafb6e55c142eab43a7d7ea09ee818a7e3c50d9314",
"sourceCodeHash": "0x9993c85445b4a00153d7bbfb8259d920d0b1b6c9fd557c65ad8df47119e55275" "sourceCodeHash": "0x440d299b7429c44f6faa4005b33428f9edc1283027d9c78a63eb3d76452bfa47"
}, },
"src/L2/L2ERC721Bridge.sol": { "src/L2/L2ERC721Bridge.sol": {
"initCodeHash": "0xcafa012b2d8f1bb05c11cbbff9749c0fe6f995c9afb1d26d2d71f03384e34a22", "initCodeHash": "0xcafa012b2d8f1bb05c11cbbff9749c0fe6f995c9afb1d26d2d71f03384e34a22",
"sourceCodeHash": "0xa7646a588275046f92525ef121e5a0fe149e7752ea51fe62f7e0686a21153542" "sourceCodeHash": "0xa7646a588275046f92525ef121e5a0fe149e7752ea51fe62f7e0686a21153542"
}, },
"src/L2/L2StandardBridge.sol": { "src/L2/L2StandardBridge.sol": {
"initCodeHash": "0xe16aed4fc021c768d0ebcb8bb83db7cfe6b062663d034df9f351cdc6af2e5077", "initCodeHash": "0x4d7e23702aa5627f78842b2b06e0ee0eec1ea63cf6552b652f4c8bdb56e1c8f4",
"sourceCodeHash": "0xc86e370a8cd1277b203d18df154a50aed16b27156b755e6bd6672d068d44b9f2" "sourceCodeHash": "0x55a865c85406b3e1414ab4e9f03a902855300272321b4eebcdfdc284e51fb2df"
}, },
"src/L2/L2ToL1MessagePasser.sol": { "src/L2/L2ToL1MessagePasser.sol": {
"initCodeHash": "0x08bbede75cd6dfd076903b8f04d24f82fa7881576c135825098778632e37eebc", "initCodeHash": "0x08bbede75cd6dfd076903b8f04d24f82fa7881576c135825098778632e37eebc",
......
...@@ -5,11 +5,11 @@ ...@@ -5,11 +5,11 @@
"outputs": [ "outputs": [
{ {
"internalType": "address", "internalType": "address",
"name": "", "name": "addr_",
"type": "address" "type": "address"
} }
], ],
"stateMutability": "view", "stateMutability": "pure",
"type": "function" "type": "function"
}, },
{ {
...@@ -77,6 +77,50 @@ ...@@ -77,6 +77,50 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "gasPayingToken",
"outputs": [
{
"internalType": "address",
"name": "addr_",
"type": "address"
},
{
"internalType": "uint8",
"name": "decimals_",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gasPayingTokenName",
"outputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gasPayingTokenSymbol",
"outputs": [
{
"internalType": "string",
"name": "symbol_",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "hash", "name": "hash",
...@@ -90,6 +134,19 @@ ...@@ -90,6 +134,19 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "isCustomGasToken",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "l1FeeOverhead", "name": "l1FeeOverhead",
...@@ -142,6 +199,34 @@ ...@@ -142,6 +199,34 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "address",
"name": "_token",
"type": "address"
},
{
"internalType": "uint8",
"name": "_decimals",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "_name",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_symbol",
"type": "bytes32"
}
],
"name": "setGasPayingToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -222,5 +307,41 @@ ...@@ -222,5 +307,41 @@
], ],
"stateMutability": "pure", "stateMutability": "pure",
"type": "function" "type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "uint8",
"name": "decimals",
"type": "uint8"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "name",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "symbol",
"type": "bytes32"
}
],
"name": "GasPayingTokenSet",
"type": "event"
},
{
"inputs": [],
"name": "NotDepositor",
"type": "error"
} }
] ]
\ No newline at end of file
...@@ -5,11 +5,11 @@ ...@@ -5,11 +5,11 @@
"outputs": [ "outputs": [
{ {
"internalType": "address", "internalType": "address",
"name": "", "name": "addr_",
"type": "address" "type": "address"
} }
], ],
"stateMutability": "view", "stateMutability": "pure",
"type": "function" "type": "function"
}, },
{ {
...@@ -109,6 +109,50 @@ ...@@ -109,6 +109,50 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "gasPayingToken",
"outputs": [
{
"internalType": "address",
"name": "addr_",
"type": "address"
},
{
"internalType": "uint8",
"name": "decimals_",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gasPayingTokenName",
"outputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gasPayingTokenSymbol",
"outputs": [
{
"internalType": "string",
"name": "symbol_",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "hash", "name": "hash",
...@@ -122,6 +166,19 @@ ...@@ -122,6 +166,19 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "isCustomGasToken",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -193,6 +250,34 @@ ...@@ -193,6 +250,34 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "address",
"name": "_token",
"type": "address"
},
{
"internalType": "uint8",
"name": "_decimals",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "_name",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_symbol",
"type": "bytes32"
}
],
"name": "setGasPayingToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -280,5 +365,41 @@ ...@@ -280,5 +365,41 @@
], ],
"stateMutability": "pure", "stateMutability": "pure",
"type": "function" "type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "uint8",
"name": "decimals",
"type": "uint8"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "name",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "symbol",
"type": "bytes32"
}
],
"name": "GasPayingTokenSet",
"type": "event"
},
{
"inputs": [],
"name": "NotDepositor",
"type": "error"
} }
] ]
\ No newline at end of file
...@@ -188,6 +188,11 @@ ...@@ -188,6 +188,11 @@
"internalType": "contract OptimismPortal", "internalType": "contract OptimismPortal",
"name": "_portal", "name": "_portal",
"type": "address" "type": "address"
},
{
"internalType": "contract SystemConfig",
"name": "_systemConfig",
"type": "address"
} }
], ],
"name": "initialize", "name": "initialize",
...@@ -340,6 +345,19 @@ ...@@ -340,6 +345,19 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "systemConfig",
"outputs": [
{
"internalType": "contract SystemConfig",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "version", "name": "version",
......
...@@ -425,6 +425,11 @@ ...@@ -425,6 +425,11 @@
"internalType": "contract SuperchainConfig", "internalType": "contract SuperchainConfig",
"name": "_superchainConfig", "name": "_superchainConfig",
"type": "address" "type": "address"
},
{
"internalType": "contract SystemConfig",
"name": "_systemConfig",
"type": "address"
} }
], ],
"name": "initialize", "name": "initialize",
...@@ -497,6 +502,19 @@ ...@@ -497,6 +502,19 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "systemConfig",
"outputs": [
{
"internalType": "contract SystemConfig",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "version", "name": "version",
......
...@@ -8,6 +8,57 @@ ...@@ -8,6 +8,57 @@
"stateMutability": "payable", "stateMutability": "payable",
"type": "receive" "type": "receive"
}, },
{
"inputs": [],
"name": "balance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_mint",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_value",
"type": "uint256"
},
{
"internalType": "uint64",
"name": "_gasLimit",
"type": "uint64"
},
{
"internalType": "bool",
"name": "_isCreation",
"type": "bool"
},
{
"internalType": "bytes",
"name": "_data",
"type": "bytes"
}
],
"name": "depositERC20Transaction",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -359,6 +410,34 @@ ...@@ -359,6 +410,34 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "address",
"name": "_token",
"type": "address"
},
{
"internalType": "uint8",
"name": "_decimals",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "_name",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "_symbol",
"type": "bytes32"
}
],
"name": "setGasPayingToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "superchainConfig", "name": "superchainConfig",
...@@ -506,6 +585,21 @@ ...@@ -506,6 +585,21 @@
"name": "LargeCalldata", "name": "LargeCalldata",
"type": "error" "type": "error"
}, },
{
"inputs": [],
"name": "NoValue",
"type": "error"
},
{
"inputs": [],
"name": "NonReentrant",
"type": "error"
},
{
"inputs": [],
"name": "OnlyCustomGasToken",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "OutOfGas", "name": "OutOfGas",
...@@ -515,5 +609,15 @@ ...@@ -515,5 +609,15 @@
"inputs": [], "inputs": [],
"name": "SmallGasLimit", "name": "SmallGasLimit",
"type": "error" "type": "error"
},
{
"inputs": [],
"name": "TransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"type": "error"
} }
] ]
\ No newline at end of file
...@@ -186,6 +186,50 @@ ...@@ -186,6 +186,50 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "gasPayingToken",
"outputs": [
{
"internalType": "address",
"name": "addr_",
"type": "address"
},
{
"internalType": "uint8",
"name": "decimals_",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gasPayingTokenName",
"outputs": [
{
"internalType": "string",
"name": "name_",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gasPayingTokenSymbol",
"outputs": [
{
"internalType": "string",
"name": "symbol_",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -291,6 +335,11 @@ ...@@ -291,6 +335,11 @@
"internalType": "address", "internalType": "address",
"name": "optimismMintableERC20Factory", "name": "optimismMintableERC20Factory",
"type": "address" "type": "address"
},
{
"internalType": "address",
"name": "gasPayingToken",
"type": "address"
} }
], ],
"internalType": "struct SystemConfig.Addresses", "internalType": "struct SystemConfig.Addresses",
...@@ -303,6 +352,19 @@ ...@@ -303,6 +352,19 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "isCustomGasToken",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [], "inputs": [],
"name": "l1CrossDomainMessenger", "name": "l1CrossDomainMessenger",
......
...@@ -3,6 +3,10 @@ ...@@ -3,6 +3,10 @@
"stateMutability": "payable", "stateMutability": "payable",
"type": "fallback" "type": "fallback"
}, },
{
"stateMutability": "payable",
"type": "receive"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -96,7 +100,7 @@ ...@@ -96,7 +100,7 @@
"outputs": [ "outputs": [
{ {
"internalType": "string", "internalType": "string",
"name": "", "name": "name_",
"type": "string" "type": "string"
} }
], ],
...@@ -109,7 +113,7 @@ ...@@ -109,7 +113,7 @@
"outputs": [ "outputs": [
{ {
"internalType": "string", "internalType": "string",
"name": "", "name": "symbol_",
"type": "string" "type": "string"
} }
], ],
......
...@@ -138,5 +138,12 @@ ...@@ -138,5 +138,12 @@
"offset": 0, "offset": 0,
"slot": "252", "slot": "252",
"type": "contract OptimismPortal" "type": "contract OptimismPortal"
},
{
"bytes": "20",
"label": "systemConfig",
"offset": 0,
"slot": "253",
"type": "contract SystemConfig"
} }
] ]
\ No newline at end of file
...@@ -61,5 +61,12 @@ ...@@ -61,5 +61,12 @@
"offset": 0, "offset": 0,
"slot": "50", "slot": "50",
"type": "contract SuperchainConfig" "type": "contract SuperchainConfig"
},
{
"bytes": "20",
"label": "systemConfig",
"offset": 0,
"slot": "51",
"type": "contract SystemConfig"
} }
] ]
\ No newline at end of file
...@@ -75,5 +75,47 @@ ...@@ -75,5 +75,47 @@
"offset": 0, "offset": 0,
"slot": "55", "slot": "55",
"type": "contract SystemConfig" "type": "contract SystemConfig"
},
{
"bytes": "20",
"label": "spacer_56_0_20",
"offset": 0,
"slot": "56",
"type": "address"
},
{
"bytes": "32",
"label": "spacer_57_0_32",
"offset": 0,
"slot": "57",
"type": "bytes32"
},
{
"bytes": "32",
"label": "spacer_58_0_32",
"offset": 0,
"slot": "58",
"type": "bytes32"
},
{
"bytes": "32",
"label": "spacer_59_0_32",
"offset": 0,
"slot": "59",
"type": "bytes32"
},
{
"bytes": "32",
"label": "spacer_60_0_32",
"offset": 0,
"slot": "60",
"type": "bytes32"
},
{
"bytes": "32",
"label": "_balance",
"offset": 0,
"slot": "61",
"type": "uint256"
} }
] ]
\ No newline at end of file
[ [
{
"bytes": "32",
"label": "name",
"offset": 0,
"slot": "0",
"type": "string"
},
{
"bytes": "32",
"label": "symbol",
"offset": 0,
"slot": "1",
"type": "string"
},
{
"bytes": "1",
"label": "decimals",
"offset": 0,
"slot": "2",
"type": "uint8"
},
{ {
"bytes": "32", "bytes": "32",
"label": "balanceOf", "label": "balanceOf",
"offset": 0, "offset": 0,
"slot": "3", "slot": "0",
"type": "mapping(address => uint256)" "type": "mapping(address => uint256)"
}, },
{ {
"bytes": "32", "bytes": "32",
"label": "allowance", "label": "allowance",
"offset": 0, "offset": 0,
"slot": "4", "slot": "1",
"type": "mapping(address => mapping(address => uint256))" "type": "mapping(address => mapping(address => uint256))"
} }
] ]
\ No newline at end of file
...@@ -6,6 +6,7 @@ import { OptimismPortal } from "src/L1/OptimismPortal.sol"; ...@@ -6,6 +6,7 @@ import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
import { ISemver } from "src/universal/ISemver.sol"; import { ISemver } from "src/universal/ISemver.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
/// @custom:proxied /// @custom:proxied
/// @title L1CrossDomainMessenger /// @title L1CrossDomainMessenger
...@@ -20,24 +21,45 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver { ...@@ -20,24 +21,45 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver {
/// @custom:network-specific /// @custom:network-specific
OptimismPortal public portal; OptimismPortal public portal;
/// @notice Address of the SystemConfig contract.
SystemConfig public systemConfig;
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 2.3.0 /// @custom:semver 2.4.0
string public constant version = "2.3.0"; string public constant version = "2.4.0";
/// @notice Constructs the L1CrossDomainMessenger contract. /// @notice Constructs the L1CrossDomainMessenger contract.
constructor() CrossDomainMessenger() { constructor() CrossDomainMessenger() {
initialize({ _superchainConfig: SuperchainConfig(address(0)), _portal: OptimismPortal(payable(address(0))) }); initialize({
_superchainConfig: SuperchainConfig(address(0)),
_portal: OptimismPortal(payable(address(0))),
_systemConfig: SystemConfig(address(0))
});
} }
/// @notice Initializes the contract. /// @notice Initializes the contract.
/// @param _superchainConfig Contract of the SuperchainConfig contract on this network. /// @param _superchainConfig Contract of the SuperchainConfig contract on this network.
/// @param _portal Contract of the OptimismPortal contract on this network. /// @param _portal Contract of the OptimismPortal contract on this network.
function initialize(SuperchainConfig _superchainConfig, OptimismPortal _portal) public initializer { /// @param _systemConfig Contract of the SystemConfig contract on this network.
function initialize(
SuperchainConfig _superchainConfig,
OptimismPortal _portal,
SystemConfig _systemConfig
)
public
initializer
{
superchainConfig = _superchainConfig; superchainConfig = _superchainConfig;
portal = _portal; portal = _portal;
systemConfig = _systemConfig;
__CrossDomainMessenger_init({ _otherMessenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER) }); __CrossDomainMessenger_init({ _otherMessenger: CrossDomainMessenger(Predeploys.L2_CROSS_DOMAIN_MESSENGER) });
} }
/// @inheritdoc CrossDomainMessenger
function gasPayingToken() internal view override returns (address _addr, uint8 _decimals) {
(_addr, _decimals) = systemConfig.gasPayingToken();
}
/// @notice Getter function for the OptimismPortal contract on this chain. /// @notice Getter function for the OptimismPortal contract on this chain.
/// Public getter is legacy and will be removed in the future. Use `portal()` instead. /// Public getter is legacy and will be removed in the future. Use `portal()` instead.
/// @return Contract of the OptimismPortal on this chain. /// @return Contract of the OptimismPortal on this chain.
......
...@@ -6,7 +6,8 @@ import { StandardBridge } from "src/universal/StandardBridge.sol"; ...@@ -6,7 +6,8 @@ import { StandardBridge } from "src/universal/StandardBridge.sol";
import { ISemver } from "src/universal/ISemver.sol"; import { ISemver } from "src/universal/ISemver.sol";
import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { Constants } from "src/libraries/Constants.sol"; import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
/// @custom:proxied /// @custom:proxied
/// @title L1StandardBridge /// @title L1StandardBridge
...@@ -70,22 +71,37 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -70,22 +71,37 @@ contract L1StandardBridge is StandardBridge, ISemver {
); );
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 2.1.0 /// @custom:semver 2.2.0
string public constant version = "2.1.0"; string public constant version = "2.2.0";
/// @notice Address of the SuperchainConfig contract. /// @notice Address of the SuperchainConfig contract.
SuperchainConfig public superchainConfig; SuperchainConfig public superchainConfig;
/// @notice Address of the SystemConfig contract.
SystemConfig public systemConfig;
/// @notice Constructs the L1StandardBridge contract. /// @notice Constructs the L1StandardBridge contract.
constructor() StandardBridge() { constructor() StandardBridge() {
initialize({ _messenger: CrossDomainMessenger(address(0)), _superchainConfig: SuperchainConfig(address(0)) }); initialize({
_messenger: CrossDomainMessenger(address(0)),
_superchainConfig: SuperchainConfig(address(0)),
_systemConfig: SystemConfig(address(0))
});
} }
/// @notice Initializer. /// @notice Initializer.
/// @param _messenger Contract for the CrossDomainMessenger on this network. /// @param _messenger Contract for the CrossDomainMessenger on this network.
/// @param _superchainConfig Contract for the SuperchainConfig on this network. /// @param _superchainConfig Contract for the SuperchainConfig on this network.
function initialize(CrossDomainMessenger _messenger, SuperchainConfig _superchainConfig) public initializer { function initialize(
CrossDomainMessenger _messenger,
SuperchainConfig _superchainConfig,
SystemConfig _systemConfig
)
public
initializer
{
superchainConfig = _superchainConfig; superchainConfig = _superchainConfig;
systemConfig = _systemConfig;
__StandardBridge_init({ __StandardBridge_init({
_messenger: _messenger, _messenger: _messenger,
_otherBridge: StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE)) _otherBridge: StandardBridge(payable(Predeploys.L2_STANDARD_BRIDGE))
...@@ -102,6 +118,11 @@ contract L1StandardBridge is StandardBridge, ISemver { ...@@ -102,6 +118,11 @@ contract L1StandardBridge is StandardBridge, ISemver {
_initiateETHDeposit(msg.sender, msg.sender, RECEIVE_DEFAULT_GAS_LIMIT, bytes("")); _initiateETHDeposit(msg.sender, msg.sender, RECEIVE_DEFAULT_GAS_LIMIT, bytes(""));
} }
/// @inheritdoc StandardBridge
function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) {
(addr_, decimals_) = systemConfig.gasPayingToken();
}
/// @custom:legacy /// @custom:legacy
/// @notice Deposits some amount of ETH into the sender's account on L2. /// @notice Deposits some amount of ETH into the sender's account on L2.
/// @param _minGasLimit Minimum gas limit for the deposit message on L2. /// @param _minGasLimit Minimum gas limit for the deposit message on L2.
......
...@@ -147,6 +147,13 @@ abstract contract ResourceMetering is Initializable { ...@@ -147,6 +147,13 @@ abstract contract ResourceMetering is Initializable {
} }
} }
/// @notice Adds an amount of L2 gas consumed to the prev bought gas params. This is meant to be used
/// when L2 system transactions are generated from L1.
/// @param _amount Amount of the L2 gas resource requested.
function useGas(uint32 _amount) internal {
params.prevBoughtGas += uint64(_amount);
}
/// @notice Virtual function that returns the resource config. /// @notice Virtual function that returns the resource config.
/// Contracts that inherit this contract must implement this function. /// Contracts that inherit this contract must implement this function.
/// @return ResourceConfig /// @return ResourceConfig
......
...@@ -6,12 +6,15 @@ import { ISemver } from "src/universal/ISemver.sol"; ...@@ -6,12 +6,15 @@ import { ISemver } from "src/universal/ISemver.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol"; import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { Storage } from "src/libraries/Storage.sol"; import { Storage } from "src/libraries/Storage.sol";
import { Constants } from "src/libraries/Constants.sol"; import { Constants } from "src/libraries/Constants.sol";
import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { GasPayingToken, IGasToken } from "src/libraries/GasPayingToken.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/// @title SystemConfig /// @title SystemConfig
/// @notice The SystemConfig contract is used to manage configuration of an Optimism network. /// @notice The SystemConfig contract is used to manage configuration of an Optimism network.
/// All configuration is stored on L1 and picked up by L2 as part of the derviation of /// All configuration is stored on L1 and picked up by L2 as part of the derviation of
/// the L2 chain. /// the L2 chain.
contract SystemConfig is OwnableUpgradeable, ISemver { contract SystemConfig is OwnableUpgradeable, ISemver, IGasToken {
/// @notice Enum representing different types of updates. /// @notice Enum representing different types of updates.
/// @custom:value BATCHER Represents an update to the batcher hash. /// @custom:value BATCHER Represents an update to the batcher hash.
/// @custom:value GAS_CONFIG Represents an update to txn fee config on L2. /// @custom:value GAS_CONFIG Represents an update to txn fee config on L2.
...@@ -26,7 +29,8 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -26,7 +29,8 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
} }
/// @notice Struct representing the addresses of L1 system contracts. These should be the /// @notice Struct representing the addresses of L1 system contracts. These should be the
/// proxies and are network specific. /// contracts that users interact with (not implementations for proxied contracts)
/// and are network specific.
struct Addresses { struct Addresses {
address l1CrossDomainMessenger; address l1CrossDomainMessenger;
address l1ERC721Bridge; address l1ERC721Bridge;
...@@ -34,6 +38,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -34,6 +38,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
address disputeGameFactory; address disputeGameFactory;
address optimismPortal; address optimismPortal;
address optimismMintableERC20Factory; address optimismMintableERC20Factory;
address gasPayingToken;
} }
/// @notice Version identifier, used for upgrades. /// @notice Version identifier, used for upgrades.
...@@ -76,6 +81,9 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -76,6 +81,9 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
bytes32 public constant DISPUTE_GAME_FACTORY_SLOT = bytes32 public constant DISPUTE_GAME_FACTORY_SLOT =
bytes32(uint256(keccak256("systemconfig.disputegamefactory")) - 1); bytes32(uint256(keccak256("systemconfig.disputegamefactory")) - 1);
/// @notice The number of decimals that the gas paying token has.
uint8 internal constant GAS_PAYING_TOKEN_DECIMALS = 18;
/// @notice Fixed L2 gas overhead. Used as part of the L2 fee calculation. /// @notice Fixed L2 gas overhead. Used as part of the L2 fee calculation.
uint256 public overhead; uint256 public overhead;
...@@ -102,8 +110,8 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -102,8 +110,8 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data);
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 2.0.0 /// @custom:semver 2.1.0
string public constant version = "2.0.0"; string public constant version = "2.1.0";
/// @notice Constructs the SystemConfig contract. Cannot set /// @notice Constructs the SystemConfig contract. Cannot set
/// the owner to `address(0)` due to the Ownable contract's /// the owner to `address(0)` due to the Ownable contract's
...@@ -134,7 +142,8 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -134,7 +142,8 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
l1StandardBridge: address(0), l1StandardBridge: address(0),
disputeGameFactory: address(0), disputeGameFactory: address(0),
optimismPortal: address(0), optimismPortal: address(0),
optimismMintableERC20Factory: address(0) optimismMintableERC20Factory: address(0),
gasPayingToken: address(0)
}) })
}); });
} }
...@@ -183,6 +192,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -183,6 +192,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
Storage.setAddress(OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT, _addresses.optimismMintableERC20Factory); Storage.setAddress(OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT, _addresses.optimismMintableERC20Factory);
_setStartBlock(); _setStartBlock();
_setGasPayingToken(_addresses.gasPayingToken);
_setResourceConfig(_config); _setResourceConfig(_config);
require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low"); require(_gasLimit >= minimumGasLimit(), "SystemConfig: gas limit too low");
...@@ -227,7 +237,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -227,7 +237,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
} }
/// @notice Getter for the OptimismPortal address. /// @notice Getter for the OptimismPortal address.
function optimismPortal() external view returns (address addr_) { function optimismPortal() public view returns (address addr_) {
addr_ = Storage.getAddress(OPTIMISM_PORTAL_SLOT); addr_ = Storage.getAddress(OPTIMISM_PORTAL_SLOT);
} }
...@@ -246,6 +256,52 @@ contract SystemConfig is OwnableUpgradeable, ISemver { ...@@ -246,6 +256,52 @@ contract SystemConfig is OwnableUpgradeable, ISemver {
startBlock_ = Storage.getUint(START_BLOCK_SLOT); startBlock_ = Storage.getUint(START_BLOCK_SLOT);
} }
/// @notice Getter for the gas paying asset address.
function gasPayingToken() public view returns (address addr_, uint8 decimals_) {
(addr_, decimals_) = GasPayingToken.getToken();
}
/// @notice Getter for custom gas token paying networks. Returns true if the
/// network uses a custom gas token.
function isCustomGasToken() public view returns (bool) {
(address token,) = gasPayingToken();
return token != Constants.ETHER;
}
/// @notice Getter for the gas paying token name.
function gasPayingTokenName() external view returns (string memory name_) {
name_ = GasPayingToken.getName();
}
/// @notice Getter for the gas paying token symbol.
function gasPayingTokenSymbol() external view returns (string memory symbol_) {
symbol_ = GasPayingToken.getSymbol();
}
/// @notice Internal setter for the gas paying token address, includes validation.
/// The token must not already be set and must be non zero and not the ether address
/// to set the token address. This prevents the token address from being changed
/// and makes it explicitly opt-in to use custom gas token.
/// @param _token Address of the gas paying token.
function _setGasPayingToken(address _token) internal {
if (_token != address(0) && _token != Constants.ETHER && !isCustomGasToken()) {
require(
ERC20(_token).decimals() == GAS_PAYING_TOKEN_DECIMALS, "SystemConfig: bad decimals of gas paying token"
);
bytes32 name = GasPayingToken.sanitize(ERC20(_token).name());
bytes32 symbol = GasPayingToken.sanitize(ERC20(_token).symbol());
// Set the gas paying token in storage and in the OptimismPortal.
GasPayingToken.set({ _token: _token, _decimals: GAS_PAYING_TOKEN_DECIMALS, _name: name, _symbol: symbol });
OptimismPortal(payable(optimismPortal())).setGasPayingToken({
_token: _token,
_decimals: GAS_PAYING_TOKEN_DECIMALS,
_name: name,
_symbol: symbol
});
}
}
/// @notice Updates the unsafe block signer address. Can only be called by the owner. /// @notice Updates the unsafe block signer address. Can only be called by the owner.
/// @param _unsafeBlockSigner New unsafe block signer address. /// @param _unsafeBlockSigner New unsafe block signer address.
function setUnsafeBlockSigner(address _unsafeBlockSigner) external onlyOwner { function setUnsafeBlockSigner(address _unsafeBlockSigner) external onlyOwner {
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
import { ISemver } from "src/universal/ISemver.sol"; import { ISemver } from "src/universal/ISemver.sol";
import { Constants } from "src/libraries/Constants.sol";
import { GasPayingToken, IGasToken } from "src/libraries/GasPayingToken.sol";
/// @custom:proxied /// @custom:proxied
/// @custom:predeploy 0x4200000000000000000000000000000000000015 /// @custom:predeploy 0x4200000000000000000000000000000000000015
...@@ -10,9 +12,17 @@ import { ISemver } from "src/universal/ISemver.sol"; ...@@ -10,9 +12,17 @@ import { ISemver } from "src/universal/ISemver.sol";
/// Values within this contract are updated once per epoch (every L1 block) and can only be /// Values within this contract are updated once per epoch (every L1 block) and can only be
/// set by the "depositor" account, a special system address. Depositor account transactions /// set by the "depositor" account, a special system address. Depositor account transactions
/// are created by the protocol whenever we move to a new epoch. /// are created by the protocol whenever we move to a new epoch.
contract L1Block is ISemver { contract L1Block is ISemver, IGasToken {
/// @notice Error returns when a non-depositor account tries to set L1 block values.
error NotDepositor();
/// @notice Event emitted when the gas paying token is set.
event GasPayingTokenSet(address indexed token, uint8 indexed decimals, bytes32 name, bytes32 symbol);
/// @notice Address of the special depositor account. /// @notice Address of the special depositor account.
address public constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001; function DEPOSITOR_ACCOUNT() public pure returns (address addr_) {
addr_ = Constants.DEPOSITOR_ACCOUNT;
}
/// @notice The latest L1 block number known by the L2 system. /// @notice The latest L1 block number known by the L2 system.
uint64 public number; uint64 public number;
...@@ -49,9 +59,34 @@ contract L1Block is ISemver { ...@@ -49,9 +59,34 @@ contract L1Block is ISemver {
/// @notice The latest L1 blob base fee. /// @notice The latest L1 blob base fee.
uint256 public blobBaseFee; uint256 public blobBaseFee;
/// @custom:semver 1.3.0 /// @custom:semver 1.4.0
function version() public pure virtual returns (string memory) { function version() public pure virtual returns (string memory) {
return "1.3.0"; return "1.4.0";
}
/// @notice Returns the gas paying token, its decimals, name and symbol.
/// If nothing is set in state, then it means ether is used.
function gasPayingToken() public view returns (address addr_, uint8 decimals_) {
(addr_, decimals_) = GasPayingToken.getToken();
}
/// @notice Returns the gas paying token name.
/// If nothing is set in state, then it means ether is used.
function gasPayingTokenName() public view returns (string memory name_) {
name_ = GasPayingToken.getName();
}
/// @notice Returns the gas paying token symbol.
/// If nothing is set in state, then it means ether is used.
function gasPayingTokenSymbol() public view returns (string memory symbol_) {
symbol_ = GasPayingToken.getSymbol();
}
/// @notice Getter for custom gas token paying networks. Returns true if the
/// network uses a custom gas token.
function isCustomGasToken() public view returns (bool) {
(address token,) = gasPayingToken();
return token != Constants.ETHER;
} }
/// @custom:legacy /// @custom:legacy
...@@ -76,7 +111,7 @@ contract L1Block is ISemver { ...@@ -76,7 +111,7 @@ contract L1Block is ISemver {
) )
external external
{ {
require(msg.sender == DEPOSITOR_ACCOUNT, "L1Block: only the depositor account can set L1 block values"); require(msg.sender == DEPOSITOR_ACCOUNT(), "L1Block: only the depositor account can set L1 block values");
number = _number; number = _number;
timestamp = _timestamp; timestamp = _timestamp;
...@@ -101,9 +136,10 @@ contract L1Block is ISemver { ...@@ -101,9 +136,10 @@ contract L1Block is ISemver {
/// 8. _hash L1 blockhash. /// 8. _hash L1 blockhash.
/// 9. _batcherHash Versioned hash to authenticate batcher by. /// 9. _batcherHash Versioned hash to authenticate batcher by.
function setL1BlockValuesEcotone() external { function setL1BlockValuesEcotone() external {
address depositor = DEPOSITOR_ACCOUNT();
assembly { assembly {
// Revert if the caller is not the depositor account. // Revert if the caller is not the depositor account.
if xor(caller(), DEPOSITOR_ACCOUNT) { if xor(caller(), depositor) {
mstore(0x00, 0x3cc50b45) // 0x3cc50b45 is the 4-byte selector of "NotDepositor()" mstore(0x00, 0x3cc50b45) // 0x3cc50b45 is the 4-byte selector of "NotDepositor()"
revert(0x1C, 0x04) // returns the stored 4-byte selector from above revert(0x1C, 0x04) // returns the stored 4-byte selector from above
} }
...@@ -117,4 +153,15 @@ contract L1Block is ISemver { ...@@ -117,4 +153,15 @@ contract L1Block is ISemver {
sstore(batcherHash.slot, calldataload(132)) // bytes32 sstore(batcherHash.slot, calldataload(132)) // bytes32
} }
} }
/// @notice Sets the gas paying token for the L2 system. Can only be called by the special
/// depositor account. This function is not called on every L2 block but instead
/// only called by specially crafted L1 deposit transactions.
function setGasPayingToken(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol) external {
if (msg.sender != DEPOSITOR_ACCOUNT()) revert NotDepositor();
GasPayingToken.set({ _token: _token, _decimals: _decimals, _name: _name, _symbol: _symbol });
emit GasPayingTokenSet({ token: _token, decimals: _decimals, name: _name, symbol: _symbol });
}
} }
...@@ -37,9 +37,10 @@ contract L1BlockInterop is L1Block { ...@@ -37,9 +37,10 @@ contract L1BlockInterop is L1Block {
/// 10. _dependencySetSize Size of the interop dependency set. /// 10. _dependencySetSize Size of the interop dependency set.
/// 11. _dependencySet Array of chain IDs for the interop dependency set. /// 11. _dependencySet Array of chain IDs for the interop dependency set.
function setL1BlockValuesInterop() external { function setL1BlockValuesInterop() external {
address depositor = DEPOSITOR_ACCOUNT();
assembly { assembly {
// Revert if the caller is not the depositor account. // Revert if the caller is not the depositor account.
if xor(caller(), DEPOSITOR_ACCOUNT) { if xor(caller(), depositor) {
mstore(0x00, 0x3cc50b45) // 0x3cc50b45 is the 4-byte selector of "NotDepositor()" mstore(0x00, 0x3cc50b45) // 0x3cc50b45 is the 4-byte selector of "NotDepositor()"
revert(0x1C, 0x04) // returns the stored 4-byte selector from above revert(0x1C, 0x04) // returns the stored 4-byte selector from above
} }
......
...@@ -7,6 +7,8 @@ import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; ...@@ -7,6 +7,8 @@ import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
import { ISemver } from "src/universal/ISemver.sol"; import { ISemver } from "src/universal/ISemver.sol";
import { L2ToL1MessagePasser } from "src/L2/L2ToL1MessagePasser.sol"; import { L2ToL1MessagePasser } from "src/L2/L2ToL1MessagePasser.sol";
import { Constants } from "src/libraries/Constants.sol"; import { Constants } from "src/libraries/Constants.sol";
import { L1Block } from "src/L2/L1Block.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
/// @custom:proxied /// @custom:proxied
/// @custom:predeploy 0x4200000000000000000000000000000000000007 /// @custom:predeploy 0x4200000000000000000000000000000000000007
...@@ -15,8 +17,8 @@ import { Constants } from "src/libraries/Constants.sol"; ...@@ -15,8 +17,8 @@ import { Constants } from "src/libraries/Constants.sol";
/// L2 on the L2 side. Users are generally encouraged to use this contract instead of lower /// L2 on the L2 side. Users are generally encouraged to use this contract instead of lower
/// level message passing contracts. /// level message passing contracts.
contract L2CrossDomainMessenger is CrossDomainMessenger, ISemver { contract L2CrossDomainMessenger is CrossDomainMessenger, ISemver {
/// @custom:semver 2.0.0 /// @custom:semver 2.1.0
string public constant version = "2.0.0"; string public constant version = "2.1.0";
/// @notice Constructs the L2CrossDomainMessenger contract. /// @notice Constructs the L2CrossDomainMessenger contract.
constructor() CrossDomainMessenger() { constructor() CrossDomainMessenger() {
...@@ -44,6 +46,11 @@ contract L2CrossDomainMessenger is CrossDomainMessenger, ISemver { ...@@ -44,6 +46,11 @@ contract L2CrossDomainMessenger is CrossDomainMessenger, ISemver {
); );
} }
/// @inheritdoc CrossDomainMessenger
function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) {
(addr_, decimals_) = L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).gasPayingToken();
}
/// @inheritdoc CrossDomainMessenger /// @inheritdoc CrossDomainMessenger
function _isOtherMessenger() internal view override returns (bool) { function _isOtherMessenger() internal view override returns (bool) {
return AddressAliasHelper.undoL1ToL2Alias(msg.sender) == address(otherMessenger); return AddressAliasHelper.undoL1ToL2Alias(msg.sender) == address(otherMessenger);
......
...@@ -6,7 +6,7 @@ import { StandardBridge } from "src/universal/StandardBridge.sol"; ...@@ -6,7 +6,7 @@ import { StandardBridge } from "src/universal/StandardBridge.sol";
import { ISemver } from "src/universal/ISemver.sol"; import { ISemver } from "src/universal/ISemver.sol";
import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol";
import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
import { Constants } from "src/libraries/Constants.sol"; import { L1Block } from "src/L2/L1Block.sol";
/// @custom:proxied /// @custom:proxied
/// @custom:predeploy 0x4200000000000000000000000000000000000010 /// @custom:predeploy 0x4200000000000000000000000000000000000010
...@@ -52,8 +52,8 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -52,8 +52,8 @@ contract L2StandardBridge is StandardBridge, ISemver {
bytes extraData bytes extraData
); );
/// @custom:semver 1.8.0 /// @custom:semver 1.9.0
string public constant version = "1.8.0"; string public constant version = "1.9.0";
/// @notice Constructs the L2StandardBridge contract. /// @notice Constructs the L2StandardBridge contract.
constructor() StandardBridge() { constructor() StandardBridge() {
...@@ -76,10 +76,16 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -76,10 +76,16 @@ contract L2StandardBridge is StandardBridge, ISemver {
); );
} }
/// @inheritdoc StandardBridge
function gasPayingToken() internal view override returns (address addr_, uint8 decimals_) {
(addr_, decimals_) = L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).gasPayingToken();
}
/// @custom:legacy /// @custom:legacy
/// @notice Initiates a withdrawal from L2 to L1. /// @notice Initiates a withdrawal from L2 to L1.
/// This function only works with OptimismMintableERC20 tokens or ether. Use the /// This function only works with OptimismMintableERC20 tokens or ether. Use the
/// `bridgeERC20` function to bridge native L2 tokens to L1. /// `bridgeERC20` function to bridge native L2 tokens to L1.
/// Subject to be deprecated in the future.
/// @param _l2Token Address of the L2 token to withdraw. /// @param _l2Token Address of the L2 token to withdraw.
/// @param _amount Amount of the L2 token to withdraw. /// @param _amount Amount of the L2 token to withdraw.
/// @param _minGasLimit Minimum gas limit to use for the transaction. /// @param _minGasLimit Minimum gas limit to use for the transaction.
...@@ -95,6 +101,7 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -95,6 +101,7 @@ contract L2StandardBridge is StandardBridge, ISemver {
virtual virtual
onlyEOA onlyEOA
{ {
require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token");
_initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData); _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _minGasLimit, _extraData);
} }
...@@ -106,6 +113,7 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -106,6 +113,7 @@ contract L2StandardBridge is StandardBridge, ISemver {
/// call will fail for any amount of gas, then the ETH will be locked permanently. /// call will fail for any amount of gas, then the ETH will be locked permanently.
/// This function only works with OptimismMintableERC20 tokens or ether. Use the /// This function only works with OptimismMintableERC20 tokens or ether. Use the
/// `bridgeERC20To` function to bridge native L2 tokens to L1. /// `bridgeERC20To` function to bridge native L2 tokens to L1.
/// Subject to be deprecated in the future.
/// @param _l2Token Address of the L2 token to withdraw. /// @param _l2Token Address of the L2 token to withdraw.
/// @param _to Recipient account on L1. /// @param _to Recipient account on L1.
/// @param _amount Amount of the L2 token to withdraw. /// @param _amount Amount of the L2 token to withdraw.
...@@ -122,12 +130,14 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -122,12 +130,14 @@ contract L2StandardBridge is StandardBridge, ISemver {
payable payable
virtual virtual
{ {
require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token");
_initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData); _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _minGasLimit, _extraData);
} }
/// @custom:legacy /// @custom:legacy
/// @notice Finalizes a deposit from L1 to L2. To finalize a deposit of ether, use address(0) /// @notice Finalizes a deposit from L1 to L2. To finalize a deposit of ether, use address(0)
/// and the l1Token and the Legacy ERC20 ether predeploy address as the l2Token. /// and the l1Token and the Legacy ERC20 ether predeploy address as the l2Token.
/// Subject to be deprecated in the future.
/// @param _l1Token Address of the L1 token to deposit. /// @param _l1Token Address of the L1 token to deposit.
/// @param _l2Token Address of the corresponding L2 token. /// @param _l2Token Address of the corresponding L2 token.
/// @param _from Address of the depositor. /// @param _from Address of the depositor.
...@@ -146,6 +156,7 @@ contract L2StandardBridge is StandardBridge, ISemver { ...@@ -146,6 +156,7 @@ contract L2StandardBridge is StandardBridge, ISemver {
payable payable
virtual virtual
{ {
require(isCustomGasToken() == false, "L2StandardBridge: not supported with custom gas token");
if (_l1Token == address(0) && _l2Token == Predeploys.LEGACY_ERC20_ETH) { if (_l1Token == address(0) && _l2Token == Predeploys.LEGACY_ERC20_ETH) {
finalizeBridgeETH(_from, _to, _amount, _extraData); finalizeBridgeETH(_from, _to, _amount, _extraData);
} else { } else {
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { WETH98 } from "src/dispute/weth/WETH98.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { L1Block } from "src/L2/L1Block.sol";
/// @title WETH contract that reads the name and symbol from the L1Block contract.
/// Allows for nice rendering of token names for chains using custom gas token.
contract WETH is WETH98 {
/// @notice Returns the name of the wrapped native asset. Will be "Wrapped Ether"
/// if the native asset is Ether.
function name() external view override returns (string memory name_) {
name_ = string.concat("Wrapped ", L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).gasPayingTokenName());
}
/// @notice Returns the symbol of the wrapped native asset. Will be "WETH" if the
/// native asset is Ether.
function symbol() external view override returns (string memory symbol_) {
symbol_ = string.concat("W", L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).gasPayingTokenSymbol());
}
}
...@@ -30,6 +30,13 @@ library Constants { ...@@ -30,6 +30,13 @@ library Constants {
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)` /// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)`
bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/// @notice The address that represents ether when dealing with ERC20 token addresses.
address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @notice The address that represents the system caller responsible for L1 attributes
/// transactions.
address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001;
/// @notice Returns the default values for the ResourceConfig. These are the recommended values /// @notice Returns the default values for the ResourceConfig. These are the recommended values
/// for a production network. /// for a production network.
function DEFAULT_RESOURCE_CONFIG() internal pure returns (ResourceMetering.ResourceConfig memory) { function DEFAULT_RESOURCE_CONFIG() internal pure returns (ResourceMetering.ResourceConfig memory) {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Storage } from "src/libraries/Storage.sol";
import { Constants } from "src/libraries/Constants.sol";
import { LibString } from "@solady/utils/LibString.sol";
/// @title IGasToken
/// @notice Implemented by contracts that are aware of the custom gas token used
/// by the L2 network.
interface IGasToken {
/// @notice Getter for the ERC20 token address that is used to pay for gas and its decimals.
function gasPayingToken() external view returns (address, uint8);
/// @notice Returns the gas token name.
function gasPayingTokenName() external view returns (string memory);
/// @notice Returns the gas token symbol.
function gasPayingTokenSymbol() external view returns (string memory);
/// @notice Returns true if the network uses a custom gas token.
function isCustomGasToken() external view returns (bool);
}
/// @title GasPayingToken
/// @notice Handles reading and writing the custom gas token to storage.
/// To be used in any place where gas token information is read or
/// written to state. If multiple contracts use this library, the
/// values in storage should be kept in sync between them.
library GasPayingToken {
/// @notice The storage slot that contains the address and decimals of the gas paying token
bytes32 internal constant GAS_PAYING_TOKEN_SLOT = bytes32(uint256(keccak256("opstack.gaspayingtoken")) - 1);
/// @notice The storage slot that contains the ERC20 `name()` of the gas paying token
bytes32 internal constant GAS_PAYING_TOKEN_NAME_SLOT = bytes32(uint256(keccak256("opstack.gaspayingtokenname")) - 1);
/// @notice the storage slot that contains the ERC20 `symbol()` of the gas paying token
bytes32 internal constant GAS_PAYING_TOKEN_SYMBOL_SLOT =
bytes32(uint256(keccak256("opstack.gaspayingtokensymbol")) - 1);
/// @notice Reads the gas paying token and its decimals from the magic
/// storage slot. If nothing is set in storage, then the ether
/// address is returned instead.
function getToken() internal view returns (address addr_, uint8 decimals_) {
bytes32 slot = Storage.getBytes32(GAS_PAYING_TOKEN_SLOT);
addr_ = address(uint160(uint256(slot) & uint256(type(uint160).max)));
if (addr_ == address(0)) {
addr_ = Constants.ETHER;
decimals_ = 18;
} else {
decimals_ = uint8(uint256(slot) >> 160);
}
}
/// @notice Reads the gas paying token's name from the magic storage slot.
/// If nothing is set in storage, then the ether name, 'Ether', is returned instead.
function getName() internal view returns (string memory name_) {
(address addr,) = getToken();
if (addr == Constants.ETHER) {
name_ = "Ether";
} else {
name_ = LibString.fromSmallString(Storage.getBytes32(GAS_PAYING_TOKEN_NAME_SLOT));
}
}
/// @notice Reads the gas paying token's symbol from the magic storage slot.
/// If nothing is set in storage, then the ether symbol, 'ETH', is returned instead.
function getSymbol() internal view returns (string memory symbol_) {
(address addr,) = getToken();
if (addr == Constants.ETHER) {
symbol_ = "ETH";
} else {
symbol_ = LibString.fromSmallString(Storage.getBytes32(GAS_PAYING_TOKEN_SYMBOL_SLOT));
}
}
/// @notice Writes the gas paying token, its decimals, name and symbol to the magic storage slot.
function set(address _token, uint8 _decimals, bytes32 _name, bytes32 _symbol) internal {
Storage.setBytes32(GAS_PAYING_TOKEN_SLOT, bytes32(uint256(_decimals) << 160 | uint256(uint160(_token))));
Storage.setBytes32(GAS_PAYING_TOKEN_NAME_SLOT, _name);
Storage.setBytes32(GAS_PAYING_TOKEN_SYMBOL_SLOT, _symbol);
}
/// @notice Maps a string to a normalized null-terminated small string.
function sanitize(string memory _str) internal pure returns (bytes32) {
require(bytes(_str).length <= 32, "GasPayingToken: string cannot be greater than 32 bytes");
return LibString.toSmallString(_str);
}
}
...@@ -20,3 +20,5 @@ error Unauthorized(); ...@@ -20,3 +20,5 @@ error Unauthorized();
error CallPaused(); error CallPaused();
/// @notice Error for special gas estimation. /// @notice Error for special gas estimation.
error GasEstimation(); error GasEstimation();
/// @notice Error for when a method is being reentered.
error NonReentrant();
...@@ -23,8 +23,8 @@ library Predeploys { ...@@ -23,8 +23,8 @@ library Predeploys {
/// @notice Address of the DeployerWhitelist predeploy. No longer active. /// @notice Address of the DeployerWhitelist predeploy. No longer active.
address internal constant DEPLOYER_WHITELIST = 0x4200000000000000000000000000000000000002; address internal constant DEPLOYER_WHITELIST = 0x4200000000000000000000000000000000000002;
/// @notice Address of the canonical WETH9 contract. /// @notice Address of the canonical WETH contract.
address internal constant WETH9 = 0x4200000000000000000000000000000000000006; address internal constant WETH = 0x4200000000000000000000000000000000000006;
/// @notice Address of the L2CrossDomainMessenger predeploy. /// @notice Address of the L2CrossDomainMessenger predeploy.
address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007; address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007;
...@@ -95,7 +95,7 @@ library Predeploys { ...@@ -95,7 +95,7 @@ library Predeploys {
if (_addr == LEGACY_MESSAGE_PASSER) return "LegacyMessagePasser"; if (_addr == LEGACY_MESSAGE_PASSER) return "LegacyMessagePasser";
if (_addr == L1_MESSAGE_SENDER) return "L1MessageSender"; if (_addr == L1_MESSAGE_SENDER) return "L1MessageSender";
if (_addr == DEPLOYER_WHITELIST) return "DeployerWhitelist"; if (_addr == DEPLOYER_WHITELIST) return "DeployerWhitelist";
if (_addr == WETH9) return "WETH9"; if (_addr == WETH) return "WETH";
if (_addr == L2_CROSS_DOMAIN_MESSENGER) return "L2CrossDomainMessenger"; if (_addr == L2_CROSS_DOMAIN_MESSENGER) return "L2CrossDomainMessenger";
if (_addr == GAS_PRICE_ORACLE) return "GasPriceOracle"; if (_addr == GAS_PRICE_ORACLE) return "GasPriceOracle";
if (_addr == L2_STANDARD_BRIDGE) return "L2StandardBridge"; if (_addr == L2_STANDARD_BRIDGE) return "L2StandardBridge";
...@@ -120,12 +120,12 @@ library Predeploys { ...@@ -120,12 +120,12 @@ library Predeploys {
/// @notice Returns true if the predeploy is not proxied. /// @notice Returns true if the predeploy is not proxied.
function notProxied(address _addr) internal pure returns (bool) { function notProxied(address _addr) internal pure returns (bool) {
return _addr == GOVERNANCE_TOKEN || _addr == WETH9; return _addr == GOVERNANCE_TOKEN || _addr == WETH;
} }
/// @notice Returns true if the address is a defined predeploy that is embedded into new OP-Stack chains. /// @notice Returns true if the address is a defined predeploy that is embedded into new OP-Stack chains.
function isSupportedPredeploy(address _addr) internal pure returns (bool) { function isSupportedPredeploy(address _addr) internal pure returns (bool) {
return _addr == LEGACY_MESSAGE_PASSER || _addr == DEPLOYER_WHITELIST || _addr == WETH9 return _addr == LEGACY_MESSAGE_PASSER || _addr == DEPLOYER_WHITELIST || _addr == WETH
|| _addr == L2_CROSS_DOMAIN_MESSENGER || _addr == GAS_PRICE_ORACLE || _addr == L2_STANDARD_BRIDGE || _addr == L2_CROSS_DOMAIN_MESSENGER || _addr == GAS_PRICE_ORACLE || _addr == L2_STANDARD_BRIDGE
|| _addr == SEQUENCER_FEE_WALLET || _addr == OPTIMISM_MINTABLE_ERC20_FACTORY || _addr == L1_BLOCK_NUMBER || _addr == SEQUENCER_FEE_WALLET || _addr == OPTIMISM_MINTABLE_ERC20_FACTORY || _addr == L1_BLOCK_NUMBER
|| _addr == L2_ERC721_BRIDGE || _addr == L1_BLOCK_ATTRIBUTES || _addr == L2_TO_L1_MESSAGE_PASSER || _addr == L2_ERC721_BRIDGE || _addr == L1_BLOCK_ATTRIBUTES || _addr == L2_TO_L1_MESSAGE_PASSER
......
...@@ -174,6 +174,10 @@ abstract contract CrossDomainMessenger is ...@@ -174,6 +174,10 @@ abstract contract CrossDomainMessenger is
/// @param _message Message to trigger the target address with. /// @param _message Message to trigger the target address with.
/// @param _minGasLimit Minimum gas limit that the message can be executed with. /// @param _minGasLimit Minimum gas limit that the message can be executed with.
function sendMessage(address _target, bytes calldata _message, uint32 _minGasLimit) external payable { function sendMessage(address _target, bytes calldata _message, uint32 _minGasLimit) external payable {
if (isCustomGasToken()) {
require(msg.value == 0, "CrossDomainMessenger: cannot send value with custom gas token");
}
// Triggers a message to the other messenger. Note that the amount of gas provided to the // Triggers a message to the other messenger. Note that the amount of gas provided to the
// message is the amount of gas requested by the user PLUS the base gas value. We want to // message is the amount of gas requested by the user PLUS the base gas value. We want to
// guarantee the property that the call to the target contract will always have at least // guarantee the property that the call to the target contract will always have at least
...@@ -358,6 +362,15 @@ abstract contract CrossDomainMessenger is ...@@ -358,6 +362,15 @@ abstract contract CrossDomainMessenger is
+ RELAY_GAS_CHECK_BUFFER; + RELAY_GAS_CHECK_BUFFER;
} }
/// @notice Returns the address of the gas token and the token's decimals.
function gasPayingToken() internal view virtual returns (address, uint8);
/// @notice Returns whether the chain uses a custom gas token or not.
function isCustomGasToken() internal view returns (bool) {
(address token,) = gasPayingToken();
return token != Constants.ETHER;
}
/// @notice Initializer. /// @notice Initializer.
/// @param _otherMessenger CrossDomainMessenger contract on the other chain. /// @param _otherMessenger CrossDomainMessenger contract on the other chain.
function __CrossDomainMessenger_init(CrossDomainMessenger _otherMessenger) internal onlyInitializing { function __CrossDomainMessenger_init(CrossDomainMessenger _otherMessenger) internal onlyInitializing {
......
...@@ -10,6 +10,7 @@ import { IOptimismMintableERC20, ILegacyMintableERC20 } from "src/universal/IOpt ...@@ -10,6 +10,7 @@ import { IOptimismMintableERC20, ILegacyMintableERC20 } from "src/universal/IOpt
import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol"; import { CrossDomainMessenger } from "src/universal/CrossDomainMessenger.sol";
import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol"; import { OptimismMintableERC20 } from "src/universal/OptimismMintableERC20.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { Constants } from "src/libraries/Constants.sol";
/// @custom:upgradeable /// @custom:upgradeable
/// @title StandardBridge /// @title StandardBridge
...@@ -129,6 +130,15 @@ abstract contract StandardBridge is Initializable { ...@@ -129,6 +130,15 @@ abstract contract StandardBridge is Initializable {
/// Must be implemented by contracts that inherit. /// Must be implemented by contracts that inherit.
receive() external payable virtual; receive() external payable virtual;
/// @notice Returns the address of the custom gas token and the token's decimals.
function gasPayingToken() internal view virtual returns (address, uint8);
/// @notice Returns whether the chain uses a custom gas token or not.
function isCustomGasToken() internal view returns (bool) {
(address token,) = gasPayingToken();
return token != Constants.ETHER;
}
/// @notice Getter for messenger contract. /// @notice Getter for messenger contract.
/// Public getter is legacy and will be removed in the future. Use `messenger` instead. /// Public getter is legacy and will be removed in the future. Use `messenger` instead.
/// @return Contract of the messenger on this domain. /// @return Contract of the messenger on this domain.
...@@ -242,6 +252,7 @@ abstract contract StandardBridge is Initializable { ...@@ -242,6 +252,7 @@ abstract contract StandardBridge is Initializable {
onlyOtherBridge onlyOtherBridge
{ {
require(paused() == false, "StandardBridge: paused"); require(paused() == false, "StandardBridge: paused");
require(isCustomGasToken() == false, "StandardBridge: cannot bridge ETH with custom gas token");
require(msg.value == _amount, "StandardBridge: amount sent does not match amount required"); require(msg.value == _amount, "StandardBridge: amount sent does not match amount required");
require(_to != address(this), "StandardBridge: cannot send to self"); require(_to != address(this), "StandardBridge: cannot send to self");
require(_to != address(messenger), "StandardBridge: cannot send to messenger"); require(_to != address(messenger), "StandardBridge: cannot send to messenger");
...@@ -310,6 +321,7 @@ abstract contract StandardBridge is Initializable { ...@@ -310,6 +321,7 @@ abstract contract StandardBridge is Initializable {
) )
internal internal
{ {
require(isCustomGasToken() == false, "StandardBridge: cannot bridge ETH with custom gas token");
require(msg.value == _amount, "StandardBridge: bridging ETH must include sufficient ETH value"); require(msg.value == _amount, "StandardBridge: bridging ETH must include sufficient ETH value");
// Emit the correct events. By default this will be _amount, but child // Emit the correct events. By default this will be _amount, but child
...@@ -343,6 +355,8 @@ abstract contract StandardBridge is Initializable { ...@@ -343,6 +355,8 @@ abstract contract StandardBridge is Initializable {
) )
internal internal
{ {
require(msg.value == 0, "StandardBridge: cannot send value");
if (_isOptimismMintableERC20(_localToken)) { if (_isOptimismMintableERC20(_localToken)) {
require( require(
_isCorrectTokenPair(_localToken, _remoteToken), _isCorrectTokenPair(_localToken, _remoteToken),
......
This diff is collapsed.
...@@ -16,6 +16,7 @@ import { Constants } from "src/libraries/Constants.sol"; ...@@ -16,6 +16,7 @@ import { Constants } from "src/libraries/Constants.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { OptimismPortal } from "src/L1/OptimismPortal.sol"; import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
contract L1CrossDomainMessenger_Test is Bridge_Initializer { contract L1CrossDomainMessenger_Test is Bridge_Initializer {
/// @dev The receiver address /// @dev The receiver address
...@@ -619,6 +620,121 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer { ...@@ -619,6 +620,121 @@ contract L1CrossDomainMessenger_Test is Bridge_Initializer {
assertTrue(l1CrossDomainMessenger.paused()); assertTrue(l1CrossDomainMessenger.paused());
assertEq(l1CrossDomainMessenger.paused(), superchainConfig.paused()); assertEq(l1CrossDomainMessenger.paused(), superchainConfig.paused());
} }
/// @dev Tests that sendMessage succeeds with a custom gas token when the call value is zero.
function test_sendMessage_customGasToken_noValue_succeeds() external {
// Mock the gasPayingToken function to return a custom gas token
vm.mockCall(
address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(18))
);
// deposit transaction on the optimism portal should be called
vm.expectCall(
address(optimismPortal),
abi.encodeWithSelector(
OptimismPortal.depositTransaction.selector,
Predeploys.L2_CROSS_DOMAIN_MESSENGER,
0,
l1CrossDomainMessenger.baseGas(hex"ff", 100),
false,
Encoding.encodeCrossDomainMessage(
l1CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff"
)
)
);
// TransactionDeposited event
vm.expectEmit(address(optimismPortal));
emitTransactionDeposited(
AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)),
Predeploys.L2_CROSS_DOMAIN_MESSENGER,
0,
0,
l1CrossDomainMessenger.baseGas(hex"ff", 100),
false,
Encoding.encodeCrossDomainMessage(l1CrossDomainMessenger.messageNonce(), alice, recipient, 0, 100, hex"ff")
);
// SentMessage event
vm.expectEmit(address(l1CrossDomainMessenger));
emit SentMessage(recipient, alice, hex"ff", l1CrossDomainMessenger.messageNonce(), 100);
// SentMessageExtension1 event
vm.expectEmit(address(l1CrossDomainMessenger));
emit SentMessageExtension1(alice, 0);
vm.prank(alice);
l1CrossDomainMessenger.sendMessage(recipient, hex"ff", uint32(100));
}
/// @dev Tests that the sendMessage reverts when call value is non-zero with custom gas token.
function test_sendMessage_customGasToken_withValue_reverts() external {
// Mock the gasPayingToken function to return a custom gas token
vm.mockCall(
address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))
);
vm.expectRevert("CrossDomainMessenger: cannot send value with custom gas token");
l1CrossDomainMessenger.sendMessage{ value: 1 }(recipient, hex"aa", uint32(500_000));
}
/// @dev Tests that the relayMessage succeeds with a custom gas token when the call value is zero.
function test_relayMessage_customGasToken_noValue_succeeds() external {
// Mock the gasPayingToken function to return a custom gas token
vm.mockCall(
address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))
);
address target = address(0xabcd);
address sender = Predeploys.L2_CROSS_DOMAIN_MESSENGER;
vm.expectCall(target, hex"1111");
// set the value of op.l2Sender() to be the L2 Cross Domain Messenger.
vm.store(address(optimismPortal), bytes32(senderSlotIndex), bytes32(abi.encode(sender)));
vm.prank(address(optimismPortal));
vm.expectEmit(address(l1CrossDomainMessenger));
bytes32 hash = Hashing.hashCrossDomainMessage(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), sender, target, 0, 0, hex"1111"
);
emit RelayedMessage(hash);
l1CrossDomainMessenger.relayMessage(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }), // nonce
sender,
target,
0, // value
0,
hex"1111"
);
// the message hash is in the successfulMessages mapping
assertTrue(l1CrossDomainMessenger.successfulMessages(hash));
// it is not in the received messages mapping
assertEq(l1CrossDomainMessenger.failedMessages(hash), false);
}
/// @dev Tests that the relayMessage reverts when call value is non-zero with custom gas token.
/// The L2CrossDomainMessenger contract cannot `sendMessage` with value when using a custom gas token.
function test_relayMessage_customGasToken_withValue_reverts() external virtual {
// Mock the gasPayingToken function to return a custom gas token
vm.mockCall(
address(systemConfig), abi.encodeWithSignature("gasPayingToken()"), abi.encode(address(1), uint8(2))
);
vm.expectRevert("CrossDomainMessenger: value must be zero unless message is from a system address");
l1CrossDomainMessenger.relayMessage{ value: 1 }(
Encoding.encodeVersionedNonce({ _nonce: 0, _version: 1 }),
address(0xabcd),
address(0xabcd),
1, // value
0,
hex"1111"
);
}
} }
/// @dev A regression test against a reentrancy vulnerability in the CrossDomainMessenger contract, which /// @dev A regression test against a reentrancy vulnerability in the CrossDomainMessenger contract, which
...@@ -653,7 +769,9 @@ contract L1CrossDomainMessenger_ReinitReentryTest is Bridge_Initializer { ...@@ -653,7 +769,9 @@ contract L1CrossDomainMessenger_ReinitReentryTest is Bridge_Initializer {
vm.store(address(l1CrossDomainMessenger), 0, bytes32(uint256(0))); vm.store(address(l1CrossDomainMessenger), 0, bytes32(uint256(0)));
// call the initializer function // call the initializer function
l1CrossDomainMessenger.initialize(SuperchainConfig(superchainConfig), OptimismPortal(optimismPortal)); l1CrossDomainMessenger.initialize(
SuperchainConfig(superchainConfig), OptimismPortal(optimismPortal), SystemConfig(systemConfig)
);
// attempt to re-replay the withdrawal // attempt to re-replay the withdrawal
vm.expectEmit(address(l1CrossDomainMessenger)); vm.expectEmit(address(l1CrossDomainMessenger));
......
...@@ -203,6 +203,16 @@ contract ResourceMetering_Test is Test { ...@@ -203,6 +203,16 @@ contract ResourceMetering_Test is Test {
vm.roll(initialBlockNum + _blockDiff); vm.roll(initialBlockNum + _blockDiff);
meter.use(_amount); meter.use(_amount);
} }
function testFuzz_meter_useGas_succeeds(uint64 _amount) external {
(, uint64 prevBoughtGas,) = meter.params();
vm.assume(prevBoughtGas + _amount <= meter.resourceConfig().maxResourceLimit);
meter.use(_amount);
(, uint64 postPrevBoughtGas,) = meter.params();
assertEq(postPrevBoughtGas, prevBoughtGas + _amount);
}
} }
/// @title CustomMeterUser /// @title CustomMeterUser
......
...@@ -6,6 +6,7 @@ import { CommonTest } from "test/setup/CommonTest.sol"; ...@@ -6,6 +6,7 @@ import { CommonTest } from "test/setup/CommonTest.sol";
// Libraries // Libraries
import { Encoding } from "src/libraries/Encoding.sol"; import { Encoding } from "src/libraries/Encoding.sol";
import { Constants } from "src/libraries/Constants.sol";
// Target contract // Target contract
import { L1Block } from "src/L2/L1Block.sol"; import { L1Block } from "src/L2/L1Block.sol";
...@@ -13,6 +14,8 @@ import { L1Block } from "src/L2/L1Block.sol"; ...@@ -13,6 +14,8 @@ import { L1Block } from "src/L2/L1Block.sol";
contract L1BlockTest is CommonTest { contract L1BlockTest is CommonTest {
address depositor; address depositor;
event GasPayingTokenSet(address indexed token, uint8 indexed decimals, bytes32 name, bytes32 symbol);
/// @dev Sets up the test suite. /// @dev Sets up the test suite.
function setUp() public virtual override { function setUp() public virtual override {
super.setUp(); super.setUp();
...@@ -163,3 +166,41 @@ contract L1BlockEcotone_Test is L1BlockTest { ...@@ -163,3 +166,41 @@ contract L1BlockEcotone_Test is L1BlockTest {
assertEq(data, expReturn); assertEq(data, expReturn);
} }
} }
contract L1BlockCustomGasToken_Test is L1BlockTest {
function testFuzz_setGasPayingToken_succeeds(
address _token,
uint8 _decimals,
string memory _name,
string memory _symbol
)
external
{
vm.assume(_token != address(0));
vm.assume(_token != Constants.ETHER);
vm.assume(bytes(_name).length <= 32);
vm.assume(bytes(_symbol).length <= 32);
bytes32 name = bytes32(abi.encodePacked(_name));
bytes32 symbol = bytes32(abi.encodePacked(_symbol));
vm.expectEmit(address(l1Block));
emit GasPayingTokenSet({ token: _token, decimals: _decimals, name: name, symbol: symbol });
vm.prank(depositor);
l1Block.setGasPayingToken({ _token: _token, _decimals: _decimals, _name: name, _symbol: symbol });
(address token, uint8 decimals) = l1Block.gasPayingToken();
assertEq(token, _token);
assertEq(decimals, _decimals);
assertEq(_name, l1Block.gasPayingTokenName());
assertEq(_symbol, l1Block.gasPayingTokenSymbol());
assertTrue(l1Block.isCustomGasToken());
}
function test_setGasPayingToken_isDepositor_reverts() external {
vm.expectRevert(L1Block.NotDepositor.selector);
l1Block.setGasPayingToken(address(this), 18, "Test", "TST");
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
// Testing utilities
import { CommonTest } from "test/setup/CommonTest.sol";
// Target contract
import { WETH } from "src/L2/WETH.sol";
contract WETH_Test is CommonTest {
/// @dev Tests that the name function returns the correct value.
function testFuzz_name_succeeds(string memory _gasPayingTokenName) external {
vm.mockCall(address(l1Block), abi.encodeWithSignature("gasPayingTokenName()"), abi.encode(_gasPayingTokenName));
assertEq(string.concat("Wrapped ", _gasPayingTokenName), weth.name());
}
/// @dev Tests that the symbol function returns the correct value.
function testFuzz_symbol_succeeds(string memory _gasPayingTokenSymbol) external {
vm.mockCall(
address(l1Block), abi.encodeWithSignature("gasPayingTokenSymbol()"), abi.encode(_gasPayingTokenSymbol)
);
assertEq(string.concat("W", _gasPayingTokenSymbol), weth.symbol());
}
/// @dev Tests that the name function returns the correct value.
function test_name_ether_succeeds() external view {
assertFalse(l1Block.isCustomGasToken());
assertEq("Wrapped Ether", weth.name());
}
/// @dev Tests that the symbol function returns the correct value.
function test_symbol_ether_succeeds() external view {
assertFalse(l1Block.isCustomGasToken());
assertEq("WETH", weth.symbol());
}
}
This diff is collapsed.
...@@ -33,7 +33,8 @@ contract SystemConfig_GasLimitLowerBound_Invariant is Test { ...@@ -33,7 +33,8 @@ contract SystemConfig_GasLimitLowerBound_Invariant is Test {
l1StandardBridge: address(0), l1StandardBridge: address(0),
disputeGameFactory: address(0), disputeGameFactory: address(0),
optimismPortal: address(0), optimismPortal: address(0),
optimismMintableERC20Factory: address(0) optimismMintableERC20Factory: address(0),
gasPayingToken: Constants.ETHER
}) })
) )
) )
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment