Commit 44a2d9ce authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat: deploy and devnet tests for FPAC (#9325)

Updates contract deployments to include a new config value that
will deploy OptimismPortal2 over OptimismPortal. Updates devnet
tests to also include a new environment variable that will trigger
this deployment and run SDK tests for this deployment.
parent c0d0db91
---
'@eth-optimism/sdk': minor
---
Updates the SDK to support FPAC in a backwards compatible way.
...@@ -1020,9 +1020,13 @@ jobs: ...@@ -1020,9 +1020,13 @@ jobs:
devnet: devnet:
machine: machine:
image: <<pipeline.parameters.base_image>> image: <<pipeline.parameters.base_image>>
parameters:
devnet_fpac:
type: string
environment: environment:
DOCKER_BUILDKIT: 1 DOCKER_BUILDKIT: 1
DEVNET_NO_BUILD: 'true' DEVNET_NO_BUILD: 'true'
DEVNET_FPAC: <<parameters.devnet_fpac>>
steps: steps:
- checkout - checkout
- check-changed: - check-changed:
...@@ -1123,12 +1127,23 @@ jobs: ...@@ -1123,12 +1127,23 @@ jobs:
command: | command: |
docker logs ops-bedrock-op-proposer-1 || echo "No logs." docker logs ops-bedrock-op-proposer-1 || echo "No logs."
when: on_fail when: on_fail
- run:
name: Dump op-challenger logs
command: |
docker logs ops-bedrock-op-challenger-1 || echo "No logs."
when: on_fail
- run: - run:
name: Log deployment artifact name: Log deployment artifact
command: | command: |
cat broadcast/Deploy.s.sol/900/run-latest.json || echo "No deployment file found" cat broadcast/Deploy.s.sol/900/run-latest.json || echo "No deployment file found"
when: on_fail when: on_fail
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
- run:
name: Log devnet config
command: |
cat deploy-config/devnetL1.json || echo "No devnet config found"
when: on_fail
working_directory: packages/contracts-bedrock
- run: - run:
name: Log artifacts directory name: Log artifacts directory
command: | command: |
...@@ -1613,6 +1628,9 @@ workflows: ...@@ -1613,6 +1628,9 @@ workflows:
requires: requires:
- go-mod-download - go-mod-download
- devnet: - devnet:
matrix:
parameters:
devnet_fpac: ["false", "true"]
requires: requires:
- pnpm-monorepo - pnpm-monorepo
- op-batcher-docker-build - op-batcher-docker-build
......
...@@ -26,6 +26,10 @@ parser.add_argument('--test', help='Tests the deployment, must already be deploy ...@@ -26,6 +26,10 @@ parser.add_argument('--test', help='Tests the deployment, must already be deploy
log = logging.getLogger() log = logging.getLogger()
# Global environment variables
DEVNET_NO_BUILD = os.getenv('DEVNET_NO_BUILD') == "true"
DEVNET_FPAC = os.getenv('DEVNET_FPAC') == "true"
class Bunch: class Bunch:
def __init__(self, **kwds): def __init__(self, **kwds):
self.__dict__.update(kwds) self.__dict__.update(kwds)
...@@ -104,7 +108,7 @@ def main(): ...@@ -104,7 +108,7 @@ def main():
git_date = subprocess.run(['git', 'show', '-s', "--format=%ct"], capture_output=True, text=True).stdout.strip() git_date = subprocess.run(['git', 'show', '-s', "--format=%ct"], capture_output=True, text=True).stdout.strip()
# CI loads the images from workspace, and does not otherwise know the images are good as-is # CI loads the images from workspace, and does not otherwise know the images are good as-is
if os.getenv('DEVNET_NO_BUILD') == "true": if DEVNET_NO_BUILD:
log.info('Skipping docker images build') log.info('Skipping docker images build')
else: else:
log.info(f'Building docker images for git commit {git_commit} ({git_date})') log.info(f'Building docker images for git commit {git_commit} ({git_date})')
...@@ -123,6 +127,9 @@ def init_devnet_l1_deploy_config(paths, update_timestamp=False): ...@@ -123,6 +127,9 @@ def init_devnet_l1_deploy_config(paths, update_timestamp=False):
deploy_config = read_json(paths.devnet_config_template_path) deploy_config = read_json(paths.devnet_config_template_path)
if update_timestamp: if update_timestamp:
deploy_config['l1GenesisBlockTimestamp'] = '{:#x}'.format(int(time.time())) deploy_config['l1GenesisBlockTimestamp'] = '{:#x}'.format(int(time.time()))
if DEVNET_FPAC:
deploy_config['useFaultProofs'] = True
deploy_config['faultGameMaxDuration'] = 10
write_json(paths.devnet_config_path, deploy_config) write_json(paths.devnet_config_path, deploy_config)
def devnet_l1_genesis(paths): def devnet_l1_genesis(paths):
...@@ -146,7 +153,11 @@ def devnet_deploy(paths): ...@@ -146,7 +153,11 @@ def devnet_deploy(paths):
log.info('L1 genesis already generated.') log.info('L1 genesis already generated.')
else: else:
log.info('Generating L1 genesis.') log.info('Generating L1 genesis.')
if os.path.exists(paths.allocs_path) == False: if os.path.exists(paths.allocs_path) == False or DEVNET_FPAC == True:
# If this is the FPAC devnet then we need to generate the allocs
# file here always. This is because CI will run devnet-allocs
# without DEVNET_FPAC=true which means the allocs will be wrong.
# Re-running this step means the allocs will be correct.
devnet_l1_genesis(paths) devnet_l1_genesis(paths)
# It's odd that we want to regenerate the devnetL1.json file with # It's odd that we want to regenerate the devnetL1.json file with
...@@ -186,30 +197,49 @@ def devnet_deploy(paths): ...@@ -186,30 +197,49 @@ def devnet_deploy(paths):
rollup_config = read_json(paths.rollup_config_path) rollup_config = read_json(paths.rollup_config_path)
addresses = read_json(paths.addresses_json_path) addresses = read_json(paths.addresses_json_path)
# Start the L2.
log.info('Bringing up L2.') log.info('Bringing up L2.')
run_command(['docker', 'compose', 'up', '-d', 'l2'], cwd=paths.ops_bedrock_dir, env={ run_command(['docker', 'compose', 'up', '-d', 'l2'], cwd=paths.ops_bedrock_dir, env={
'PWD': paths.ops_bedrock_dir 'PWD': paths.ops_bedrock_dir
}) })
# Wait for the L2 to be available.
wait_up(9545) wait_up(9545)
wait_for_rpc_server('127.0.0.1:9545') wait_for_rpc_server('127.0.0.1:9545')
# Print out the addresses being used for easier debugging.
l2_output_oracle = addresses['L2OutputOracleProxy'] l2_output_oracle = addresses['L2OutputOracleProxy']
log.info(f'Using L2OutputOracle {l2_output_oracle}') dispute_game_factory = addresses['DisputeGameFactoryProxy']
batch_inbox_address = rollup_config['batch_inbox_address'] batch_inbox_address = rollup_config['batch_inbox_address']
log.info(f'Using L2OutputOracle {l2_output_oracle}')
log.info(f'Using DisputeGameFactory {dispute_game_factory}')
log.info(f'Using batch inbox {batch_inbox_address}') log.info(f'Using batch inbox {batch_inbox_address}')
log.info('Bringing up `op-node`, `op-proposer` and `op-batcher`.') # Set up the base docker environment.
run_command(['docker', 'compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher'], cwd=paths.ops_bedrock_dir, env={ docker_env = {
'PWD': paths.ops_bedrock_dir, 'PWD': paths.ops_bedrock_dir,
'L2OO_ADDRESS': l2_output_oracle,
'SEQUENCER_BATCH_INBOX_ADDRESS': batch_inbox_address 'SEQUENCER_BATCH_INBOX_ADDRESS': batch_inbox_address
}) }
# Selectively set the L2OO_ADDRESS or DGF_ADDRESS if using FPAC.
# Must be done selectively because op-proposer throws if both are set.
if DEVNET_FPAC:
docker_env['DGF_ADDRESS'] = dispute_game_factory
docker_env['DG_TYPE'] = '0'
docker_env['PROPOSAL_INTERVAL'] = '10s'
else:
docker_env['L2OO_ADDRESS'] = l2_output_oracle
log.info('Bringing up `artifact-server`') # Bring up the rest of the services.
run_command(['docker', 'compose', 'up', '-d', 'artifact-server'], cwd=paths.ops_bedrock_dir, env={ log.info('Bringing up `op-node`, `op-proposer` and `op-batcher`.')
'PWD': paths.ops_bedrock_dir run_command(['docker', 'compose', 'up', '-d', 'op-node', 'op-proposer', 'op-batcher', 'artifact-server'], cwd=paths.ops_bedrock_dir, env=docker_env)
})
# Optionally bring up op-challenger.
if DEVNET_FPAC:
log.info('Bringing up `op-challenger`.')
run_command(['docker', 'compose', 'up', '-d', 'op-challenger'], cwd=paths.ops_bedrock_dir, env=docker_env)
# Fin.
log.info('Devnet ready.') log.info('Devnet ready.')
def wait_for_rpc_server(url): def wait_for_rpc_server(url):
......
This diff is collapsed.
...@@ -233,6 +233,18 @@ type DeployConfig struct { ...@@ -233,6 +233,18 @@ type DeployConfig struct {
// RequiredProtocolVersion indicates the protocol version that // RequiredProtocolVersion indicates the protocol version that
// nodes are recommended to adopt, to stay in sync with the network. // nodes are recommended to adopt, to stay in sync with the network.
RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"` RecommendedProtocolVersion params.ProtocolVersion `json:"recommendedProtocolVersion"`
// ProofMaturityDelaySeconds is the number of seconds that a proof must be
// mature before it can be used to finalize a withdrawal.
ProofMaturityDelaySeconds uint64 `json:"proofMaturityDelaySeconds"`
// DisputeGameFinalityDelaySeconds is an additional number of seconds a
// dispute game must wait before it can be used to finalize a withdrawal.
DisputeGameFinalityDelaySeconds uint64 `json:"disputeGameFinalityDelaySeconds"`
// RespectedGameType is the dispute game type that the OptimismPortal
// contract will respect for finalizing withdrawals.
RespectedGameType uint32 `json:"respectedGameType"`
// UseFaultProofs is a flag that indicates if the system is using fault
// proofs instead of the older output oracle mechanism.
UseFaultProofs bool `json:"useFaultProofs"`
// When Cancun activates. Relative to L1 genesis. // When Cancun activates. Relative to L1 genesis.
L1CancunTimeOffset *hexutil.Uint64 `json:"l1CancunTimeOffset,omitempty"` L1CancunTimeOffset *hexutil.Uint64 `json:"l1CancunTimeOffset,omitempty"`
...@@ -377,6 +389,12 @@ func (d *DeployConfig) Check() error { ...@@ -377,6 +389,12 @@ func (d *DeployConfig) Check() error {
if d.RecommendedProtocolVersion == (params.ProtocolVersion{}) { if d.RecommendedProtocolVersion == (params.ProtocolVersion{}) {
log.Warn("RecommendedProtocolVersion is empty") log.Warn("RecommendedProtocolVersion is empty")
} }
if d.ProofMaturityDelaySeconds == 0 {
log.Warn("ProofMaturityDelaySeconds is 0")
}
if d.DisputeGameFinalityDelaySeconds == 0 {
log.Warn("DisputeGameFinalityDelaySeconds is 0")
}
// 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 {
......
...@@ -76,5 +76,9 @@ ...@@ -76,5 +76,9 @@
"preimageOracleCancunActivationTimestamp": 0, "preimageOracleCancunActivationTimestamp": 0,
"systemConfigStartBlock": 0, "systemConfigStartBlock": 0,
"requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000" "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"proofMaturityDelaySeconds": 12,
"disputeGameFinalityDelaySeconds": 6,
"respectedGameType": 0,
"useFaultProofs": false
} }
...@@ -125,6 +125,9 @@ services: ...@@ -125,6 +125,9 @@ services:
OP_PROPOSER_MNEMONIC: test test test test test test test test test test test junk OP_PROPOSER_MNEMONIC: test test test test test test test test test test test junk
OP_PROPOSER_L2_OUTPUT_HD_PATH: "m/44'/60'/0'/0/1" OP_PROPOSER_L2_OUTPUT_HD_PATH: "m/44'/60'/0'/0/1"
OP_PROPOSER_L2OO_ADDRESS: "${L2OO_ADDRESS}" OP_PROPOSER_L2OO_ADDRESS: "${L2OO_ADDRESS}"
OP_PROPOSER_DGF_ADDRESS: "${DGF_ADDRESS}"
OP_PROPOSER_DG_TYPE: "${DG_TYPE}"
OP_PROPOSER_PROPOSAL_INTERVAL: "${PROPOSAL_INTERVAL}"
OP_PROPOSER_PPROF_ENABLED: "true" OP_PROPOSER_PPROF_ENABLED: "true"
OP_PROPOSER_METRICS_ENABLED: "true" OP_PROPOSER_METRICS_ENABLED: "true"
OP_PROPOSER_ALLOW_NON_FINALIZED: "true" OP_PROPOSER_ALLOW_NON_FINALIZED: "true"
...@@ -161,6 +164,34 @@ services: ...@@ -161,6 +164,34 @@ services:
OP_BATCHER_RPC_ENABLE_ADMIN: "true" OP_BATCHER_RPC_ENABLE_ADMIN: "true"
OP_BATCHER_BATCH_TYPE: 0 OP_BATCHER_BATCH_TYPE: 0
op-challenger:
depends_on:
- op_stack_go_builder
- l1
- l2
- op-node
build:
context: ../
dockerfile: ./op-challenger/Dockerfile
args:
OP_STACK_GO_BUILDER: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-stack-go:devnet
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:devnet
environment:
OP_CHALLENGER_L1_ETH_RPC: http://l1:8545
OP_CHALLENGER_ROLLUP_RPC: http://op-node:8545
OP_CHALLENGER_TRACE_TYPE: cannon
OP_CHALLENGER_GAME_FACTORY_ADDRESS: ${DGF_ADDRESS}
OP_CHALLENGER_DATADIR: temp/challenger-data
OP_CHALLENGER_CANNON_ROLLUP_CONFIG: ./.devnet/rollup.json
OP_CHALLENGER_CANNON_L2_GENESIS: ./.devnet/genesis-l2.json
OP_CHALLENGER_CANNON_BIN: ./cannon/bin/cannon
OP_CHALLENGER_CANNON_SERVER: ./op-program/bin/op-program
OP_CHALLENGER_CANNON_PRESTATE: ./op-program/bin/prestate.json
OP_CHALLENGER_CANNON_L2: http://l2:8545
OP_CHALLENGER_MNEMONIC: test test test test test test test test test test test junk
OP_CHALLENGER_HD_PATH: "m/44'/60'/0'/0/4"
OP_CHALLENGER_NUM_CONFIRMATIONS: 1
artifact-server: artifact-server:
depends_on: depends_on:
- l1 - l1
......
...@@ -57,5 +57,9 @@ ...@@ -57,5 +57,9 @@
"faultGameSplitDepth": 14, "faultGameSplitDepth": 14,
"preimageOracleMinProposalSize": 10000, "preimageOracleMinProposalSize": 10000,
"preimageOracleChallengePeriod": 120, "preimageOracleChallengePeriod": 120,
"preimageOracleCancunActivationTimestamp": 0 "preimageOracleCancunActivationTimestamp": 0,
"proofMaturityDelaySeconds": 12,
"disputeGameFinalityDelaySeconds": 6,
"respectedGameType": 0,
"useFaultProofs": false
} }
...@@ -42,5 +42,9 @@ ...@@ -42,5 +42,9 @@
"l2GenesisRegolithTimeOffset": "0x0", "l2GenesisRegolithTimeOffset": "0x0",
"systemConfigStartBlock": 0, "systemConfigStartBlock": 0,
"requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000", "requiredProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000" "recommendedProtocolVersion": "0x0000000000000000000000000000000000000000000000000000000000000000",
"proofMaturityDelaySeconds": 12,
"disputeGameFinalityDelaySeconds": 6,
"respectedGameType": 0,
"useFaultProofs": false
} }
...@@ -12,6 +12,7 @@ import { L2OutputOracle } from "src/L1/L2OutputOracle.sol"; ...@@ -12,6 +12,7 @@ import { L2OutputOracle } from "src/L1/L2OutputOracle.sol";
import { ProtocolVersion, ProtocolVersions } from "src/L1/ProtocolVersions.sol"; import { ProtocolVersion, ProtocolVersions } from "src/L1/ProtocolVersions.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { OptimismPortal } from "src/L1/OptimismPortal.sol"; import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol"; import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol";
import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol";
...@@ -50,6 +51,7 @@ library ChainAssertions { ...@@ -50,6 +51,7 @@ library ChainAssertions {
checkOptimismMintableERC20Factory({ _contracts: _prox, _isProxy: true }); checkOptimismMintableERC20Factory({ _contracts: _prox, _isProxy: true });
checkL1ERC721Bridge({ _contracts: _prox, _isProxy: true }); checkL1ERC721Bridge({ _contracts: _prox, _isProxy: true });
checkOptimismPortal({ _contracts: _prox, _cfg: _cfg, _isProxy: true }); checkOptimismPortal({ _contracts: _prox, _cfg: _cfg, _isProxy: true });
checkOptimismPortal2({ _contracts: _prox, _cfg: _cfg, _isProxy: true });
checkProtocolVersions({ _contracts: _prox, _cfg: _cfg, _isProxy: true }); checkProtocolVersions({ _contracts: _prox, _cfg: _cfg, _isProxy: true });
} }
...@@ -259,6 +261,42 @@ library ChainAssertions { ...@@ -259,6 +261,42 @@ library ChainAssertions {
} }
} }
/// @notice Asserts the OptimismPortal2 is setup correctly
function checkOptimismPortal2(
Types.ContractSet memory _contracts,
DeployConfig _cfg,
bool _isProxy
)
internal
view
{
console.log("Running chain assertions on the OptimismPortal2");
OptimismPortal2 portal = OptimismPortal2(payable(_contracts.OptimismPortal2));
address guardian = _cfg.superchainConfigGuardian();
if (guardian.code.length == 0) {
console.log("Guardian has no code: %s", guardian);
}
if (_isProxy) {
require(address(portal.disputeGameFactory()) == _contracts.DisputeGameFactory);
require(address(portal.SYSTEM_CONFIG()) == _contracts.SystemConfig);
require(address(portal.systemConfig()) == _contracts.SystemConfig);
require(portal.GUARDIAN() == guardian);
require(portal.guardian() == guardian);
require(address(portal.superchainConfig()) == address(_contracts.SuperchainConfig));
require(portal.paused() == SuperchainConfig(_contracts.SuperchainConfig).paused());
require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER);
} else {
require(address(portal.disputeGameFactory()) == address(0));
require(address(portal.SYSTEM_CONFIG()) == address(0));
require(address(portal.systemConfig()) == address(0));
require(address(portal.superchainConfig()) == address(0));
require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER);
}
}
/// @notice Asserts that the ProtocolVersions is setup correctly /// @notice Asserts that the ProtocolVersions is setup correctly
function checkProtocolVersions( function checkProtocolVersions(
Types.ContractSet memory _contracts, Types.ContractSet memory _contracts,
......
...@@ -20,6 +20,7 @@ import { Proxy } from "src/universal/Proxy.sol"; ...@@ -20,6 +20,7 @@ import { Proxy } from "src/universal/Proxy.sol";
import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; import { L1StandardBridge } from "src/L1/L1StandardBridge.sol";
import { StandardBridge } from "src/universal/StandardBridge.sol"; import { StandardBridge } from "src/universal/StandardBridge.sol";
import { OptimismPortal } from "src/L1/OptimismPortal.sol"; import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol";
import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol"; import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol";
import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol";
import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol";
...@@ -128,8 +129,10 @@ contract Deploy is Deployer { ...@@ -128,8 +129,10 @@ contract Deploy is Deployer {
L1CrossDomainMessenger: mustGetAddress("L1CrossDomainMessengerProxy"), L1CrossDomainMessenger: mustGetAddress("L1CrossDomainMessengerProxy"),
L1StandardBridge: mustGetAddress("L1StandardBridgeProxy"), L1StandardBridge: mustGetAddress("L1StandardBridgeProxy"),
L2OutputOracle: mustGetAddress("L2OutputOracleProxy"), L2OutputOracle: mustGetAddress("L2OutputOracleProxy"),
DisputeGameFactory: mustGetAddress("DisputeGameFactoryProxy"),
OptimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"), OptimismMintableERC20Factory: mustGetAddress("OptimismMintableERC20FactoryProxy"),
OptimismPortal: mustGetAddress("OptimismPortalProxy"), OptimismPortal: mustGetAddress("OptimismPortalProxy"),
OptimismPortal2: mustGetAddress("OptimismPortalProxy"),
SystemConfig: mustGetAddress("SystemConfigProxy"), SystemConfig: mustGetAddress("SystemConfigProxy"),
L1ERC721Bridge: mustGetAddress("L1ERC721BridgeProxy"), L1ERC721Bridge: mustGetAddress("L1ERC721BridgeProxy"),
ProtocolVersions: mustGetAddress("ProtocolVersionsProxy"), ProtocolVersions: mustGetAddress("ProtocolVersionsProxy"),
...@@ -143,8 +146,10 @@ contract Deploy is Deployer { ...@@ -143,8 +146,10 @@ contract Deploy is Deployer {
L1CrossDomainMessenger: getAddress("L1CrossDomainMessengerProxy"), L1CrossDomainMessenger: getAddress("L1CrossDomainMessengerProxy"),
L1StandardBridge: getAddress("L1StandardBridgeProxy"), L1StandardBridge: getAddress("L1StandardBridgeProxy"),
L2OutputOracle: getAddress("L2OutputOracleProxy"), L2OutputOracle: getAddress("L2OutputOracleProxy"),
DisputeGameFactory: getAddress("DisputeGameFactoryProxy"),
OptimismMintableERC20Factory: getAddress("OptimismMintableERC20FactoryProxy"), OptimismMintableERC20Factory: getAddress("OptimismMintableERC20FactoryProxy"),
OptimismPortal: getAddress("OptimismPortalProxy"), OptimismPortal: getAddress("OptimismPortalProxy"),
OptimismPortal2: getAddress("OptimismPortalProxy"),
SystemConfig: getAddress("SystemConfigProxy"), SystemConfig: getAddress("SystemConfigProxy"),
L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"), L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"),
ProtocolVersions: getAddress("ProtocolVersionsProxy"), ProtocolVersions: getAddress("ProtocolVersionsProxy"),
...@@ -339,6 +344,7 @@ contract Deploy is Deployer { ...@@ -339,6 +344,7 @@ contract Deploy is Deployer {
function deployImplementations() public { function deployImplementations() public {
console.log("Deploying implementations"); console.log("Deploying implementations");
deployOptimismPortal(); deployOptimismPortal();
deployOptimismPortal2();
deployL1CrossDomainMessenger(); deployL1CrossDomainMessenger();
deployL2OutputOracle(); deployL2OutputOracle();
deployOptimismMintableERC20Factory(); deployOptimismMintableERC20Factory();
...@@ -360,7 +366,17 @@ contract Deploy is Deployer { ...@@ -360,7 +366,17 @@ contract Deploy is Deployer {
initializeOptimismMintableERC20Factory(); initializeOptimismMintableERC20Factory();
initializeL1CrossDomainMessenger(); initializeL1CrossDomainMessenger();
initializeL2OutputOracle(); initializeL2OutputOracle();
initializeOptimismPortal();
// 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 contract.
if (cfg.useFaultProofs()) {
console.log("WARNING: FPAC is enabled and using OptimismPortal2");
initializeOptimismPortal2();
} else {
initializeOptimismPortal();
}
} }
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
...@@ -539,6 +555,36 @@ contract Deploy is Deployer { ...@@ -539,6 +555,36 @@ contract Deploy is Deployer {
addr_ = address(portal); addr_ = address(portal);
} }
/// @notice Deploy the OptimismPortal2
function deployOptimismPortal2() public broadcast returns (address addr_) {
console.log("Deploying OptimismPortal2 implementation");
// Could also verify this inside DeployConfig but doing it here is a bit more reliable.
require(
uint32(cfg.respectedGameType()) == cfg.respectedGameType(), "Deploy: respectedGameType must fit into uint32"
);
OptimismPortal2 portal = new OptimismPortal2{ salt: _implSalt() }({
_proofMaturityDelaySeconds: cfg.proofMaturityDelaySeconds(),
_disputeGameFinalityDelaySeconds: cfg.disputeGameFinalityDelaySeconds(),
_initialRespectedGameType: GameType.wrap(uint32(cfg.respectedGameType()))
});
save("OptimismPortal2", address(portal));
console.log("OptimismPortal2 deployed at %s", address(portal));
// Override the `OptimismPortal2` contract to the deployed implementation. This is necessary
// to check the `OptimismPortal2` implementation alongside dependent contracts, which
// are always proxies.
Types.ContractSet memory contracts = _proxiesUnstrict();
contracts.OptimismPortal2 = address(portal);
ChainAssertions.checkOptimismPortal2({ _contracts: contracts, _cfg: cfg, _isProxy: false });
require(loadInitializedSlot("OptimismPortal2") == 1, "OptimismPortal2 is not initialized");
addr_ = address(portal);
}
/// @notice Deploy the L2OutputOracle /// @notice Deploy the L2OutputOracle
function deployL2OutputOracle() public broadcast returns (address addr_) { function deployL2OutputOracle() public broadcast returns (address addr_) {
console.log("Deploying L2OutputOracle implementation"); console.log("Deploying L2OutputOracle implementation");
...@@ -588,6 +634,8 @@ contract Deploy is Deployer { ...@@ -588,6 +634,8 @@ contract Deploy is Deployer {
DisputeGameFactory factory = new DisputeGameFactory{ salt: _implSalt() }(); DisputeGameFactory factory = new DisputeGameFactory{ salt: _implSalt() }();
save("DisputeGameFactory", address(factory)); save("DisputeGameFactory", address(factory));
console.log("DisputeGameFactory deployed at %s", address(factory)); console.log("DisputeGameFactory deployed at %s", address(factory));
// TODO: Run the checkDisputeGameFactory function here.
// @see https://github.com/ethereum-optimism/optimism/issues/9354
addr_ = address(factory); addr_ = address(factory);
} }
...@@ -738,6 +786,8 @@ contract Deploy is Deployer { ...@@ -738,6 +786,8 @@ contract Deploy is Deployer {
string memory version = DisputeGameFactory(disputeGameFactoryProxy).version(); string memory version = DisputeGameFactory(disputeGameFactoryProxy).version();
console.log("DisputeGameFactory version: %s", version); console.log("DisputeGameFactory version: %s", version);
// TODO: Run the checkDisputeGameFactory function here.
// @see https://github.com/ethereum-optimism/optimism/issues/9354
} }
/// @notice Initialize the SystemConfig /// @notice Initialize the SystemConfig
...@@ -979,6 +1029,37 @@ contract Deploy is Deployer { ...@@ -979,6 +1029,37 @@ contract Deploy is Deployer {
require(loadInitializedSlot("OptimismPortalProxy") == 1, "OptimismPortalProxy is not initialized"); require(loadInitializedSlot("OptimismPortalProxy") == 1, "OptimismPortalProxy is not initialized");
} }
/// @notice Initialize the OptimismPortal2
function initializeOptimismPortal2() public broadcast {
console.log("Upgrading and initializing OptimismPortal2 proxy");
address optimismPortalProxy = mustGetAddress("OptimismPortalProxy");
address optimismPortal2 = mustGetAddress("OptimismPortal2");
address disputeGameFactoryProxy = mustGetAddress("DisputeGameFactoryProxy");
address systemConfigProxy = mustGetAddress("SystemConfigProxy");
address superchainConfigProxy = mustGetAddress("SuperchainConfigProxy");
_upgradeAndCallViaSafe({
_proxy: payable(optimismPortalProxy),
_implementation: optimismPortal2,
_innerCallData: abi.encodeCall(
OptimismPortal2.initialize,
(
DisputeGameFactory(disputeGameFactoryProxy),
SystemConfig(systemConfigProxy),
SuperchainConfig(superchainConfigProxy)
)
)
});
OptimismPortal2 portal = OptimismPortal2(payable(optimismPortalProxy));
string memory version = portal.version();
console.log("OptimismPortal2 version: %s", version);
ChainAssertions.checkOptimismPortal2({ _contracts: _proxies(), _cfg: cfg, _isProxy: true });
require(loadInitializedSlot("OptimismPortalProxy") == 1, "OptimismPortalProxy is not initialized");
}
function initializeProtocolVersions() public broadcast { function initializeProtocolVersions() public broadcast {
console.log("Upgrading and initializing ProtocolVersions proxy"); console.log("Upgrading and initializing ProtocolVersions proxy");
address protocolVersionsProxy = mustGetAddress("ProtocolVersionsProxy"); address protocolVersionsProxy = mustGetAddress("ProtocolVersionsProxy");
......
...@@ -59,6 +59,10 @@ contract DeployConfig is Script { ...@@ -59,6 +59,10 @@ contract DeployConfig is Script {
uint256 public systemConfigStartBlock; uint256 public systemConfigStartBlock;
uint256 public requiredProtocolVersion; uint256 public requiredProtocolVersion;
uint256 public recommendedProtocolVersion; uint256 public recommendedProtocolVersion;
uint256 public proofMaturityDelaySeconds;
uint256 public disputeGameFinalityDelaySeconds;
uint256 public respectedGameType;
bool public useFaultProofs;
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);
...@@ -105,6 +109,10 @@ contract DeployConfig is Script { ...@@ -105,6 +109,10 @@ contract DeployConfig is Script {
systemConfigStartBlock = stdJson.readUint(_json, "$.systemConfigStartBlock"); systemConfigStartBlock = stdJson.readUint(_json, "$.systemConfigStartBlock");
requiredProtocolVersion = stdJson.readUint(_json, "$.requiredProtocolVersion"); requiredProtocolVersion = stdJson.readUint(_json, "$.requiredProtocolVersion");
recommendedProtocolVersion = stdJson.readUint(_json, "$.recommendedProtocolVersion"); recommendedProtocolVersion = stdJson.readUint(_json, "$.recommendedProtocolVersion");
proofMaturityDelaySeconds = stdJson.readUint(_json, "$.proofMaturityDelaySeconds");
disputeGameFinalityDelaySeconds = stdJson.readUint(_json, "$.disputeGameFinalityDelaySeconds");
respectedGameType = stdJson.readUint(_json, "$.respectedGameType");
useFaultProofs = stdJson.readBool(_json, "$.useFaultProofs");
if ( if (
block.chainid == Chains.LocalDevnet || block.chainid == Chains.GethDevnet || block.chainid == Chains.Sepolia block.chainid == Chains.LocalDevnet || block.chainid == Chains.GethDevnet || block.chainid == Chains.Sepolia
......
...@@ -7,8 +7,10 @@ library Types { ...@@ -7,8 +7,10 @@ library Types {
address L1CrossDomainMessenger; address L1CrossDomainMessenger;
address L1StandardBridge; address L1StandardBridge;
address L2OutputOracle; address L2OutputOracle;
address DisputeGameFactory;
address OptimismMintableERC20Factory; address OptimismMintableERC20Factory;
address OptimismPortal; address OptimismPortal;
address OptimismPortal2;
address SystemConfig; address SystemConfig;
address L1ERC721Bridge; address L1ERC721Bridge;
address ProtocolVersions; address ProtocolVersions;
......
...@@ -32,8 +32,8 @@ ...@@ -32,8 +32,8 @@
"sourceCodeHash": "0xdc27421279afb6c3b26fc8c589c5d213695f666c74d2c2c41cb7df719d172f37" "sourceCodeHash": "0xdc27421279afb6c3b26fc8c589c5d213695f666c74d2c2c41cb7df719d172f37"
}, },
"src/L1/OptimismPortal2.sol": { "src/L1/OptimismPortal2.sol": {
"initCodeHash": "0x3b140ef8e85169a59c626974aeb31be43bc58c5a48592611d74004925732ab5f", "initCodeHash": "0x48b052d78aafe26222a58fb01fee937402e43479c611739567551445ac235986",
"sourceCodeHash": "0xf28140c13ea7cefb9525ee106268e7d482f3e6e13d8b300d3b1423de8ac173f3" "sourceCodeHash": "0xfb5b8bfa0ae30cd5969376d26aff9c9e2c44e5c1ebecdc95a83efad0eaaf0e85"
}, },
"src/L1/ProtocolVersions.sol": { "src/L1/ProtocolVersions.sol": {
"initCodeHash": "0x72cd467e8bcf019c02675d72ab762e088bcc9cc0f1a4e9f587fa4589f7fdd1b8", "initCodeHash": "0x72cd467e8bcf019c02675d72ab762e088bcc9cc0f1a4e9f587fa4589f7fdd1b8",
...@@ -92,12 +92,12 @@ ...@@ -92,12 +92,12 @@
"sourceCodeHash": "0x1afb1d392e8f6a58ff86ea7f648e0d1756d4ba8d0d964279d58a390deaa53b7e" "sourceCodeHash": "0x1afb1d392e8f6a58ff86ea7f648e0d1756d4ba8d0d964279d58a390deaa53b7e"
}, },
"src/dispute/DisputeGameFactory.sol": { "src/dispute/DisputeGameFactory.sol": {
"initCodeHash": "0xbb8c4273acaede381fe88e6974e073fac363c7ec21618af737df2d3118608665", "initCodeHash": "0x45178e8a18a376e2f77fc6d076a7e5c900c7608e471b3074b3101059757f2426",
"sourceCodeHash": "0x78a030cd808a997f4b82c4fa284d6c394dae9d1fafeccb8783059a3b60bfb0ce" "sourceCodeHash": "0x628d32fcd779d535cebf7914fc1e930ad4767f196fc12ce4bd13b0c6541b0aa1"
}, },
"src/dispute/FaultDisputeGame.sol": { "src/dispute/FaultDisputeGame.sol": {
"initCodeHash": "0x471c66c96f233d0d1589c18efada49e14bf52e22e1bafc6d7768ee10b5b4f1de", "initCodeHash": "0x471c66c96f233d0d1589c18efada49e14bf52e22e1bafc6d7768ee10b5b4f1de",
"sourceCodeHash": "0x34d7af1f21b12900721a11eabb4131b99920047830c6784d06aacf648156d993" "sourceCodeHash": "0xa803b8daea162054f924575e9b8bc7931832c91d9d9b2a5ef672746bd1a4fc56"
}, },
"src/legacy/DeployerWhitelist.sol": { "src/legacy/DeployerWhitelist.sol": {
"initCodeHash": "0x8de80fb23b26dd9d849f6328e56ea7c173cd9e9ce1f05c9beea559d1720deb3d", "initCodeHash": "0x8de80fb23b26dd9d849f6328e56ea7c173cd9e9ce1f05c9beea559d1720deb3d",
......
...@@ -64,6 +64,21 @@ ...@@ -64,6 +64,21 @@
"internalType": "GameId", "internalType": "GameId",
"name": "metadata", "name": "metadata",
"type": "bytes32" "type": "bytes32"
},
{
"internalType": "Timestamp",
"name": "timestamp",
"type": "uint64"
},
{
"internalType": "Claim",
"name": "rootClaim",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "extraData",
"type": "bytes"
} }
], ],
"internalType": "struct IDisputeGameFactory.GameSearchResult[]", "internalType": "struct IDisputeGameFactory.GameSearchResult[]",
......
...@@ -63,6 +63,19 @@ ...@@ -63,6 +63,19 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "bytes32",
"name": "_withdrawalHash",
"type": "bytes32"
}
],
"name": "checkWithdrawal",
"outputs": [],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
......
...@@ -313,51 +313,11 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { ...@@ -313,51 +313,11 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver {
l2Sender == Constants.DEFAULT_L2_SENDER, "OptimismPortal: can only trigger one withdrawal per transaction" l2Sender == Constants.DEFAULT_L2_SENDER, "OptimismPortal: can only trigger one withdrawal per transaction"
); );
// Grab the proven withdrawal from the `provenWithdrawals` map. // Compute the withdrawal hash.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx); bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
IDisputeGame disputeGameProxy = provenWithdrawal.disputeGameProxy;
// The dispute game must not be blacklisted.
require(!disputeGameBlacklist[disputeGameProxy], "OptimismPortal: dispute game has been blacklisted");
// A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
// been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
// a timestamp of zero.
require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven yet");
// As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
// starting timestamp inside the Dispute Game. Not strictly necessary but extra layer of
// safety against weird bugs in the proving step.
require(
provenWithdrawal.timestamp > disputeGameProxy.createdAt().raw(),
"OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
);
// A proven withdrawal must wait at least `PROOF_MATURITY_DELAY_SECONDS` before finalizing.
require(
block.timestamp - provenWithdrawal.timestamp > PROOF_MATURITY_DELAY_SECONDS,
"OptimismPortal: proven withdrawal has not matured yet"
);
// A proven withdrawal must wait until the dispute game it was proven against has been // Check that the withdrawal can be finalized.
// resolved in favor of the root claim (the output proposal). This is to prevent users checkWithdrawal(withdrawalHash);
// from finalizing withdrawals proven against non-finalized output roots.
require(
disputeGameProxy.status() == GameStatus.DEFENDER_WINS,
"OptimismPortal: output proposal has not been finalized yet"
);
// Before a withdrawal can be finalized, the dispute game it was proven against must have been
// resolved for at least `DISPUTE_GAME_FINALITY_DELAY_SECONDS`. This is to allow for manual
// intervention in the event that a dispute game is resolved incorrectly.
require(
block.timestamp - disputeGameProxy.resolvedAt().raw() > DISPUTE_GAME_FINALITY_DELAY_SECONDS,
"OptimismPortal: output proposal in air-gap"
);
// Check that this withdrawal has not already been finalized, this is replay protection.
require(!finalizedWithdrawals[withdrawalHash], "OptimismPortal: withdrawal has already been finalized");
// Mark the withdrawal as finalized so it can't be replayed. // Mark the withdrawal as finalized so it can't be replayed.
finalizedWithdrawals[withdrawalHash] = true; finalizedWithdrawals[withdrawalHash] = true;
...@@ -463,4 +423,54 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { ...@@ -463,4 +423,54 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver {
require(msg.sender == SAURON, "OptimismPortal: only sauron can set the respected game type"); require(msg.sender == SAURON, "OptimismPortal: only sauron can set the respected game type");
respectedGameType = _gameType; respectedGameType = _gameType;
} }
/// @notice Checks if a withdrawal can be finalized. NOTE: Decision was made to have this
/// function revert rather than returning a boolean so that was more obvious why the
/// function failed.
/// @param _withdrawalHash Hash of the withdrawal to check.
function checkWithdrawal(bytes32 _withdrawalHash) public view {
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[_withdrawalHash];
IDisputeGame disputeGameProxy = provenWithdrawal.disputeGameProxy;
// The dispute game must not be blacklisted.
require(!disputeGameBlacklist[disputeGameProxy], "OptimismPortal: dispute game has been blacklisted");
// A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
// been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
// a timestamp of zero.
require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven yet");
// As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
// starting timestamp inside the Dispute Game. Not strictly necessary but extra layer of
// safety against weird bugs in the proving step.
require(
provenWithdrawal.timestamp > disputeGameProxy.createdAt().raw(),
"OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
);
// A proven withdrawal must wait at least `PROOF_MATURITY_DELAY_SECONDS` before finalizing.
require(
block.timestamp - provenWithdrawal.timestamp > PROOF_MATURITY_DELAY_SECONDS,
"OptimismPortal: proven withdrawal has not matured yet"
);
// A proven withdrawal must wait until the dispute game it was proven against has been
// resolved in favor of the root claim (the output proposal). This is to prevent users
// from finalizing withdrawals proven against non-finalized output roots.
require(
disputeGameProxy.status() == GameStatus.DEFENDER_WINS,
"OptimismPortal: output proposal has not been finalized yet"
);
// Before a withdrawal can be finalized, the dispute game it was proven against must have been
// resolved for at least `DISPUTE_GAME_FINALITY_DELAY_SECONDS`. This is to allow for manual
// intervention in the event that a dispute game is resolved incorrectly.
require(
block.timestamp - disputeGameProxy.resolvedAt().raw() > DISPUTE_GAME_FINALITY_DELAY_SECONDS,
"OptimismPortal: output proposal in air-gap"
);
// Check that this withdrawal has not already been finalized, this is replay protection.
require(!finalizedWithdrawals[_withdrawalHash], "OptimismPortal: withdrawal has already been finalized");
}
} }
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity 0.8.15;
import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol"; import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
...@@ -156,7 +156,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver ...@@ -156,7 +156,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver
// Perform a reverse linear search for the `_n` most recent games of type `_gameType`. // Perform a reverse linear search for the `_n` most recent games of type `_gameType`.
for (uint256 i = _start; i >= 0 && i <= _start;) { for (uint256 i = _start; i >= 0 && i <= _start;) {
GameId id = _disputeGameList[i]; GameId id = _disputeGameList[i];
(GameType gameType,,) = id.unpack(); (GameType gameType, Timestamp timestamp, IDisputeGame proxy) = id.unpack();
if (gameType.raw() == _gameType.raw()) { if (gameType.raw() == _gameType.raw()) {
// Increase the size of the `games_` array by 1. // Increase the size of the `games_` array by 1.
...@@ -166,7 +166,15 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver ...@@ -166,7 +166,15 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver
mstore(games_, add(mload(games_), 0x01)) mstore(games_, add(mload(games_), 0x01))
} }
games_[games_.length - 1] = GameSearchResult({ index: i, metadata: id }); bytes memory extraData = proxy.extraData();
Claim rootClaim = proxy.rootClaim();
games_[games_.length - 1] = GameSearchResult({
index: i,
metadata: id,
timestamp: timestamp,
rootClaim: rootClaim,
extraData: extraData
});
if (games_.length >= _n) break; if (games_.length >= _n) break;
} }
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity 0.8.15;
import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol";
import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol"; import { IFaultDisputeGame } from "src/dispute/interfaces/IFaultDisputeGame.sol";
......
...@@ -28,6 +28,9 @@ interface IDisputeGameFactory { ...@@ -28,6 +28,9 @@ interface IDisputeGameFactory {
struct GameSearchResult { struct GameSearchResult {
uint256 index; uint256 index;
GameId metadata; GameId metadata;
Timestamp timestamp;
Claim rootClaim;
bytes extraData;
} }
/// @notice The total number of dispute games created by this factory. /// @notice The total number of dispute games created by this factory.
......
...@@ -251,6 +251,7 @@ contract Specification_Test is CommonTest { ...@@ -251,6 +251,7 @@ contract Specification_Test is CommonTest {
_addSpec({ _name: "OptimismPortal2", _sel: _getSel("blacklistDisputeGame(address)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("blacklistDisputeGame(address)") });
_addSpec({ _name: "OptimismPortal2", _sel: _getSel("deleteProvenWithdrawal(bytes32)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("deleteProvenWithdrawal(bytes32)") });
_addSpec({ _name: "OptimismPortal2", _sel: _getSel("setRespectedGameType(uint32)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("setRespectedGameType(uint32)") });
_addSpec({ _name: "OptimismPortal2", _sel: _getSel("checkWithdrawal(bytes32)") });
// ProtocolVersions // ProtocolVersions
_addSpec({ _name: "ProtocolVersions", _sel: _getSel("RECOMMENDED_SLOT()") }); _addSpec({ _name: "ProtocolVersions", _sel: _getSel("RECOMMENDED_SLOT()") });
......
...@@ -274,8 +274,9 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_Init { ...@@ -274,8 +274,9 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_Init {
uint256 gameCount = factory.gameCount(); uint256 gameCount = factory.gameCount();
IDisputeGameFactory.GameSearchResult[] memory games = IDisputeGameFactory.GameSearchResult[] memory games;
factory.findLatestGames(GameType.wrap(0), gameCount - 1, 1);
games = factory.findLatestGames(GameType.wrap(0), gameCount - 1, 1);
assertEq(games.length, 1); assertEq(games.length, 1);
assertEq(games[0].index, 30); assertEq(games[0].index, 30);
(GameType gameType, Timestamp createdAt, IDisputeGame game) = games[0].metadata.unpack(); (GameType gameType, Timestamp createdAt, IDisputeGame game) = games[0].metadata.unpack();
...@@ -309,8 +310,9 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_Init { ...@@ -309,8 +310,9 @@ contract DisputeGameFactory_FindLatestGames_Test is DisputeGameFactory_Init {
uint256 gameCount = factory.gameCount(); uint256 gameCount = factory.gameCount();
IDisputeGameFactory.GameSearchResult[] memory games = IDisputeGameFactory.GameSearchResult[] memory games;
factory.findLatestGames(GameType.wrap(2), gameCount - 1, 5);
games = factory.findLatestGames(GameType.wrap(2), gameCount - 1, 5);
assertEq(games.length, 0); assertEq(games.length, 0);
games = factory.findLatestGames(GameType.wrap(1), gameCount - 1, 5); games = factory.findLatestGames(GameType.wrap(1), gameCount - 1, 5);
...@@ -349,4 +351,12 @@ contract FakeClone { ...@@ -349,4 +351,12 @@ contract FakeClone {
function initialize() external payable { function initialize() external payable {
// noop // noop
} }
function extraData() external pure returns (bytes memory) {
return hex"FF0420";
}
function rootClaim() external pure returns (Claim) {
return Claim.wrap(bytes32(0));
}
} }
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
"@types/chai-as-promised": "^7.1.8", "@types/chai-as-promised": "^7.1.8",
"@types/mocha": "^10.0.6", "@types/mocha": "^10.0.6",
"@types/node": "^20.11.13", "@types/node": "^20.11.13",
"@types/semver": "^7.5.6",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"ethereum-waffle": "^4.0.10", "ethereum-waffle": "^4.0.10",
"ethers": "^5.7.2", "ethers": "^5.7.2",
...@@ -66,7 +67,8 @@ ...@@ -66,7 +67,8 @@
"@eth-optimism/core-utils": "workspace:*", "@eth-optimism/core-utils": "workspace:*",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"merkletreejs": "^0.3.11", "merkletreejs": "^0.3.11",
"rlp": "^2.2.7" "rlp": "^2.2.7",
"semver": "^7.5.4"
}, },
"peerDependencies": { "peerDependencies": {
"ethers": "^5" "ethers": "^5"
......
...@@ -32,6 +32,7 @@ import { ...@@ -32,6 +32,7 @@ import {
} from '@eth-optimism/core-utils' } from '@eth-optimism/core-utils'
import { getContractInterface, predeploys } from '@eth-optimism/contracts' import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import * as rlp from 'rlp' import * as rlp from 'rlp'
import semver from 'semver'
import { import {
OEContracts, OEContracts,
...@@ -228,6 +229,29 @@ export class CrossChainMessenger { ...@@ -228,6 +229,29 @@ export class CrossChainMessenger {
} }
} }
/**
* Uses portal version to determine if the messenger is using fpac contracts. Better not to cache
* this value as it will change during the fpac upgrade and we want clients to automatically
* begin using the new logic without throwing any errors.
*
* @returns Whether or not the messenger is using fpac contracts.
*/
public async fpac(): Promise<boolean> {
if (
this.contracts.l1.OptimismPortal.address === ethers.constants.AddressZero
) {
// Only really relevant for certain SDK tests where the portal is not deployed. We should
// probably just update the tests so the portal gets deployed but feels like it's out of
// scope for the FPAC changes.
return false
} else {
return semver.gte(
await this.contracts.l1.OptimismPortal.version(),
'3.0.0'
)
}
}
/** /**
* Retrieves all cross chain messages sent within a given transaction. * Retrieves all cross chain messages sent within a given transaction.
* *
...@@ -731,11 +755,16 @@ export class CrossChainMessenger { ...@@ -731,11 +755,16 @@ export class CrossChainMessenger {
messageIndex messageIndex
) )
// Pick portal based on FPAC compatibility.
const portal = (await this.fpac())
? this.contracts.l1.OptimismPortal2
: this.contracts.l1.OptimismPortal
// Attempt to fetch the proven withdrawal. // Attempt to fetch the proven withdrawal.
const provenWithdrawal = const provenWithdrawal = await portal.provenWithdrawals(
await this.contracts.l1.OptimismPortal.provenWithdrawals( hashLowLevelMessage(withdrawal)
hashLowLevelMessage(withdrawal) )
)
// If the withdrawal hash has not been proven on L1, // If the withdrawal hash has not been proven on L1,
// return `READY_TO_PROVE` // return `READY_TO_PROVE`
if (provenWithdrawal.timestamp.eq(BigNumber.from(0))) { if (provenWithdrawal.timestamp.eq(BigNumber.from(0))) {
...@@ -758,13 +787,32 @@ export class CrossChainMessenger { ...@@ -758,13 +787,32 @@ export class CrossChainMessenger {
timestamp = block.timestamp timestamp = block.timestamp
} }
const challengePeriod = await this.getChallengePeriodSeconds() if (await this.fpac()) {
const latestBlock = await this.l1Provider.getBlock('latest') // Convert the message to the low level message that was proven.
const withdrawal = await this.toLowLevelMessage(
resolved,
messageIndex
)
try {
// If this doesn't revert then we should be fine to relay.
await this.contracts.l1.OptimismPortal2.checkWithdrawal(
hashLowLevelMessage(withdrawal)
)
if (timestamp + challengePeriod > latestBlock.timestamp) { return MessageStatus.READY_FOR_RELAY
return MessageStatus.IN_CHALLENGE_PERIOD } catch (err) {
return MessageStatus.IN_CHALLENGE_PERIOD
}
} else { } else {
return MessageStatus.READY_FOR_RELAY const challengePeriod = await this.getChallengePeriodSeconds()
const latestBlock = await this.l1Provider.getBlock('latest')
if (timestamp + challengePeriod > latestBlock.timestamp) {
return MessageStatus.IN_CHALLENGE_PERIOD
} else {
return MessageStatus.READY_FOR_RELAY
}
} }
} }
} }
...@@ -1212,29 +1260,85 @@ export class CrossChainMessenger { ...@@ -1212,29 +1260,85 @@ export class CrossChainMessenger {
throw new Error(`cannot get a state root for an L1 to L2 message`) throw new Error(`cannot get a state root for an L1 to L2 message`)
} }
// Try to find the output index that corresponds to the block number attached to the message. let proposal: any
// We'll explicitly handle "cannot get output" errors as a null return value, but anything else
// needs to get thrown. Might need to revisit this in the future to be a little more robust
// when connected to RPCs that don't return nice error messages.
let l2OutputIndex: BigNumber let l2OutputIndex: BigNumber
try { if (await this.fpac()) {
l2OutputIndex = // Get the respected game type from the portal.
await this.contracts.l1.L2OutputOracle.getL2OutputIndexAfter( const gameType =
resolved.blockNumber await this.contracts.l1.OptimismPortal2.respectedGameType()
// Get the total game count from the DisputeGameFactory since that will give us the end of
// the array that we're searching over. We'll then use that to find the latest games.
const gameCount = await this.contracts.l1.DisputeGameFactory.gameCount()
// Find the latest 100 games (or as many as we can up to 100).
const latestGames =
await this.contracts.l1.DisputeGameFactory.findLatestGames(
gameType,
gameCount.sub(1),
Math.min(100, gameCount.toNumber())
) )
} catch (err) {
if (err.message.includes('L2OutputOracle: cannot get output')) { // Find a game with a block number that is greater than or equal to the block number that the
// message was included in. We can use this proposal to prove the message to the portal.
let match: any
for (const game of latestGames) {
const [blockNumber] = ethers.utils.defaultAbiCoder.decode(
['uint256'],
game.extraData
)
if (blockNumber.gte(resolved.blockNumber)) {
match = {
...game,
l2BlockNumber: blockNumber,
}
break
}
}
// TODO: It would be more correct here to actually verify the proposal since proposals are
// not guaranteed to be correct. proveMessage will actually do this verification for us but
// there's a devex edge case where this message appears to give back a valid proposal that
// ends up reverting inside of proveMessage. At least this is safe for users but not ideal
// for developers and we should work out the simplest way to fix it. Main blocker is that
// verifying the proposal may require access to an archive node.
// If there's no match then we can't prove the message to the portal.
if (!match) {
return null return null
} else {
throw err
} }
}
// Now pull the proposal out given the output index. Should always work as long as the above // Put the result into the same format as the old logic for now to reduce added code.
// codepath completed successfully. l2OutputIndex = match.index
const proposal = await this.contracts.l1.L2OutputOracle.getL2Output( proposal = {
l2OutputIndex outputRoot: match.rootClaim,
) timestamp: match.timestamp,
l2BlockNumber: match.l2BlockNumber,
}
} else {
// Try to find the output index that corresponds to the block number attached to the message.
// We'll explicitly handle "cannot get output" errors as a null return value, but anything else
// needs to get thrown. Might need to revisit this in the future to be a little more robust
// when connected to RPCs that don't return nice error messages.
try {
l2OutputIndex =
await this.contracts.l1.L2OutputOracle.getL2OutputIndexAfter(
resolved.blockNumber
)
} catch (err) {
if (err.message.includes('L2OutputOracle: cannot get output')) {
return null
} else {
throw err
}
}
// Now pull the proposal out given the output index. Should always work as long as the above
// codepath completed successfully.
proposal = await this.contracts.l1.L2OutputOracle.getL2Output(
l2OutputIndex
)
}
// Format everything and return it nicely. // Format everything and return it nicely.
return { return {
......
...@@ -50,6 +50,9 @@ export interface OEL1Contracts { ...@@ -50,6 +50,9 @@ export interface OEL1Contracts {
// Bedrock // Bedrock
OptimismPortal: Contract OptimismPortal: Contract
L2OutputOracle: Contract L2OutputOracle: Contract
// FPAC
OptimismPortal2?: Contract
DisputeGameFactory?: Contract
} }
/** /**
......
...@@ -120,9 +120,16 @@ const getL1ContractsByNetworkName = (network: string): OEL1ContractsLike => { ...@@ -120,9 +120,16 @@ const getL1ContractsByNetworkName = (network: string): OEL1ContractsLike => {
BondManager: ethers.constants.AddressZero, BondManager: ethers.constants.AddressZero,
OptimismPortal: portalAddresses[network], OptimismPortal: portalAddresses[network],
L2OutputOracle: l2OutputOracleAddresses[network], L2OutputOracle: l2OutputOracleAddresses[network],
OptimismPortal2: portalAddresses[network],
DisputeGameFactory: ethers.constants.AddressZero,
} }
} }
/**
* List of contracts that are ignorable when checking for contracts on a given network.
*/
export const IGNORABLE_CONTRACTS = ['OptimismPortal2', 'DisputeGameFactory']
/** /**
* Mapping of L1 chain IDs to the appropriate contract addresses for the OE deployments to the * Mapping of L1 chain IDs to the appropriate contract addresses for the OE deployments to the
* given network. Simplifies the process of getting the correct contract addresses for a given * given network. Simplifies the process of getting the correct contract addresses for a given
...@@ -157,6 +164,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -157,6 +164,8 @@ export const CONTRACT_ADDRESSES: {
// FIXME // FIXME
OptimismPortal: '0x0000000000000000000000000000000000000000' as const, OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const, L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
OptimismPortal2: '0x0000000000000000000000000000000000000000' as const,
DisputeGameFactory: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -173,6 +182,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -173,6 +182,8 @@ export const CONTRACT_ADDRESSES: {
BondManager: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' as const, BondManager: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' as const,
OptimismPortal: '0x0000000000000000000000000000000000000000' as const, OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const, L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
OptimismPortal2: '0x0000000000000000000000000000000000000000' as const,
DisputeGameFactory: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -189,6 +200,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -189,6 +200,8 @@ export const CONTRACT_ADDRESSES: {
BondManager: '0x0000000000000000000000000000000000000000' as const, BondManager: '0x0000000000000000000000000000000000000000' as const,
OptimismPortal: '0xA581Ca3353DB73115C4625FFC7aDF5dB379434A8' as const, OptimismPortal: '0xA581Ca3353DB73115C4625FFC7aDF5dB379434A8' as const,
L2OutputOracle: '0x3A234299a14De50027eA65dCdf1c0DaC729e04A6' as const, L2OutputOracle: '0x3A234299a14De50027eA65dCdf1c0DaC729e04A6' as const,
OptimismPortal2: '0x0000000000000000000000000000000000000000' as const,
DisputeGameFactory: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -205,6 +218,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -205,6 +218,8 @@ export const CONTRACT_ADDRESSES: {
BondManager: '0x0000000000000000000000000000000000000000' as const, BondManager: '0x0000000000000000000000000000000000000000' as const,
OptimismPortal: '0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA' as const, OptimismPortal: '0xe93c8cD0D409341205A592f8c4Ac1A5fe5585cfA' as const,
L2OutputOracle: '0x2A35891ff30313CcFa6CE88dcf3858bb075A2298' as const, L2OutputOracle: '0x2A35891ff30313CcFa6CE88dcf3858bb075A2298' as const,
OptimismPortal2: '0x0000000000000000000000000000000000000000' as const,
DisputeGameFactory: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -221,6 +236,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -221,6 +236,8 @@ export const CONTRACT_ADDRESSES: {
BondManager: '0x0000000000000000000000000000000000000000' as const, BondManager: '0x0000000000000000000000000000000000000000' as const,
OptimismPortal: '0x49f53e41452C74589E85cA1677426Ba426459e85' as const, OptimismPortal: '0x49f53e41452C74589E85cA1677426Ba426459e85' as const,
L2OutputOracle: '0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254' as const, L2OutputOracle: '0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254' as const,
OptimismPortal2: '0x0000000000000000000000000000000000000000' as const,
DisputeGameFactory: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -237,6 +254,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -237,6 +254,8 @@ export const CONTRACT_ADDRESSES: {
BondManager: '0x0000000000000000000000000000000000000000' as const, BondManager: '0x0000000000000000000000000000000000000000' as const,
OptimismPortal: '0x49048044D57e1C92A77f79988d21Fa8fAF74E97e' as const, OptimismPortal: '0x49048044D57e1C92A77f79988d21Fa8fAF74E97e' as const,
L2OutputOracle: '0x56315b90c40730925ec5485cf004d835058518A0' as const, L2OutputOracle: '0x56315b90c40730925ec5485cf004d835058518A0' as const,
OptimismPortal2: '0x0000000000000000000000000000000000000000' as const,
DisputeGameFactory: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -254,6 +273,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -254,6 +273,8 @@ export const CONTRACT_ADDRESSES: {
BondManager: '0x0000000000000000000000000000000000000000' as const, BondManager: '0x0000000000000000000000000000000000000000' as const,
OptimismPortal: '0xDb9F51790365e7dc196e7D072728df39Be958ACe' as const, OptimismPortal: '0xDb9F51790365e7dc196e7D072728df39Be958ACe' as const,
L2OutputOracle: '0xdD292C9eEd00f6A32Ff5245d0BCd7f2a15f24e00' as const, L2OutputOracle: '0xdD292C9eEd00f6A32Ff5245d0BCd7f2a15f24e00' as const,
OptimismPortal2: '0x0000000000000000000000000000000000000000' as const,
DisputeGameFactory: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -270,6 +291,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -270,6 +291,8 @@ export const CONTRACT_ADDRESSES: {
BondManager: '0x0000000000000000000000000000000000000000' as const, BondManager: '0x0000000000000000000000000000000000000000' as const,
OptimismPortal: '0x1a0ad011913A150f69f6A19DF447A0CfD9551054' as const, OptimismPortal: '0x1a0ad011913A150f69f6A19DF447A0CfD9551054' as const,
L2OutputOracle: '0x9E6204F750cD866b299594e2aC9eA824E2e5f95c' as const, L2OutputOracle: '0x9E6204F750cD866b299594e2aC9eA824E2e5f95c' as const,
OptimismPortal2: '0x0000000000000000000000000000000000000000' as const,
DisputeGameFactory: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
......
...@@ -14,6 +14,8 @@ import l2ERC721Bridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L2ER ...@@ -14,6 +14,8 @@ import l2ERC721Bridge from '@eth-optimism/contracts-bedrock/forge-artifacts/L2ER
import l1Block from '@eth-optimism/contracts-bedrock/forge-artifacts/L1Block.sol/L1Block.json' import l1Block from '@eth-optimism/contracts-bedrock/forge-artifacts/L1Block.sol/L1Block.json'
import l2ToL1MessagePasser from '@eth-optimism/contracts-bedrock/forge-artifacts/L2ToL1MessagePasser.sol/L2ToL1MessagePasser.json' import l2ToL1MessagePasser from '@eth-optimism/contracts-bedrock/forge-artifacts/L2ToL1MessagePasser.sol/L2ToL1MessagePasser.json'
import gasPriceOracle from '@eth-optimism/contracts-bedrock/forge-artifacts/GasPriceOracle.sol/GasPriceOracle.json' import gasPriceOracle from '@eth-optimism/contracts-bedrock/forge-artifacts/GasPriceOracle.sol/GasPriceOracle.json'
import disputeGameFactory from '@eth-optimism/contracts-bedrock/forge-artifacts/DisputeGameFactory.sol/DisputeGameFactory.json'
import optimismPortal2 from '@eth-optimism/contracts-bedrock/forge-artifacts/OptimismPortal2.sol/OptimismPortal2.json'
import { toAddress } from './coercion' import { toAddress } from './coercion'
import { DeepPartial } from './type-utils' import { DeepPartial } from './type-utils'
...@@ -23,6 +25,7 @@ import { ...@@ -23,6 +25,7 @@ import {
CONTRACT_ADDRESSES, CONTRACT_ADDRESSES,
DEFAULT_L2_CONTRACT_ADDRESSES, DEFAULT_L2_CONTRACT_ADDRESSES,
BRIDGE_ADAPTER_DATA, BRIDGE_ADAPTER_DATA,
IGNORABLE_CONTRACTS,
} from './chain-constants' } from './chain-constants'
import { import {
OEContracts, OEContracts,
...@@ -94,6 +97,12 @@ const getContractInterfaceBedrock = (name: string): ethers.utils.Interface => { ...@@ -94,6 +97,12 @@ const getContractInterfaceBedrock = (name: string): ethers.utils.Interface => {
case 'GasPriceOracle': case 'GasPriceOracle':
artifact = gasPriceOracle artifact = gasPriceOracle
break break
case 'DisputeGameFactory':
artifact = disputeGameFactory
break
case 'OptimismPortal2':
artifact = optimismPortal2
break
} }
return new ethers.utils.Interface(artifact.abi) return new ethers.utils.Interface(artifact.abi)
} }
...@@ -119,11 +128,19 @@ export const getOEContract = ( ...@@ -119,11 +128,19 @@ export const getOEContract = (
signerOrProvider?: ethers.Signer | ethers.providers.Provider signerOrProvider?: ethers.Signer | ethers.providers.Provider
} = {} } = {}
): Contract => { ): Contract => {
// Generally we want to throw an error if a contract address is not provided but there are some
// exceptions, particularly for contracts that are part of an upgrade that has not yet been
// deployed. It's OK to not provide an address for these contracts with the caveat that this may
// cause a runtime error if the contract does actually need to be used.
const addresses = CONTRACT_ADDRESSES[l2ChainId] const addresses = CONTRACT_ADDRESSES[l2ChainId]
if (addresses === undefined && opts.address === undefined) { if (addresses === undefined && opts.address === undefined) {
throw new Error( if (IGNORABLE_CONTRACTS.includes(contractName)) {
`cannot get contract ${contractName} for unknown L2 chain ID ${l2ChainId}, you must provide an address` return undefined
) } else {
throw new Error(
`cannot get contract ${contractName} for unknown L2 chain ID ${l2ChainId}, you must provide an address`
)
}
} }
// Bedrock interfaces are backwards compatible. We can prefer Bedrock interfaces over legacy // Bedrock interfaces are backwards compatible. We can prefer Bedrock interfaces over legacy
...@@ -177,6 +194,8 @@ export const getAllOEContracts = ( ...@@ -177,6 +194,8 @@ export const getAllOEContracts = (
BondManager: undefined, BondManager: undefined,
OptimismPortal: undefined, OptimismPortal: undefined,
L2OutputOracle: undefined, L2OutputOracle: undefined,
DisputeGameFactory: undefined,
OptimismPortal2: undefined,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
} }
......
...@@ -158,6 +158,8 @@ task('deposit-erc20', 'Deposits WETH9 onto L2.') ...@@ -158,6 +158,8 @@ task('deposit-erc20', 'Deposits WETH9 onto L2.')
BondManager: ethers.constants.AddressZero, BondManager: ethers.constants.AddressZero,
OptimismPortal: json.OptimismPortalProxy, OptimismPortal: json.OptimismPortalProxy,
L2OutputOracle: json.L2OutputOracleProxy, L2OutputOracle: json.L2OutputOracleProxy,
OptimismPortal2: json.OptimismPortalProxy,
DisputeGameFactory: json.DisputeGameFactoryProxy,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
} as OEContractsLike } as OEContractsLike
......
...@@ -103,6 +103,8 @@ task('deposit-eth', 'Deposits ether to L2.') ...@@ -103,6 +103,8 @@ task('deposit-eth', 'Deposits ether to L2.')
BondManager: ethers.constants.AddressZero, BondManager: ethers.constants.AddressZero,
OptimismPortal: json.OptimismPortalProxy, OptimismPortal: json.OptimismPortalProxy,
L2OutputOracle: json.L2OutputOracleProxy, L2OutputOracle: json.L2OutputOracleProxy,
OptimismPortal2: json.OptimismPortalProxy,
DisputeGameFactory: json.DisputeGameFactoryProxy,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
} as OEContractsLike } as OEContractsLike
......
...@@ -16,6 +16,7 @@ import { ...@@ -16,6 +16,7 @@ import {
ETHBridgeAdapter, ETHBridgeAdapter,
L1ChainID, L1ChainID,
L2ChainID, L2ChainID,
IGNORABLE_CONTRACTS,
} from '../src' } from '../src'
import { DUMMY_MESSAGE, DUMMY_EXTENDED_MESSAGE } from './helpers' import { DUMMY_MESSAGE, DUMMY_EXTENDED_MESSAGE } from './helpers'
...@@ -234,7 +235,7 @@ describe('CrossChainMessenger', () => { ...@@ -234,7 +235,7 @@ describe('CrossChainMessenger', () => {
if (overrides.l1[contractName]) { if (overrides.l1[contractName]) {
const contract = messenger.contracts.l1[contractName] const contract = messenger.contracts.l1[contractName]
expect(contract.address).to.equal(overrides.l1[contractName]) expect(contract.address).to.equal(overrides.l1[contractName])
} else { } else if (!IGNORABLE_CONTRACTS.includes(contractName)) {
const contract = messenger.contracts.l1[contractName] const contract = messenger.contracts.l1[contractName]
expect(contract.address).to.equal(contractAddress) expect(contract.address).to.equal(contractAddress)
} }
......
...@@ -450,6 +450,9 @@ importers: ...@@ -450,6 +450,9 @@ importers:
rlp: rlp:
specifier: ^2.2.7 specifier: ^2.2.7
version: 2.2.7 version: 2.2.7
semver:
specifier: ^7.5.4
version: 7.5.4
devDependencies: devDependencies:
'@ethersproject/abstract-provider': '@ethersproject/abstract-provider':
specifier: ^5.7.0 specifier: ^5.7.0
...@@ -478,6 +481,9 @@ importers: ...@@ -478,6 +481,9 @@ importers:
'@types/node': '@types/node':
specifier: ^20.11.13 specifier: ^20.11.13
version: 20.11.13 version: 20.11.13
'@types/semver':
specifier: ^7.5.6
version: 7.5.6
chai-as-promised: chai-as-promised:
specifier: ^7.1.1 specifier: ^7.1.1
version: 7.1.1(chai@4.3.10) version: 7.1.1(chai@4.3.10)
...@@ -6006,6 +6012,10 @@ packages: ...@@ -6006,6 +6012,10 @@ packages:
/@types/semver@7.5.0: /@types/semver@7.5.0:
resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==}
/@types/semver@7.5.6:
resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
dev: true
/@types/send@0.17.1: /@types/send@0.17.1:
resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==}
dependencies: dependencies:
...@@ -9874,7 +9884,6 @@ packages: ...@@ -9874,7 +9884,6 @@ packages:
is-hex-prefixed: 1.0.0 is-hex-prefixed: 1.0.0
strip-hex-prefix: 1.0.0 strip-hex-prefix: 1.0.0
dev: true dev: true
bundledDependencies: false
/event-target-shim@5.0.1: /event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
......
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