diff --git a/integration-tests/contracts/FakeOptimismMintableERC721.sol b/integration-tests/contracts/FakeOptimismMintableERC721.sol index 769a29f63593..fa9e6f31aa90 100644 --- a/integration-tests/contracts/FakeOptimismMintableERC721.sol +++ b/integration-tests/contracts/FakeOptimismMintableERC721.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.9; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import { OptimismMintableERC721 } from "@eth-optimism/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721.sol"; +import { OptimismMintableERC721 } from "@eth-optimism/contracts-bedrock/contracts/universal/OptimismMintableERC721.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; contract FakeOptimismMintableERC721 is OptimismMintableERC721 { diff --git a/integration-tests/package.json b/integration-tests/package.json index 0e6d87d8f836..90867de788fd 100644 --- a/integration-tests/package.json +++ b/integration-tests/package.json @@ -30,6 +30,7 @@ "devDependencies": { "@babel/eslint-parser": "^7.5.4", "@eth-optimism/contracts": "^0.5.39", + "@eth-optimism/contracts-bedrock": "0.11.0", "@eth-optimism/contracts-periphery": "^1.0.4", "@eth-optimism/core-utils": "0.12.0", "@eth-optimism/sdk": "1.8.0", diff --git a/integration-tests/test/nft-bridge.spec.ts b/integration-tests/test/nft-bridge.spec.ts index 83e395ab06d0..4ddc74b27c63 100644 --- a/integration-tests/test/nft-bridge.spec.ts +++ b/integration-tests/test/nft-bridge.spec.ts @@ -4,10 +4,10 @@ import { ethers } from 'hardhat' import { getChainId } from '@eth-optimism/core-utils' import { predeploys } from '@eth-optimism/contracts' import Artifact__TestERC721 from '@eth-optimism/contracts-periphery/artifacts/contracts/testing/helpers/TestERC721.sol/TestERC721.json' -import Artifact__L1ERC721Bridge from '@eth-optimism/contracts-periphery/artifacts/contracts/L1/L1ERC721Bridge.sol/L1ERC721Bridge.json' -import Artifact__L2ERC721Bridge from '@eth-optimism/contracts-periphery/artifacts/contracts/L2/L2ERC721Bridge.sol/L2ERC721Bridge.json' -import Artifact__OptimismMintableERC721Factory from '@eth-optimism/contracts-periphery/artifacts/contracts/universal/op-erc721/OptimismMintableERC721Factory.sol/OptimismMintableERC721Factory.json' -import Artifact__OptimismMintableERC721 from '@eth-optimism/contracts-periphery/artifacts/contracts/universal/op-erc721/OptimismMintableERC721.sol/OptimismMintableERC721.json' +import Artifact__L1ERC721Bridge from '@eth-optimism/contracts-bedrock/artifacts/contracts/L1/L1ERC721Bridge.sol/L1ERC721Bridge.json' +import Artifact__L2ERC721Bridge from '@eth-optimism/contracts-bedrock/artifacts/contracts/L2/L2ERC721Bridge.sol/L2ERC721Bridge.json' +import Artifact__OptimismMintableERC721Factory from '@eth-optimism/contracts-bedrock/artifacts/contracts/universal/OptimismMintableERC721Factory.sol/OptimismMintableERC721Factory.json' +import Artifact__OptimismMintableERC721 from '@eth-optimism/contracts-bedrock/artifacts/contracts/universal/OptimismMintableERC721.sol/OptimismMintableERC721.json' /* Imports: Internal */ import { expect } from './shared/setup' diff --git a/packages/contracts-periphery/deployments/mainnet/L1ERC721Bridge.json b/packages/contracts-bedrock/deployments/mainnet/L1ERC721Bridge.json similarity index 100% rename from packages/contracts-periphery/deployments/mainnet/L1ERC721Bridge.json rename to packages/contracts-bedrock/deployments/mainnet/L1ERC721Bridge.json diff --git a/packages/contracts-periphery/deployments/mainnet/L1ERC721BridgeProxy.json b/packages/contracts-bedrock/deployments/mainnet/L1ERC721BridgeProxy.json similarity index 100% rename from packages/contracts-periphery/deployments/mainnet/L1ERC721BridgeProxy.json rename to packages/contracts-bedrock/deployments/mainnet/L1ERC721BridgeProxy.json diff --git a/packages/contracts-periphery/deployments/optimism/OptimismMintableERC721Factory.json b/packages/contracts-bedrock/deployments/optimism-mainnet/OptimismMintableERC721Factory.json similarity index 100% rename from packages/contracts-periphery/deployments/optimism/OptimismMintableERC721Factory.json rename to packages/contracts-bedrock/deployments/optimism-mainnet/OptimismMintableERC721Factory.json diff --git a/packages/contracts-periphery/deployments/optimism/OptimismMintableERC721FactoryProxy.json b/packages/contracts-bedrock/deployments/optimism-mainnet/OptimismMintableERC721FactoryProxy.json similarity index 100% rename from packages/contracts-periphery/deployments/optimism/OptimismMintableERC721FactoryProxy.json rename to packages/contracts-bedrock/deployments/optimism-mainnet/OptimismMintableERC721FactoryProxy.json diff --git a/packages/contracts-periphery/contracts/L1/L1ERC721Bridge.sol b/packages/contracts-periphery/contracts/L1/L1ERC721Bridge.sol deleted file mode 100644 index 5e8fa6bc832a..000000000000 --- a/packages/contracts-periphery/contracts/L1/L1ERC721Bridge.sol +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { ERC721Bridge } from "../universal/op-erc721/ERC721Bridge.sol"; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { L2ERC721Bridge } from "../L2/L2ERC721Bridge.sol"; -import { Semver } from "@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol"; - -/** - * @title L1ERC721Bridge - * @notice The L1 ERC721 bridge is a contract which works together with the L2 ERC721 bridge to - * make it possible to transfer ERC721 tokens from Ethereum to Optimism. This contract - * acts as an escrow for ERC721 tokens deposited into L2. - */ -contract L1ERC721Bridge is ERC721Bridge, Semver { - /** - * @notice Mapping of L1 token to L2 token to ID to boolean, indicating if the given L1 token - * by ID was deposited for a given L2 token. - */ - mapping(address => mapping(address => mapping(uint256 => bool))) public deposits; - - /** - * @custom:semver 1.0.0 - * - * @param _messenger Address of the CrossDomainMessenger on this network. - * @param _otherBridge Address of the ERC721 bridge on the other network. - */ - constructor(address _messenger, address _otherBridge) - Semver(1, 0, 0) - ERC721Bridge(_messenger, _otherBridge) - {} - - /************************* - * Cross-chain Functions * - *************************/ - - /** - * @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the - * recipient on this domain. - * - * @param _localToken Address of the ERC721 token on this domain. - * @param _remoteToken Address of the ERC721 token on the other domain. - * @param _from Address that triggered the bridge on the other domain. - * @param _to Address to receive the token on this domain. - * @param _tokenId ID of the token being deposited. - * @param _extraData Optional data to forward to L2. Data supplied here will not be used to - * execute any code on L2 and is only emitted as extra data for the - * convenience of off-chain tooling. - */ - function finalizeBridgeERC721( - address _localToken, - address _remoteToken, - address _from, - address _to, - uint256 _tokenId, - bytes calldata _extraData - ) external onlyOtherBridge { - require(_localToken != address(this), "L1ERC721Bridge: local token cannot be self"); - - // Checks that the L1/L2 NFT pair has a token ID that is escrowed in the L1 Bridge. - require( - deposits[_localToken][_remoteToken][_tokenId] == true, - "L1ERC721Bridge: Token ID is not escrowed in the L1 Bridge" - ); - - // Mark that the token ID for this L1/L2 token pair is no longer escrowed in the L1 - // Bridge. - deposits[_localToken][_remoteToken][_tokenId] = false; - - // When a withdrawal is finalized on L1, the L1 Bridge transfers the NFT to the - // withdrawer. - IERC721(_localToken).safeTransferFrom(address(this), _to, _tokenId); - - // slither-disable-next-line reentrancy-events - emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); - } - - /** - * @inheritdoc ERC721Bridge - */ - function _initiateBridgeERC721( - address _localToken, - address _remoteToken, - address _from, - address _to, - uint256 _tokenId, - uint32 _minGasLimit, - bytes calldata _extraData - ) internal override { - require(_remoteToken != address(0), "ERC721Bridge: remote token cannot be address(0)"); - - // Construct calldata for _l2Token.finalizeBridgeERC721(_to, _tokenId) - bytes memory message = abi.encodeWithSelector( - L2ERC721Bridge.finalizeBridgeERC721.selector, - _remoteToken, - _localToken, - _from, - _to, - _tokenId, - _extraData - ); - - // Lock token into bridge - deposits[_localToken][_remoteToken][_tokenId] = true; - IERC721(_localToken).transferFrom(_from, address(this), _tokenId); - - // Send calldata into L2 - messenger.sendMessage(otherBridge, message, _minGasLimit); - emit ERC721BridgeInitiated(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); - } -} diff --git a/packages/contracts-periphery/contracts/L2/L2ERC721Bridge.sol b/packages/contracts-periphery/contracts/L2/L2ERC721Bridge.sol deleted file mode 100644 index 7ff753d7184c..000000000000 --- a/packages/contracts-periphery/contracts/L2/L2ERC721Bridge.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { ERC721Bridge } from "../universal/op-erc721/ERC721Bridge.sol"; -import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import { L1ERC721Bridge } from "../L1/L1ERC721Bridge.sol"; -import { IOptimismMintableERC721 } from "../universal/op-erc721/IOptimismMintableERC721.sol"; -import { Semver } from "@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol"; - -/** - * @title L2ERC721Bridge - * @notice The L2 ERC721 bridge is a contract which works together with the L1 ERC721 bridge to - * make it possible to transfer ERC721 tokens from Ethereum to Optimism. This contract - * acts as a minter for new tokens when it hears about deposits into the L1 ERC721 bridge. - * This contract also acts as a burner for tokens being withdrawn. - * **WARNING**: Do not bridge an ERC721 that was originally deployed on Optimism. This - * bridge ONLY supports ERC721s originally deployed on Ethereum. Users will need to - * wait for the one-week challenge period to elapse before their Optimism-native NFT - * can be refunded on L2. - */ -contract L2ERC721Bridge is ERC721Bridge, Semver { - /** - * @custom:semver 1.0.0 - * - * @param _messenger Address of the CrossDomainMessenger on this network. - * @param _otherBridge Address of the ERC721 bridge on the other network. - */ - constructor(address _messenger, address _otherBridge) - Semver(1, 0, 0) - ERC721Bridge(_messenger, _otherBridge) - {} - - /** - * @notice Completes an ERC721 bridge from the other domain and sends the ERC721 token to the - * recipient on this domain. - * - * @param _localToken Address of the ERC721 token on this domain. - * @param _remoteToken Address of the ERC721 token on the other domain. - * @param _from Address that triggered the bridge on the other domain. - * @param _to Address to receive the token on this domain. - * @param _tokenId ID of the token being deposited. - * @param _extraData Optional data to forward to L1. Data supplied here will not be used to - * execute any code on L1 and is only emitted as extra data for the - * convenience of off-chain tooling. - */ - function finalizeBridgeERC721( - address _localToken, - address _remoteToken, - address _from, - address _to, - uint256 _tokenId, - bytes calldata _extraData - ) external onlyOtherBridge { - require(_localToken != address(this), "L2ERC721Bridge: local token cannot be self"); - - // Note that supportsInterface makes a callback to the _localToken address which is user - // provided. - require( - ERC165Checker.supportsInterface(_localToken, type(IOptimismMintableERC721).interfaceId), - "L2ERC721Bridge: local token interface is not compliant" - ); - - require( - _remoteToken == IOptimismMintableERC721(_localToken).remoteToken(), - "L2ERC721Bridge: wrong remote token for Optimism Mintable ERC721 local token" - ); - - // When a deposit is finalized, we give the NFT with the same tokenId to the account - // on L2. Note that safeMint makes a callback to the _to address which is user provided. - IOptimismMintableERC721(_localToken).safeMint(_to, _tokenId); - - // slither-disable-next-line reentrancy-events - emit ERC721BridgeFinalized(_localToken, _remoteToken, _from, _to, _tokenId, _extraData); - } - - /** - * @inheritdoc ERC721Bridge - */ - function _initiateBridgeERC721( - address _localToken, - address _remoteToken, - address _from, - address _to, - uint256 _tokenId, - uint32 _minGasLimit, - bytes calldata _extraData - ) internal override { - require(_remoteToken != address(0), "ERC721Bridge: remote token cannot be address(0)"); - - // Check that the withdrawal is being initiated by the NFT owner - require( - _from == IOptimismMintableERC721(_localToken).ownerOf(_tokenId), - "Withdrawal is not being initiated by NFT owner" - ); - - // Construct calldata for l1ERC721Bridge.finalizeBridgeERC721(_to, _tokenId) - // slither-disable-next-line reentrancy-events - address remoteToken = IOptimismMintableERC721(_localToken).remoteToken(); - require( - remoteToken == _remoteToken, - "L2ERC721Bridge: remote token does not match given value" - ); - - // When a withdrawal is initiated, we burn the withdrawer's NFT to prevent subsequent L2 - // usage - // slither-disable-next-line reentrancy-events - IOptimismMintableERC721(_localToken).burn(_from, _tokenId); - - bytes memory message = abi.encodeWithSelector( - L1ERC721Bridge.finalizeBridgeERC721.selector, - remoteToken, - _localToken, - _from, - _to, - _tokenId, - _extraData - ); - - // Send message to L1 bridge - // slither-disable-next-line reentrancy-events - messenger.sendMessage(otherBridge, message, _minGasLimit); - - // slither-disable-next-line reentrancy-events - emit ERC721BridgeInitiated(_localToken, remoteToken, _from, _to, _tokenId, _extraData); - } -} diff --git a/packages/contracts-periphery/contracts/universal/op-erc721/ERC721Bridge.sol b/packages/contracts-periphery/contracts/universal/op-erc721/ERC721Bridge.sol deleted file mode 100644 index df892cf61e5e..000000000000 --- a/packages/contracts-periphery/contracts/universal/op-erc721/ERC721Bridge.sol +++ /dev/null @@ -1,196 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { - CrossDomainMessenger -} from "@eth-optimism/contracts-bedrock/contracts/universal/CrossDomainMessenger.sol"; -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; - -/** - * @title ERC721Bridge - * @notice ERC721Bridge is a base contract for the L1 and L2 ERC721 bridges. - */ -abstract contract ERC721Bridge { - /** - * @notice Emitted when an ERC721 bridge to the other network is initiated. - * - * @param localToken Address of the token on this domain. - * @param remoteToken Address of the token on the remote domain. - * @param from Address that initiated bridging action. - * @param to Address to receive the token. - * @param tokenId ID of the specific token deposited. - * @param extraData Extra data for use on the client-side. - */ - event ERC721BridgeInitiated( - address indexed localToken, - address indexed remoteToken, - address indexed from, - address to, - uint256 tokenId, - bytes extraData - ); - - /** - * @notice Emitted when an ERC721 bridge from the other network is finalized. - * - * @param localToken Address of the token on this domain. - * @param remoteToken Address of the token on the remote domain. - * @param from Address that initiated bridging action. - * @param to Address to receive the token. - * @param tokenId ID of the specific token deposited. - * @param extraData Extra data for use on the client-side. - */ - event ERC721BridgeFinalized( - address indexed localToken, - address indexed remoteToken, - address indexed from, - address to, - uint256 tokenId, - bytes extraData - ); - - /** - * @notice Messenger contract on this domain. - */ - CrossDomainMessenger public immutable messenger; - - /** - * @notice Address of the bridge on the other network. - */ - address public immutable otherBridge; - - /** - * @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades. - */ - uint256[49] private __gap; - - /** - * @notice Ensures that the caller is a cross-chain message from the other bridge. - */ - modifier onlyOtherBridge() { - require( - msg.sender == address(messenger) && messenger.xDomainMessageSender() == otherBridge, - "ERC721Bridge: function can only be called from the other bridge" - ); - _; - } - - /** - * @param _messenger Address of the CrossDomainMessenger on this network. - * @param _otherBridge Address of the ERC721 bridge on the other network. - */ - constructor(address _messenger, address _otherBridge) { - require(_messenger != address(0), "ERC721Bridge: messenger cannot be address(0)"); - require(_otherBridge != address(0), "ERC721Bridge: other bridge cannot be address(0)"); - - messenger = CrossDomainMessenger(_messenger); - otherBridge = _otherBridge; - } - - /** - * @notice Initiates a bridge of an NFT to the caller's account on the other chain. Note that - * this function can only be called by EOAs. Smart contract wallets should use the - * `bridgeERC721To` function after ensuring that the recipient address on the remote - * chain exists. Also note that the current owner of the token on this chain must - * approve this contract to operate the NFT before it can be bridged. - * **WARNING**: Do not bridge an ERC721 that was originally deployed on Optimism. This - * bridge only supports ERC721s originally deployed on Ethereum. Users will need to - * wait for the one-week challenge period to elapse before their Optimism-native NFT - * can be refunded on L2. - * - * @param _localToken Address of the ERC721 on this domain. - * @param _remoteToken Address of the ERC721 on the remote domain. - * @param _tokenId Token ID to bridge. - * @param _minGasLimit Minimum gas limit for the bridge message on the other domain. - * @param _extraData Optional data to forward to the other chain. Data supplied here will not - * be used to execute any code on the other chain and is only emitted as - * extra data for the convenience of off-chain tooling. - */ - function bridgeERC721( - address _localToken, - address _remoteToken, - uint256 _tokenId, - uint32 _minGasLimit, - bytes calldata _extraData - ) external { - // Modifier requiring sender to be EOA. This prevents against a user error that would occur - // if the sender is a smart contract wallet that has a different address on the remote chain - // (or doesn't have an address on the remote chain at all). The user would fail to receive - // the NFT if they use this function because it sends the NFT to the same address as the - // caller. This check could be bypassed by a malicious contract via initcode, but it takes - // care of the user error we want to avoid. - require(!Address.isContract(msg.sender), "ERC721Bridge: account is not externally owned"); - - _initiateBridgeERC721( - _localToken, - _remoteToken, - msg.sender, - msg.sender, - _tokenId, - _minGasLimit, - _extraData - ); - } - - /** - * @notice Initiates a bridge of an NFT to some recipient's account on the other chain. Note - * that the current owner of the token on this chain must approve this contract to - * operate the NFT before it can be bridged. - * **WARNING**: Do not bridge an ERC721 that was originally deployed on Optimism. This - * bridge only supports ERC721s originally deployed on Ethereum. Users will need to - * wait for the one-week challenge period to elapse before their Optimism-native NFT - * can be refunded on L2. - * - * @param _localToken Address of the ERC721 on this domain. - * @param _remoteToken Address of the ERC721 on the remote domain. - * @param _to Address to receive the token on the other domain. - * @param _tokenId Token ID to bridge. - * @param _minGasLimit Minimum gas limit for the bridge message on the other domain. - * @param _extraData Optional data to forward to the other chain. Data supplied here will not - * be used to execute any code on the other chain and is only emitted as - * extra data for the convenience of off-chain tooling. - */ - function bridgeERC721To( - address _localToken, - address _remoteToken, - address _to, - uint256 _tokenId, - uint32 _minGasLimit, - bytes calldata _extraData - ) external { - require(_to != address(0), "ERC721Bridge: nft recipient cannot be address(0)"); - - _initiateBridgeERC721( - _localToken, - _remoteToken, - msg.sender, - _to, - _tokenId, - _minGasLimit, - _extraData - ); - } - - /** - * @notice Internal function for initiating a token bridge to the other domain. - * - * @param _localToken Address of the ERC721 on this domain. - * @param _remoteToken Address of the ERC721 on the remote domain. - * @param _from Address of the sender on this domain. - * @param _to Address to receive the token on the other domain. - * @param _tokenId Token ID to bridge. - * @param _minGasLimit Minimum gas limit for the bridge message on the other domain. - * @param _extraData Optional data to forward to the other domain. Data supplied here will - * not be used to execute any code on the other domain and is only emitted - * as extra data for the convenience of off-chain tooling. - */ - function _initiateBridgeERC721( - address _localToken, - address _remoteToken, - address _from, - address _to, - uint256 _tokenId, - uint32 _minGasLimit, - bytes calldata _extraData - ) internal virtual; -} diff --git a/packages/contracts-periphery/contracts/universal/op-erc721/IOptimismMintableERC721.sol b/packages/contracts-periphery/contracts/universal/op-erc721/IOptimismMintableERC721.sol deleted file mode 100644 index a37dbd3d6ac5..000000000000 --- a/packages/contracts-periphery/contracts/universal/op-erc721/IOptimismMintableERC721.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { - IERC721Enumerable -} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; - -/** - * @title IOptimismMintableERC721 - * @notice Interface for contracts that are compatible with the OptimismMintableERC721 standard. - * Tokens that follow this standard can be easily transferred across the ERC721 bridge. - */ -interface IOptimismMintableERC721 is IERC721Enumerable { - /** - * @notice Emitted when a token is minted. - * - * @param account Address of the account the token was minted to. - * @param tokenId Token ID of the minted token. - */ - event Mint(address indexed account, uint256 tokenId); - - /** - * @notice Emitted when a token is burned. - * - * @param account Address of the account the token was burned from. - * @param tokenId Token ID of the burned token. - */ - event Burn(address indexed account, uint256 tokenId); - - /** - * @notice Chain ID of the chain where the remote token is deployed. - */ - function remoteChainId() external view returns (uint256); - - /** - * @notice Address of the token on the remote domain. - */ - function remoteToken() external view returns (address); - - /** - * @notice Address of the ERC721 bridge on this network. - */ - function bridge() external view returns (address); - - /** - * @notice Mints some token ID for a user, checking first that contract recipients - * are aware of the ERC721 protocol to prevent tokens from being forever locked. - * - * @param _to Address of the user to mint the token for. - * @param _tokenId Token ID to mint. - */ - function safeMint(address _to, uint256 _tokenId) external; - - /** - * @notice Burns a token ID from a user. - * - * @param _from Address of the user to burn the token from. - * @param _tokenId Token ID to burn. - */ - function burn(address _from, uint256 _tokenId) external; -} diff --git a/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721.sol b/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721.sol deleted file mode 100644 index 0307a8f580a1..000000000000 --- a/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721.sol +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { - ERC721Enumerable -} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; -import { IOptimismMintableERC721 } from "./IOptimismMintableERC721.sol"; - -/** - * @title OptimismMintableERC721 - * @notice This contract is the remote representation for some token that lives on another network, - * typically an Optimism representation of an Ethereum-based token. Standard reference - * implementation that can be extended or modified according to your needs. - */ -contract OptimismMintableERC721 is ERC721Enumerable, IOptimismMintableERC721 { - /** - * @inheritdoc IOptimismMintableERC721 - */ - uint256 public immutable remoteChainId; - - /** - * @inheritdoc IOptimismMintableERC721 - */ - address public immutable remoteToken; - - /** - * @inheritdoc IOptimismMintableERC721 - */ - address public immutable bridge; - - /** - * @notice Base token URI for this token. - */ - string public baseTokenURI; - - /** - * @param _bridge Address of the bridge on this network. - * @param _remoteChainId Chain ID where the remote token is deployed. - * @param _remoteToken Address of the corresponding token on the other network. - * @param _name ERC721 name. - * @param _symbol ERC721 symbol. - */ - constructor( - address _bridge, - uint256 _remoteChainId, - address _remoteToken, - string memory _name, - string memory _symbol - ) ERC721(_name, _symbol) { - require(_bridge != address(0), "OptimismMintableERC721: bridge cannot be address(0)"); - require(_remoteChainId != 0, "OptimismMintableERC721: remote chain id cannot be zero"); - require( - _remoteToken != address(0), - "OptimismMintableERC721: remote token cannot be address(0)" - ); - - remoteChainId = _remoteChainId; - remoteToken = _remoteToken; - bridge = _bridge; - - // Creates a base URI in the format specified by EIP-681: - // https://eips.ethereum.org/EIPS/eip-681 - baseTokenURI = string( - abi.encodePacked( - "ethereum:", - Strings.toHexString(uint160(_remoteToken), 20), - "@", - Strings.toString(_remoteChainId), - "/tokenURI?uint256=" - ) - ); - } - - /** - * @notice Modifier that prevents callers other than the bridge from calling the function. - */ - modifier onlyBridge() { - require(msg.sender == bridge, "OptimismMintableERC721: only bridge can call this function"); - _; - } - - /** - * @inheritdoc IOptimismMintableERC721 - */ - function safeMint(address _to, uint256 _tokenId) external virtual onlyBridge { - _safeMint(_to, _tokenId); - - emit Mint(_to, _tokenId); - } - - /** - * @inheritdoc IOptimismMintableERC721 - */ - function burn(address _from, uint256 _tokenId) external virtual onlyBridge { - _burn(_tokenId); - - emit Burn(_from, _tokenId); - } - - /** - * @notice Checks if a given interface ID is supported by this contract. - * - * @param _interfaceId The interface ID to check. - * - * @return True if the interface ID is supported, false otherwise. - */ - function supportsInterface(bytes4 _interfaceId) - public - view - override(ERC721Enumerable, IERC165) - returns (bool) - { - bytes4 iface1 = type(IERC165).interfaceId; - bytes4 iface2 = type(IOptimismMintableERC721).interfaceId; - return - _interfaceId == iface1 || - _interfaceId == iface2 || - super.supportsInterface(_interfaceId); - } - - /** - * @notice Returns the base token URI. - * - * @return Base token URI. - */ - function _baseURI() internal view virtual override returns (string memory) { - return baseTokenURI; - } -} diff --git a/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721Factory.sol b/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721Factory.sol deleted file mode 100644 index 670fa76b31b1..000000000000 --- a/packages/contracts-periphery/contracts/universal/op-erc721/OptimismMintableERC721Factory.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { OptimismMintableERC721 } from "./OptimismMintableERC721.sol"; -import { Semver } from "@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol"; - -/** - * @title OptimismMintableERC721Factory - * @notice Factory contract for creating OptimismMintableERC721 contracts. - */ -contract OptimismMintableERC721Factory is Semver { - /** - * @notice Emitted whenever a new OptimismMintableERC721 contract is created. - * - * @param localToken Address of the token on the this domain. - * @param remoteToken Address of the token on the remote domain. - * @param deployer Address of the initiator of the deployment - */ - event OptimismMintableERC721Created( - address indexed localToken, - address indexed remoteToken, - address deployer - ); - - /** - * @notice Address of the ERC721 bridge on this network. - */ - address public immutable bridge; - - /** - * @notice Chain ID for the remote network. - */ - uint256 public immutable remoteChainId; - - /** - * @notice Tracks addresses created by this factory. - */ - mapping(address => bool) public isOptimismMintableERC721; - - /** - * @custom:semver 1.0.0 - * - * @param _bridge Address of the ERC721 bridge on this network. - */ - constructor(address _bridge, uint256 _remoteChainId) Semver(1, 0, 0) { - require( - _bridge != address(0), - "OptimismMintableERC721Factory: bridge cannot be address(0)" - ); - require( - _remoteChainId != 0, - "OptimismMintableERC721Factory: remote chain id cannot be zero" - ); - - bridge = _bridge; - remoteChainId = _remoteChainId; - } - - /** - * @notice Creates an instance of the standard ERC721. - * - * @param _remoteToken Address of the corresponding token on the other domain. - * @param _name ERC721 name. - * @param _symbol ERC721 symbol. - */ - function createOptimismMintableERC721( - address _remoteToken, - string memory _name, - string memory _symbol - ) external returns (address) { - require( - _remoteToken != address(0), - "OptimismMintableERC721Factory: L1 token address cannot be address(0)" - ); - - address localToken = address( - new OptimismMintableERC721(bridge, remoteChainId, _remoteToken, _name, _symbol) - ); - - isOptimismMintableERC721[localToken] = true; - emit OptimismMintableERC721Created(localToken, _remoteToken, msg.sender); - - return localToken; - } -} diff --git a/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeImplementation.ts b/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeImplementation.ts deleted file mode 100644 index b01e6f654c7f..000000000000 --- a/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeImplementation.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* Imports: External */ -import { DeployFunction } from 'hardhat-deploy/dist/types' -import { HardhatRuntimeEnvironment } from 'hardhat/types' -import '@eth-optimism/hardhat-deploy-config' -import '@nomiclabs/hardhat-ethers' -import 'hardhat-deploy' -import fetch from 'node-fetch' - -import { - isTargetL1Network, - predeploy, - getProxyAdmin, - validateERC721Bridge, -} from '../../src/nft-bridge-deploy-helpers' - -// Handle the `ops` deployment -const getL1CrossDomainMessengerProxyDeployment = async ( - hre: HardhatRuntimeEnvironment -) => { - const network = hre.network.name - if (network === 'ops-l1') { - const res = await fetch( - 'http://localhost:8080/deployments/local/Proxy__OVM_L1CrossDomainMessenger.json' - ) - return res.json() - } else { - return hre.deployments.get('Proxy__OVM_L1CrossDomainMessenger') - } -} - -const deployFn: DeployFunction = async (hre) => { - const { deployer } = await hre.getNamedAccounts() - const { deploy } = hre.deployments - const { getAddress } = hre.ethers.utils - - if (!isTargetL1Network(hre.network.name)) { - console.log(`Deploying to unsupported network ${hre.network.name}`) - return - } - - console.log(`Deploying L1ERC721Bridge to ${hre.network.name}`) - console.log(`Using deployer ${deployer}`) - - const Deployment__L1ERC721BridgeProxy = await hre.deployments.get( - 'L1ERC721BridgeProxy' - ) - - const L1ERC721BridgeProxy = await hre.ethers.getContractAt( - 'Proxy', - Deployment__L1ERC721BridgeProxy.address - ) - - const admin = await L1ERC721BridgeProxy.callStatic.admin() - if (getAddress(admin) !== getAddress(deployer)) { - throw new Error('deployer is not proxy admin') - } - - // Get the address of the currently deployed L1CrossDomainMessenger. - // This should be the address of the proxy - const Deployment__L1CrossDomainMessengerProxy = - await getL1CrossDomainMessengerProxyDeployment(hre) - - const L1CrossDomainMessengerProxyAddress = - Deployment__L1CrossDomainMessengerProxy.address - - // Deploy the L1ERC721Bridge. The arguments are - // - messenger - // - otherBridge - // Since this is the L1ERC721Bridge, the otherBridge is the - // predeploy address - await deploy('L1ERC721Bridge', { - from: deployer, - args: [L1CrossDomainMessengerProxyAddress, predeploy], - log: true, - waitConfirmations: 1, - }) - - const Deployment__L1ERC721Bridge = await hre.deployments.get('L1ERC721Bridge') - console.log( - `L1ERC721Bridge deployed to ${Deployment__L1ERC721Bridge.address}` - ) - - await validateERC721Bridge(hre, Deployment__L1ERC721Bridge.address, { - messenger: L1CrossDomainMessengerProxyAddress, - otherBridge: predeploy, - }) - - { - // Upgrade the Proxy to the newly deployed implementation - const tx = await L1ERC721BridgeProxy.upgradeTo( - Deployment__L1ERC721Bridge.address - ) - const receipt = await tx.wait() - console.log(`L1ERC721BridgeProxy upgraded: ${receipt.transactionHash}`) - } - - { - // Set the admin correctly - const newAdmin = getProxyAdmin(hre.network.name) - const tx = await L1ERC721BridgeProxy.changeAdmin(newAdmin) - const receipt = await tx.wait() - console.log(`L1ERC721BridgeProxy admin updated: ${receipt.transactionHash}`) - } - - await validateERC721Bridge(hre, L1ERC721BridgeProxy.address, { - messenger: L1CrossDomainMessengerProxyAddress, - otherBridge: predeploy, - }) -} - -deployFn.tags = ['L1ERC721BridgeImplementation'] -deployFn.dependencies = ['L1ERC721BridgeProxy'] - -export default deployFn diff --git a/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeProxy.ts b/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeProxy.ts deleted file mode 100644 index 68fd1f6a0c86..000000000000 --- a/packages/contracts-periphery/deploy/nft-bridge/L1ERC721BridgeProxy.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* Imports: External */ -import { DeployFunction } from 'hardhat-deploy/dist/types' -import '@eth-optimism/hardhat-deploy-config' -import '@nomiclabs/hardhat-ethers' -import 'hardhat-deploy' - -import { isTargetL1Network } from '../../src/nft-bridge-deploy-helpers' - -const deployFn: DeployFunction = async (hre) => { - const { deployer } = await hre.getNamedAccounts() - const { deploy } = hre.deployments - - if (!isTargetL1Network(hre.network.name)) { - console.log(`Deploying to unsupported network ${hre.network.name}`) - return - } - - console.log(`Deploying L1ERC721BridgeProxy to ${hre.network.name}`) - console.log(`Using deployer ${deployer}`) - - await deploy('L1ERC721BridgeProxy', { - contract: 'Proxy', - from: deployer, - args: [deployer], - log: true, - waitConfirmations: 1, - }) - - const Deployment__L1ERC721BridgeProxy = await hre.deployments.get( - 'L1ERC721BridgeProxy' - ) - console.log( - `L1ERC721BridgeProxy deployed to ${Deployment__L1ERC721BridgeProxy.address}` - ) -} - -deployFn.tags = ['L1ERC721BridgeProxy'] - -export default deployFn diff --git a/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeImplementation.ts b/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeImplementation.ts deleted file mode 100644 index 63e6859aa6c2..000000000000 --- a/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeImplementation.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* Imports: External */ -import { DeployFunction } from 'hardhat-deploy/dist/types' -import '@eth-optimism/hardhat-deploy-config' -import '@nomiclabs/hardhat-ethers' -import 'hardhat-deploy' -import { predeploys } from '@eth-optimism/contracts' - -import { - isTargetL2Network, - predeploy, - validateERC721Bridge, - getProxyAdmin, -} from '../../src/nft-bridge-deploy-helpers' - -const deployFn: DeployFunction = async (hre) => { - const { deployer } = await hre.getNamedAccounts() - const { getAddress } = hre.ethers.utils - - if (!isTargetL2Network(hre.network.name)) { - console.log(`Deploying to unsupported network ${hre.network.name}`) - return - } - - console.log(`Deploying L2ERC721Bridge to ${hre.network.name}`) - console.log(`Using deployer ${deployer}`) - - const L2ERC721BridgeProxy = await hre.ethers.getContractAt('Proxy', predeploy) - - // Check to make sure that the admin of the proxy is the deployer. - // The deployer of the L2ERC721Bridge should be the same as the - // admin of the L2ERC721BridgeProxy so that it is easy to upgrade - // the implementation. The admin is then changed depending on the - // network after the L2ERC721BridgeProxy is upgraded to the implementation - const admin = await L2ERC721BridgeProxy.callStatic.admin() - - if (getAddress(admin) !== getAddress(deployer)) { - throw new Error(`Unexpected admin ${admin}`) - } - - const Deployment__L1ERC721Bridge = await hre.companionNetworks[ - 'l1' - ].deployments.get('L1ERC721BridgeProxy') - - const L1ERC721BridgeAddress = Deployment__L1ERC721Bridge.address - - // Deploy the L2ERC721Bridge implementation - await hre.deployments.deploy('L2ERC721Bridge', { - from: deployer, - args: [predeploys.L2CrossDomainMessenger, L1ERC721BridgeAddress], - log: true, - waitConfirmations: 1, - }) - - const Deployment__L2ERC721Bridge = await hre.deployments.get('L2ERC721Bridge') - console.log( - `L2ERC721Bridge deployed to ${Deployment__L2ERC721Bridge.address}` - ) - - await validateERC721Bridge(hre, Deployment__L2ERC721Bridge.address, { - messenger: predeploys.L2CrossDomainMessenger, - otherBridge: L1ERC721BridgeAddress, - }) - - { - // Upgrade the implementation of the proxy to the newly deployed - // L2ERC721Bridge - const tx = await L2ERC721BridgeProxy.upgradeTo( - Deployment__L2ERC721Bridge.address - ) - const receipt = await tx.wait() - console.log( - `Upgraded the implementation of the L2ERC721BridgeProxy: ${receipt.transactionhash}` - ) - } - - await validateERC721Bridge(hre, L2ERC721BridgeProxy.address, { - messenger: predeploys.L2CrossDomainMessenger, - otherBridge: L1ERC721BridgeAddress, - }) - - { - const newAdmin = getProxyAdmin(hre.network.name) - console.log(`Changing admin to ${newAdmin}`) - const tx = await L2ERC721BridgeProxy.changeAdmin(newAdmin) - const receipt = await tx.wait() - console.log( - `Changed admin of the L2ERC721BridgeProxy: ${receipt.transactionHash}` - ) - } -} - -deployFn.tags = ['L2ERC721BridgeImplementation'] -deployFn.dependencies = ['L2ERC721BridgeProxy', 'L1ERC721BridgeProxy'] - -export default deployFn diff --git a/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeProxy.ts b/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeProxy.ts deleted file mode 100644 index b3e6fc8e9f6b..000000000000 --- a/packages/contracts-periphery/deploy/nft-bridge/L2ERC721BridgeProxy.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* Imports: External */ -import { DeployFunction } from 'hardhat-deploy/dist/types' -import '@eth-optimism/hardhat-deploy-config' -import '@nomiclabs/hardhat-ethers' -import 'hardhat-deploy' - -const predeploy = '0x4200000000000000000000000000000000000014' - -const deployFn: DeployFunction = async (hre) => { - const { deployer } = await hre.getNamedAccounts() - const { getAddress } = hre.ethers.utils - - console.log(`Deploying L2ERC721BridgeProxy to ${hre.network.name}`) - console.log(`Using deployer ${deployer}`) - - // Check to make sure that the Proxy has not been deployed yet - const pre = await hre.ethers.provider.getCode(predeploy, 'latest') - if (pre !== '0x') { - console.log(`Code already deployed to ${predeploy}`) - return - } - - // A special deployer account must be used - const mainnetDeployer = getAddress( - '0x53A6eecC2dD4795Fcc68940ddc6B4d53Bd88Bd9E' - ) - const goerliDeployer = getAddress( - '0x5c679a57e018f5f146838138d3e032ef4913d551' - ) - const kovanDeployer = getAddress('0xa81224490b9fa4930a2e920550cd1c9106bb6d9e') - const localDeployer = getAddress('0xdfc82d475833a50de90c642770f34a9db7deb725') - - // Deploy the L2ERC721BridgeProxy as a predeploy address - if (hre.network.name === 'optimism') { - if (getAddress(deployer) !== mainnetDeployer) { - throw new Error(`Incorrect deployer: ${deployer}`) - } - } else if (hre.network.name === 'optimism-goerli') { - if (getAddress(deployer) !== goerliDeployer) { - throw new Error(`Incorrect deployer: ${deployer}`) - } - } else if (hre.network.name === 'ops-l2') { - if (getAddress(deployer) !== localDeployer) { - throw new Error(`Incorrect deployer: ${deployer}`) - } - } else if (hre.network.name === 'optimism-kovan') { - if (getAddress(deployer) !== kovanDeployer) { - throw new Error(`Incorrect deployer: ${deployer}`) - } - } else { - throw new Error(`Unknown network: ${hre.network.name}`) - } - - // Set the deployer as the admin of the Proxy. This is - // temporary, the admin will be updated when deploying - // the implementation - await hre.deployments.deploy('L2ERC721BridgeProxy', { - contract: 'Proxy', - from: deployer, - args: [deployer], - log: true, - waitConfirmations: 1, - }) - - // Check that the Proxy was deployed to the correct address - const code = await hre.ethers.provider.getCode(predeploy) - if (code === '0x') { - throw new Error('Code is not set at expected predeploy address') - } - console.log(`L2ERC721BridgeProxy deployed to ${predeploy}`) -} - -deployFn.tags = ['L2ERC721BridgeProxy'] - -export default deployFn diff --git a/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryImplementation.ts b/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryImplementation.ts deleted file mode 100644 index 8666a2c96138..000000000000 --- a/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryImplementation.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* Imports: External */ -import { DeployFunction } from 'hardhat-deploy/dist/types' -import '@eth-optimism/hardhat-deploy-config' -import '@nomiclabs/hardhat-ethers' -import 'hardhat-deploy' - -import { getProxyAdmin, predeploy } from '../../src/nft-bridge-deploy-helpers' - -const deployFn: DeployFunction = async (hre) => { - const { deployer } = await hre.getNamedAccounts() - const { getAddress } = hre.ethers.utils - - console.log(`Deploying OptimismMintableERC721Factory to ${hre.network.name}`) - console.log(`Using deployer ${deployer}`) - - const Deployment__OptimismMintableERC721FactoryProxy = - await hre.deployments.get('OptimismMintableERC721FactoryProxy') - - const OptimismMintableERC721FactoryProxy = await hre.ethers.getContractAt( - 'Proxy', - Deployment__OptimismMintableERC721FactoryProxy.address - ) - - // Check that the admin of the OptimismMintableERC721FactoryProxy is the - // deployer. This makes it easy to upgrade the implementation of the proxy - // and then transfer the admin privilege after deploying the implementation - const admin = await OptimismMintableERC721FactoryProxy.callStatic.admin() - if (getAddress(admin) !== getAddress(deployer)) { - throw new Error('deployer is not proxy admin') - } - - let remoteChainId: number - if (hre.network.name === 'optimism') { - remoteChainId = 1 - } else if (hre.network.name === 'optimism-goerli') { - remoteChainId = 5 - } else if (hre.network.name === 'ops-l2') { - remoteChainId = 31337 - } else if (hre.network.name === 'optimism-kovan') { - remoteChainId = 42 - } else { - remoteChainId = hre.deployConfig.remoteChainId - } - - if (typeof remoteChainId !== 'number') { - throw new Error('remoteChainId not defined') - } - - await hre.deployments.deploy('OptimismMintableERC721Factory', { - from: deployer, - args: [predeploy, remoteChainId], - log: true, - waitConfirmations: 1, - }) - - const Deployment__OptimismMintableERC721Factory = await hre.deployments.get( - 'OptimismMintableERC721Factory' - ) - console.log( - `OptimismMintableERC721Factory deployed to ${Deployment__OptimismMintableERC721Factory.address}` - ) - - { - // Upgrade the Proxy to the newly deployed implementation - const tx = await OptimismMintableERC721FactoryProxy.upgradeTo( - Deployment__OptimismMintableERC721Factory.address - ) - const receipt = await tx.wait() - console.log( - `OptimismMintableERC721FactoryProxy upgraded: ${receipt.transactionHash}` - ) - } - - { - const newAdmin = getProxyAdmin(hre.network.name) - const tx = await OptimismMintableERC721FactoryProxy.changeAdmin(newAdmin) - const receipt = await tx.wait() - console.log( - `OptimismMintableERC721FactoryProxy admin updated: ${receipt.transactionHash}` - ) - } -} - -deployFn.tags = ['OptimismMintableERC721FactoryImplementation'] -deployFn.dependencies = ['OptimismMintableERC721FactoryProxy'] - -export default deployFn diff --git a/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryProxy.ts b/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryProxy.ts deleted file mode 100644 index 9d33385abe81..000000000000 --- a/packages/contracts-periphery/deploy/nft-bridge/OptimismMintableERC721FactoryProxy.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* Imports: External */ -import { DeployFunction } from 'hardhat-deploy/dist/types' -import '@eth-optimism/hardhat-deploy-config' -import '@nomiclabs/hardhat-ethers' -import 'hardhat-deploy' - -const deployFn: DeployFunction = async (hre) => { - const { deployer } = await hre.getNamedAccounts() - const { deploy } = hre.deployments - - console.log( - `Deploying OptimismMintableERC721FactoryProxy to ${hre.network.name}` - ) - console.log(`Using deployer ${deployer}`) - - // Deploy the OptimismMintableERC721FactoryProxy with - // the deployer as the admin. The admin and implementation - // will be updated with the deployment of the implementation - await deploy('OptimismMintableERC721FactoryProxy', { - contract: 'Proxy', - from: deployer, - args: [deployer], - log: true, - waitConfirmations: 1, - }) - - const Deployment__OptimismMintableERC721FactoryProxy = - await hre.deployments.get('OptimismMintableERC721FactoryProxy') - console.log( - `OptimismMintableERC721FactoryProxy deployed to ${Deployment__OptimismMintableERC721FactoryProxy.address}` - ) -} - -deployFn.tags = ['OptimismMintableERC721FactoryProxy'] - -export default deployFn diff --git a/packages/contracts-periphery/package.json b/packages/contracts-periphery/package.json index fa88a65fa70b..b86292b3b2a3 100644 --- a/packages/contracts-periphery/package.json +++ b/packages/contracts-periphery/package.json @@ -55,7 +55,6 @@ }, "devDependencies": { "@defi-wonderland/smock": "^2.0.7", - "@eth-optimism/contracts": "^0.5.39", "@eth-optimism/contracts-bedrock": "0.10.0", "@eth-optimism/core-utils": "^0.12.0", "@eth-optimism/hardhat-deploy-config": "^0.2.5", diff --git a/packages/contracts-periphery/test/contracts/L1/L1ERC721Bridge.spec.ts b/packages/contracts-periphery/test/contracts/L1/L1ERC721Bridge.spec.ts deleted file mode 100644 index 8246eba2dc79..000000000000 --- a/packages/contracts-periphery/test/contracts/L1/L1ERC721Bridge.spec.ts +++ /dev/null @@ -1,425 +0,0 @@ -/* Imports */ -import { ethers } from 'hardhat' -import { Signer, ContractFactory, Contract, constants } from 'ethers' -import { Interface } from 'ethers/lib/utils' -import { - smock, - MockContractFactory, - FakeContract, - MockContract, -} from '@defi-wonderland/smock' -import ICrossDomainMessenger from '@eth-optimism/contracts/artifacts/contracts/libraries/bridge/ICrossDomainMessenger.sol/ICrossDomainMessenger.json' - -import { NON_NULL_BYTES32, NON_ZERO_ADDRESS } from '../../helpers' -import { expect } from '../../setup' - -const ERR_INVALID_X_DOMAIN_MESSAGE = - 'ERC721Bridge: function can only be called from the other bridge' -const DUMMY_L2_ERC721_ADDRESS = ethers.utils.getAddress( - '0x' + 'abba'.repeat(10) -) -const DUMMY_L2_BRIDGE_ADDRESS = ethers.utils.getAddress( - '0x' + 'acdc'.repeat(10) -) - -const FINALIZATION_GAS = 600_000 - -describe('L1ERC721Bridge', () => { - // init signers - let l1MessengerImpersonator: Signer - let alice: Signer - let bob: Signer - let bobsAddress - let aliceAddress - let tokenId - let aliceInitialBalance - - // we can just make up this string since it's on the "other" Layer - let Factory__L1ERC721: MockContractFactory - let IL2ERC721Bridge: Interface - before(async () => { - ;[l1MessengerImpersonator, alice, bob] = await ethers.getSigners() - - // deploy an ERC721 contract on L1 - Factory__L1ERC721 = await smock.mock( - '@openzeppelin/contracts/token/ERC721/ERC721.sol:ERC721' - ) - - // get an L2ERC721Bridge Interface - IL2ERC721Bridge = (await ethers.getContractFactory('L2ERC721Bridge')) - .interface - - aliceAddress = await alice.getAddress() - bobsAddress = await bob.getAddress() - aliceInitialBalance = 5 - tokenId = 10 - }) - - let L1ERC721: MockContract - let L1ERC721Bridge: Contract - let Fake__L1CrossDomainMessenger: FakeContract - let Factory__L1ERC721Bridge: ContractFactory - beforeEach(async () => { - // Get a new mock L1 messenger - Fake__L1CrossDomainMessenger = await smock.fake( - new ethers.utils.Interface(ICrossDomainMessenger.abi), - { address: await l1MessengerImpersonator.getAddress() } // This allows us to use an ethers override {from: Fake__L1CrossDomainMessenger.address} to mock calls - ) - - // Deploy the contract under test - Factory__L1ERC721Bridge = await ethers.getContractFactory('L1ERC721Bridge') - L1ERC721Bridge = await Factory__L1ERC721Bridge.deploy( - Fake__L1CrossDomainMessenger.address, - DUMMY_L2_BRIDGE_ADDRESS - ) - - L1ERC721 = await Factory__L1ERC721.deploy('L1ERC721', 'ERC') - - await L1ERC721.setVariable('_owners', { - [tokenId]: aliceAddress, - }) - await L1ERC721.setVariable('_balances', { - [aliceAddress]: aliceInitialBalance, - }) - }) - - describe('constructor', async () => { - it('initializes correctly', async () => { - it('reverts if cross domain messenger is address(0)', async () => { - await expect( - Factory__L1ERC721Bridge.deploy( - constants.AddressZero, - DUMMY_L2_BRIDGE_ADDRESS - ) - ).to.be.revertedWith('ERC721Bridge: messenger cannot be address(0)') - }) - - it('reverts if other bridge is address(0)', async () => { - await expect( - Factory__L1ERC721Bridge.deploy( - Fake__L1CrossDomainMessenger.address, - constants.AddressZero - ) - ).to.be.revertedWith('ERC721Bridge: other bridge cannot be address(0)') - }) - - expect(await L1ERC721Bridge.messenger()).equals( - Fake__L1CrossDomainMessenger.address - ) - expect(await L1ERC721Bridge.otherBridge()).equals(DUMMY_L2_BRIDGE_ADDRESS) - }) - }) - - describe('ERC721 deposits', () => { - beforeEach(async () => { - await L1ERC721.connect(alice).approve(L1ERC721Bridge.address, tokenId) - }) - - it('bridgeERC721() reverts if remote token is address(0)', async () => { - await expect( - L1ERC721Bridge.connect(alice).bridgeERC721( - L1ERC721.address, - constants.AddressZero, - tokenId, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith('ERC721Bridge: remote token cannot be address(0)') - }) - - it('bridgeERC721() escrows the deposit and sends the correct deposit message', async () => { - // alice calls deposit on the bridge and the L1 bridge calls transferFrom on the token. - // emits an ERC721BridgeInitiated event with the correct arguments. - await expect( - L1ERC721Bridge.connect(alice).bridgeERC721( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - tokenId, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - ) - .to.emit(L1ERC721Bridge, 'ERC721BridgeInitiated') - .withArgs( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - aliceAddress, - aliceAddress, - tokenId, - NON_NULL_BYTES32 - ) - - const depositCallToMessenger = - Fake__L1CrossDomainMessenger.sendMessage.getCall(0) - - // alice's balance decreases by 1 - const depositerBalance = await L1ERC721.balanceOf(aliceAddress) - expect(depositerBalance).to.equal(aliceInitialBalance - 1) - - // bridge's balance increases by 1 - const bridgeBalance = await L1ERC721.balanceOf(L1ERC721Bridge.address) - expect(bridgeBalance).to.equal(1) - - // Check the correct cross-chain call was sent: - // Message should be sent to the L2 bridge - expect(depositCallToMessenger.args[0]).to.equal(DUMMY_L2_BRIDGE_ADDRESS) - // Message data should be a call telling the L2DepositedERC721 to finalize the deposit - - // the L1 bridge sends the correct message to the L1 messenger - expect(depositCallToMessenger.args[1]).to.equal( - IL2ERC721Bridge.encodeFunctionData('finalizeBridgeERC721', [ - DUMMY_L2_ERC721_ADDRESS, - L1ERC721.address, - aliceAddress, - aliceAddress, - tokenId, - NON_NULL_BYTES32, - ]) - ) - expect(depositCallToMessenger.args[2]).to.equal(FINALIZATION_GAS) - - // Updates the deposits mapping - expect( - await L1ERC721Bridge.deposits( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - tokenId - ) - ).to.equal(true) - }) - - it('bridgeERC721To() reverts if NFT receiver is address(0)', async () => { - await expect( - L1ERC721Bridge.connect(alice).bridgeERC721To( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - constants.AddressZero, - tokenId, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith('ERC721Bridge: nft recipient cannot be address(0)') - }) - - it('bridgeERC721To() escrows the deposited NFT and sends the correct deposit message', async () => { - // depositor calls deposit on the bridge and the L1 bridge calls transferFrom on the token. - // emits an ERC721BridgeInitiated event with the correct arguments. - await expect( - L1ERC721Bridge.connect(alice).bridgeERC721To( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - bobsAddress, - tokenId, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - ) - .to.emit(L1ERC721Bridge, 'ERC721BridgeInitiated') - .withArgs( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - aliceAddress, - bobsAddress, - tokenId, - NON_NULL_BYTES32 - ) - - const depositCallToMessenger = - Fake__L1CrossDomainMessenger.sendMessage.getCall(0) - - // alice's balance decreases by 1 - const depositerBalance = await L1ERC721.balanceOf(aliceAddress) - expect(depositerBalance).to.equal(aliceInitialBalance - 1) - - // bridge's balance is increased - const bridgeBalance = await L1ERC721.balanceOf(L1ERC721Bridge.address) - expect(bridgeBalance).to.equal(1) - - // bridge is owner of tokenId - const tokenIdOwner = await L1ERC721.ownerOf(tokenId) - expect(tokenIdOwner).to.equal(L1ERC721Bridge.address) - - // Check the correct cross-chain call was sent: - // Message should be sent to the L2DepositedERC721 on L2 - expect(depositCallToMessenger.args[0]).to.equal(DUMMY_L2_BRIDGE_ADDRESS) - // Message data should be a call telling the L2DepositedERC721 to finalize the deposit - - // the L1 bridge sends the correct message to the L1 messenger - expect(depositCallToMessenger.args[1]).to.equal( - IL2ERC721Bridge.encodeFunctionData('finalizeBridgeERC721', [ - DUMMY_L2_ERC721_ADDRESS, - L1ERC721.address, - aliceAddress, - bobsAddress, - tokenId, - NON_NULL_BYTES32, - ]) - ) - expect(depositCallToMessenger.args[2]).to.equal(FINALIZATION_GAS) - - // Updates the deposits mapping - expect( - await L1ERC721Bridge.deposits( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - tokenId - ) - ).to.equal(true) - }) - - it('cannot bridgeERC721 from a contract account', async () => { - await expect( - L1ERC721Bridge.bridgeERC721( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - tokenId, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith('ERC721Bridge: account is not externally owned') - }) - - describe('Handling ERC721.transferFrom() failures that revert', () => { - it('bridgeERC721(): will revert if ERC721.transferFrom() reverts', async () => { - await expect( - L1ERC721Bridge.connect(bob).bridgeERC721To( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - bobsAddress, - tokenId, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith('ERC721: transfer from incorrect owner') - }) - - it('bridgeERC721To(): will revert if ERC721.transferFrom() reverts', async () => { - await expect( - L1ERC721Bridge.connect(bob).bridgeERC721To( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - bobsAddress, - tokenId, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith('ERC721: transfer from incorrect owner') - }) - - it('bridgeERC721To(): will revert if the L1 ERC721 is zero address', async () => { - await expect( - L1ERC721Bridge.connect(alice).bridgeERC721To( - constants.AddressZero, - DUMMY_L2_ERC721_ADDRESS, - bobsAddress, - tokenId, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith('function call to a non-contract account') - }) - - it('bridgeERC721To(): will revert if the L1 ERC721 has no code', async () => { - await expect( - L1ERC721Bridge.connect(alice).bridgeERC721To( - bobsAddress, - DUMMY_L2_ERC721_ADDRESS, - bobsAddress, - tokenId, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith('function call to a non-contract account') - }) - }) - }) - - describe('ERC721 withdrawals', () => { - it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L1 account', async () => { - await expect( - L1ERC721Bridge.connect(alice).finalizeBridgeERC721( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - constants.AddressZero, - constants.AddressZero, - tokenId, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith(ERR_INVALID_X_DOMAIN_MESSAGE) - }) - - it('onlyFromCrossDomainAccount: should revert on calls from the right crossDomainMessenger, but wrong xDomainMessageSender (ie. not the L2DepositedERC721)', async () => { - await expect( - L1ERC721Bridge.finalizeBridgeERC721( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - constants.AddressZero, - constants.AddressZero, - tokenId, - NON_NULL_BYTES32, - { - from: Fake__L1CrossDomainMessenger.address, - } - ) - ).to.be.revertedWith(ERR_INVALID_X_DOMAIN_MESSAGE) - }) - - describe('withdrawal attempts that pass the onlyFromCrossDomainAccount check', () => { - beforeEach(async () => { - // First Alice will send an NFT so that there's a balance to be withdrawn - await L1ERC721.connect(alice).approve(L1ERC721Bridge.address, tokenId) - - await L1ERC721Bridge.connect(alice).bridgeERC721( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - tokenId, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - - // make sure bridge owns NFT - expect(await L1ERC721.ownerOf(tokenId)).to.equal(L1ERC721Bridge.address) - - Fake__L1CrossDomainMessenger.xDomainMessageSender.returns( - DUMMY_L2_BRIDGE_ADDRESS - ) - }) - - it('should credit funds to the withdrawer to finalize withdrawal', async () => { - // finalizing the withdrawal emits an ERC721BridgeFinalized event with the correct arguments. - await expect( - L1ERC721Bridge.finalizeBridgeERC721( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - NON_ZERO_ADDRESS, - NON_ZERO_ADDRESS, - tokenId, - NON_NULL_BYTES32, - { from: Fake__L1CrossDomainMessenger.address } - ) - ) - .to.emit(L1ERC721Bridge, 'ERC721BridgeFinalized') - .withArgs( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - NON_ZERO_ADDRESS, - NON_ZERO_ADDRESS, - tokenId, - NON_NULL_BYTES32 - ) - - // NFT is transferred to new owner - expect(await L1ERC721.ownerOf(tokenId)).to.equal(NON_ZERO_ADDRESS) - - // deposits state variable is updated - expect( - await L1ERC721Bridge.deposits( - L1ERC721.address, - DUMMY_L2_ERC721_ADDRESS, - tokenId - ) - ).to.equal(false) - }) - }) - }) -}) diff --git a/packages/contracts-periphery/test/contracts/L2/L2ERC721Bridge.spec.ts b/packages/contracts-periphery/test/contracts/L2/L2ERC721Bridge.spec.ts deleted file mode 100644 index 566777c871ac..000000000000 --- a/packages/contracts-periphery/test/contracts/L2/L2ERC721Bridge.spec.ts +++ /dev/null @@ -1,402 +0,0 @@ -/* Imports */ -import { ethers } from 'hardhat' -import { Signer, ContractFactory, Contract, constants } from 'ethers' -import { smock, FakeContract } from '@defi-wonderland/smock' -import ICrossDomainMessenger from '@eth-optimism/contracts/artifacts/contracts/libraries/bridge/ICrossDomainMessenger.sol/ICrossDomainMessenger.json' -import { toRpcHexString } from '@eth-optimism/core-utils' - -import { NON_NULL_BYTES32, NON_ZERO_ADDRESS } from '../../helpers' -import { expect } from '../../setup' - -const ERR_INVALID_X_DOMAIN_MESSAGE = - 'ERC721Bridge: function can only be called from the other bridge' -const DUMMY_L1BRIDGE_ADDRESS: string = - '0x1234123412341234123412341234123412341234' -const DUMMY_L1ERC721_ADDRESS: string = - '0x2234223412342234223422342234223422342234' -const ERR_INVALID_WITHDRAWAL: string = - 'Withdrawal is not being initiated by NFT owner' -const TOKEN_ID: number = 10 - -const FINALIZATION_GAS = 600_000 - -describe('L2ERC721Bridge', () => { - let alice: Signer - let aliceAddress: string - let bob: Signer - let bobsAddress: string - let l2MessengerImpersonator: Signer - let Factory__L1ERC721Bridge: ContractFactory - - before(async () => { - // Create a special signer which will enable us to send messages from the L2Messenger contract - ;[l2MessengerImpersonator, alice, bob] = await ethers.getSigners() - aliceAddress = await alice.getAddress() - bobsAddress = await bob.getAddress() - Factory__L1ERC721Bridge = await ethers.getContractFactory('L1ERC721Bridge') - }) - - let Factory__L2ERC721Bridge: ContractFactory - let L2ERC721Bridge: Contract - let L2ERC721: Contract - let Fake__L2CrossDomainMessenger: FakeContract - beforeEach(async () => { - // Get a new fake L2 messenger - Fake__L2CrossDomainMessenger = await smock.fake( - new ethers.utils.Interface(ICrossDomainMessenger.abi), - // This allows us to use an ethers override {from: Fake__L2CrossDomainMessenger.address} to mock calls - { address: await l2MessengerImpersonator.getAddress() } - ) - - // Deploy the contract under test - Factory__L2ERC721Bridge = await ethers.getContractFactory('L2ERC721Bridge') - L2ERC721Bridge = await Factory__L2ERC721Bridge.deploy( - Fake__L2CrossDomainMessenger.address, - DUMMY_L1BRIDGE_ADDRESS - ) - - // Deploy an L2 ERC721 - L2ERC721 = await ( - await ethers.getContractFactory('OptimismMintableERC721') - ).deploy( - L2ERC721Bridge.address, - 100, - DUMMY_L1ERC721_ADDRESS, - 'L2Token', - 'L2T', - { gasLimit: 4_000_000 } // Necessary to avoid an out-of-gas error - ) - }) - - describe('constructor', async () => { - it('reverts if cross domain messenger is address(0)', async () => { - await expect( - Factory__L2ERC721Bridge.deploy( - constants.AddressZero, - DUMMY_L1BRIDGE_ADDRESS - ) - ).to.be.revertedWith('ERC721Bridge: messenger cannot be address(0)') - }) - - it('reverts if other bridge is address(0)', async () => { - await expect( - Factory__L2ERC721Bridge.deploy( - Fake__L2CrossDomainMessenger.address, - constants.AddressZero - ) - ).to.be.revertedWith('ERC721Bridge: other bridge cannot be address(0)') - }) - - it('initializes correctly', async () => { - expect(await L2ERC721Bridge.messenger()).equals( - Fake__L2CrossDomainMessenger.address - ) - expect(await L2ERC721Bridge.otherBridge()).equals(DUMMY_L1BRIDGE_ADDRESS) - }) - }) - - // test the transfer flow of moving a token from L1 to L2 - describe('finalizeBridgeERC721', () => { - it('onlyFromCrossDomainAccount: should revert on calls from a non-crossDomainMessenger L2 account', async () => { - await expect( - L2ERC721Bridge.connect(alice).finalizeBridgeERC721( - DUMMY_L1ERC721_ADDRESS, - NON_ZERO_ADDRESS, - NON_ZERO_ADDRESS, - NON_ZERO_ADDRESS, - TOKEN_ID, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith(ERR_INVALID_X_DOMAIN_MESSAGE) - }) - - it('onlyFromCrossDomainAccount: should revert on calls from the right crossDomainMessenger, but wrong xDomainMessageSender (ie. not the L1ERC721Bridge)', async () => { - Fake__L2CrossDomainMessenger.xDomainMessageSender.returns( - NON_ZERO_ADDRESS - ) - - await expect( - L2ERC721Bridge.connect(l2MessengerImpersonator).finalizeBridgeERC721( - DUMMY_L1ERC721_ADDRESS, - NON_ZERO_ADDRESS, - NON_ZERO_ADDRESS, - NON_ZERO_ADDRESS, - TOKEN_ID, - NON_NULL_BYTES32, - { - from: Fake__L2CrossDomainMessenger.address, - } - ) - ).to.be.revertedWith(ERR_INVALID_X_DOMAIN_MESSAGE) - }) - - it('should credit funds to the depositor', async () => { - Fake__L2CrossDomainMessenger.xDomainMessageSender.returns( - DUMMY_L1BRIDGE_ADDRESS - ) - - // Assert that nobody owns the L2 token initially - await expect(L2ERC721.ownerOf(TOKEN_ID)).to.be.revertedWith( - 'ERC721: invalid token ID' - ) - - // Successfully finalizes the deposit. - const expectedResult = expect( - L2ERC721Bridge.connect(l2MessengerImpersonator).finalizeBridgeERC721( - L2ERC721.address, - DUMMY_L1ERC721_ADDRESS, - aliceAddress, - bobsAddress, - TOKEN_ID, - NON_NULL_BYTES32, - { - from: Fake__L2CrossDomainMessenger.address, - } - ) - ) - - // Depositing causes an ERC721BridgeFinalized event to be emitted. - await expectedResult.to - .emit(L2ERC721Bridge, 'ERC721BridgeFinalized') - .withArgs( - L2ERC721.address, - DUMMY_L1ERC721_ADDRESS, - aliceAddress, - bobsAddress, - TOKEN_ID, - NON_NULL_BYTES32 - ) - - // Causes a Transfer event to be emitted from the L2 ERC721. - await expectedResult.to - .emit(L2ERC721, 'Transfer') - .withArgs(constants.AddressZero, bobsAddress, TOKEN_ID) - - // Bob is now the owner of the L2 ERC721 - const tokenIdOwner = await L2ERC721.ownerOf(TOKEN_ID) - tokenIdOwner.should.equal(bobsAddress) - }) - }) - - describe('withdrawals', () => { - let L2Token: Contract - beforeEach(async () => { - L2Token = await ( - await ethers.getContractFactory('OptimismMintableERC721') - ).deploy( - L2ERC721Bridge.address, - 100, - DUMMY_L1ERC721_ADDRESS, - 'L2Token', - 'L2T' - ) - - await ethers.provider.send('hardhat_impersonateAccount', [ - L2ERC721Bridge.address, - ]) - - await ethers.provider.send('hardhat_setBalance', [ - L2ERC721Bridge.address, - toRpcHexString(ethers.utils.parseEther('1')), - ]) - - const signer = await ethers.getSigner(L2ERC721Bridge.address) - await L2Token.connect(signer).safeMint(aliceAddress, TOKEN_ID) - }) - - it('bridgeERC721() reverts if remote token is address(0)', async () => { - await expect( - L2ERC721Bridge.connect(alice).bridgeERC721( - L2Token.address, - constants.AddressZero, - TOKEN_ID, - FINALIZATION_GAS, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith('ERC721Bridge: remote token cannot be address(0)') - }) - - it('bridgeERC721() reverts when called by non-owner of nft', async () => { - await expect( - L2ERC721Bridge.connect(bob).bridgeERC721( - L2Token.address, - DUMMY_L1ERC721_ADDRESS, - TOKEN_ID, - 0, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith(ERR_INVALID_WITHDRAWAL) - }) - - it('bridgeERC721() reverts if called by a contract', async () => { - await expect( - L2ERC721Bridge.connect(l2MessengerImpersonator).bridgeERC721( - L2Token.address, - DUMMY_L1ERC721_ADDRESS, - TOKEN_ID, - 0, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith('ERC721Bridge: account is not externally owned') - }) - - it('bridgeERC721() burns and sends the correct withdrawal message', async () => { - // Make sure that alice begins as the NFT owner - expect(await L2Token.ownerOf(TOKEN_ID)).to.equal(aliceAddress) - - // Initiates a successful withdrawal. - const expectedResult = expect( - L2ERC721Bridge.connect(alice).bridgeERC721( - L2Token.address, - DUMMY_L1ERC721_ADDRESS, - TOKEN_ID, - 0, - NON_NULL_BYTES32 - ) - ) - - // A successful withdrawal causes an ERC721BridgeInitiated event to be emitted from the L2 ERC721 Bridge. - await expectedResult.to - .emit(L2ERC721Bridge, 'ERC721BridgeInitiated') - .withArgs( - L2Token.address, - DUMMY_L1ERC721_ADDRESS, - aliceAddress, - aliceAddress, - TOKEN_ID, - NON_NULL_BYTES32 - ) - - // A withdrawal also causes a Transfer event to be emitted the L2 ERC721, signifying that the L2 token - // has been burnt. - await expectedResult.to - .emit(L2Token, 'Transfer') - .withArgs(aliceAddress, constants.AddressZero, TOKEN_ID) - - // Assert Alice's balance went down - const aliceBalance = await L2Token.balanceOf(aliceAddress) - expect(aliceBalance).to.equal(0) - - // Assert that the token isn't owned by anyone - await expect(L2Token.ownerOf(TOKEN_ID)).to.be.revertedWith( - 'ERC721: invalid token ID' - ) - - const withdrawalCallToMessenger = - Fake__L2CrossDomainMessenger.sendMessage.getCall(0) - - // Assert the correct cross-chain call was sent: - // Message should be sent to the L1ERC721Bridge on L1 - expect(withdrawalCallToMessenger.args[0]).to.equal(DUMMY_L1BRIDGE_ADDRESS) - // Message data should be a call telling the L1ERC721Bridge to finalize the withdrawal - expect(withdrawalCallToMessenger.args[1]).to.equal( - Factory__L1ERC721Bridge.interface.encodeFunctionData( - 'finalizeBridgeERC721', - [ - DUMMY_L1ERC721_ADDRESS, - L2Token.address, - aliceAddress, - aliceAddress, - TOKEN_ID, - NON_NULL_BYTES32, - ] - ) - ) - // gaslimit should be correct - expect(withdrawalCallToMessenger.args[2]).to.equal(0) - }) - - it('bridgeERC721To() reverts if NFT receiver is address(0)', async () => { - await expect( - L2ERC721Bridge.connect(alice).bridgeERC721To( - L2Token.address, - DUMMY_L1ERC721_ADDRESS, - constants.AddressZero, - TOKEN_ID, - 0, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith('ERC721Bridge: nft recipient cannot be address(0)') - }) - - it('bridgeERC721To() reverts when called by non-owner of nft', async () => { - await expect( - L2ERC721Bridge.connect(bob).bridgeERC721To( - L2Token.address, - DUMMY_L1ERC721_ADDRESS, - bobsAddress, - TOKEN_ID, - 0, - NON_NULL_BYTES32 - ) - ).to.be.revertedWith(ERR_INVALID_WITHDRAWAL) - }) - - it('bridgeERC721To() burns and sends the correct withdrawal message', async () => { - // Make sure that alice begins as the NFT owner - expect(await L2Token.ownerOf(TOKEN_ID)).to.equal(aliceAddress) - - // Initiates a successful withdrawal. - const expectedResult = expect( - L2ERC721Bridge.connect(alice).bridgeERC721To( - L2Token.address, - DUMMY_L1ERC721_ADDRESS, - bobsAddress, - TOKEN_ID, - 0, - NON_NULL_BYTES32 - ) - ) - - // A successful withdrawal causes an ERC721BridgeInitiated event to be emitted from the L2 ERC721 Bridge. - await expectedResult.to - .emit(L2ERC721Bridge, 'ERC721BridgeInitiated') - .withArgs( - L2Token.address, - DUMMY_L1ERC721_ADDRESS, - aliceAddress, - bobsAddress, - TOKEN_ID, - NON_NULL_BYTES32 - ) - - // A withdrawal also causes a Transfer event to be emitted the L2 ERC721, signifying that the L2 token - // has been burnt. - await expectedResult.to - .emit(L2Token, 'Transfer') - .withArgs(aliceAddress, constants.AddressZero, TOKEN_ID) - - // Assert Alice's balance went down - const aliceBalance = await L2Token.balanceOf(aliceAddress) - expect(aliceBalance).to.equal(0) - - // Assert that the token isn't owned by anyone - await expect(L2Token.ownerOf(TOKEN_ID)).to.be.revertedWith( - 'ERC721: invalid token ID' - ) - - const withdrawalCallToMessenger = - Fake__L2CrossDomainMessenger.sendMessage.getCall(0) - - // Assert the correct cross-chain call was sent. - // Message should be sent to the L1ERC721Bridge on L1 - expect(withdrawalCallToMessenger.args[0]).to.equal(DUMMY_L1BRIDGE_ADDRESS) - // The message data should be a call telling the L1ERC721Bridge to finalize the withdrawal - expect(withdrawalCallToMessenger.args[1]).to.equal( - Factory__L1ERC721Bridge.interface.encodeFunctionData( - 'finalizeBridgeERC721', - [ - DUMMY_L1ERC721_ADDRESS, - L2Token.address, - aliceAddress, - bobsAddress, - TOKEN_ID, - NON_NULL_BYTES32, - ] - ) - ) - // gas value is ignored and set to 0. - expect(withdrawalCallToMessenger.args[2]).to.equal(0) - }) - }) -}) diff --git a/packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721.spec.ts b/packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721.spec.ts deleted file mode 100644 index 19ac72d6f561..000000000000 --- a/packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721.spec.ts +++ /dev/null @@ -1,167 +0,0 @@ -/* External Imports */ -import { ethers } from 'hardhat' -import { Signer, Contract } from 'ethers' -import { smock, FakeContract } from '@defi-wonderland/smock' - -/* Internal Imports */ -import { expect } from '../../setup' - -const TOKEN_ID = 10 -const DUMMY_L1ERC721_ADDRESS: string = - '0x0034223412342234223422342234223422342234' - -describe('OptimismMintableERC721', () => { - let l2BridgeImpersonator: Signer - let alice: Signer - let Fake__L2ERC721Bridge: FakeContract - let OptimismMintableERC721: Contract - let l2BridgeImpersonatorAddress: string - let aliceAddress: string - let baseUri: string - const remoteChainId = 100 - - let Factory__OptimismMintableERC721 - before(async () => { - ;[l2BridgeImpersonator, alice] = await ethers.getSigners() - l2BridgeImpersonatorAddress = await l2BridgeImpersonator.getAddress() - aliceAddress = await alice.getAddress() - - baseUri = ''.concat( - 'ethereum:', - DUMMY_L1ERC721_ADDRESS, - '@', - remoteChainId.toString(), - '/tokenURI?uint256=' - ) - - Factory__OptimismMintableERC721 = await ethers.getContractFactory( - 'OptimismMintableERC721' - ) - OptimismMintableERC721 = await Factory__OptimismMintableERC721.deploy( - l2BridgeImpersonatorAddress, - remoteChainId, - DUMMY_L1ERC721_ADDRESS, - 'L2ERC721', - 'ERC' - ) - - // Get a new fake L2 bridge - Fake__L2ERC721Bridge = await smock.fake( - 'L2ERC721Bridge', - // This allows us to use an ethers override {from: Fake__L2ERC721Bridge.address} to mock calls - { address: await l2BridgeImpersonator.getAddress() } - ) - - // mint an nft to alice - await OptimismMintableERC721.connect(l2BridgeImpersonator).safeMint( - aliceAddress, - TOKEN_ID, - { - from: Fake__L2ERC721Bridge.address, - } - ) - }) - - describe('constructor', () => { - it('should revert if bridge is address(0)', async () => { - await expect( - Factory__OptimismMintableERC721.deploy( - ethers.constants.AddressZero, - remoteChainId, - DUMMY_L1ERC721_ADDRESS, - 'L2ERC721', - 'ERC' - ) - ).to.be.revertedWith( - 'OptimismMintableERC721: bridge cannot be address(0)' - ) - }) - - it('should revert if remote chain id is address(0)', async () => { - await expect( - Factory__OptimismMintableERC721.deploy( - l2BridgeImpersonatorAddress, - 0, - DUMMY_L1ERC721_ADDRESS, - 'L2ERC721', - 'ERC' - ) - ).to.be.revertedWith( - 'OptimismMintableERC721: remote chain id cannot be zero' - ) - }) - - it('should revert if remote token is address(0)', async () => { - await expect( - Factory__OptimismMintableERC721.deploy( - l2BridgeImpersonatorAddress, - remoteChainId, - ethers.constants.AddressZero, - 'L2ERC721', - 'ERC' - ) - ).to.be.revertedWith( - 'OptimismMintableERC721: remote token cannot be address(0)' - ) - }) - - it('should be able to create a standard ERC721 contract with the correct parameters', async () => { - expect(await OptimismMintableERC721.bridge()).to.equal( - l2BridgeImpersonatorAddress - ) - expect(await OptimismMintableERC721.remoteToken()).to.equal( - DUMMY_L1ERC721_ADDRESS - ) - expect(await OptimismMintableERC721.name()).to.equal('L2ERC721') - expect(await OptimismMintableERC721.symbol()).to.equal('ERC') - expect(await OptimismMintableERC721.baseTokenURI()).to.equal(baseUri) - - // alice has been minted an nft - expect(await OptimismMintableERC721.ownerOf(TOKEN_ID)).to.equal( - aliceAddress - ) - }) - }) - - describe('mint and burn', () => { - it('should not allow anyone but the L2 bridge to mint and burn', async () => { - await expect( - OptimismMintableERC721.connect(alice).safeMint(aliceAddress, 100) - ).to.be.revertedWith( - 'OptimismMintableERC721: only bridge can call this function' - ) - await expect( - OptimismMintableERC721.connect(alice).burn(aliceAddress, 100) - ).to.be.revertedWith( - 'OptimismMintableERC721: only bridge can call this function' - ) - }) - }) - - describe('supportsInterface', () => { - it('should return the correct interface support', async () => { - // ERC165 - expect(await OptimismMintableERC721.supportsInterface(0x01ffc9a7)).to.be - .true - - // OptimismMintableERC721 - expect(await OptimismMintableERC721.supportsInterface(0xe49bc7f8)).to.be - .true - - // ERC721 - expect(await OptimismMintableERC721.supportsInterface(0x80ac58cd)).to.be - .true - - // Some bad interface - expect(await OptimismMintableERC721.supportsInterface(0xffffffff)).to.be - .false - }) - }) - - describe('tokenURI', () => { - it('should return the correct token uri', async () => { - const tokenUri = baseUri.concat(TOKEN_ID.toString()) - expect(await OptimismMintableERC721.tokenURI(TOKEN_ID)).to.equal(tokenUri) - }) - }) -}) diff --git a/packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721Factory.spec.ts b/packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721Factory.spec.ts deleted file mode 100644 index 997c9d02fabd..000000000000 --- a/packages/contracts-periphery/test/contracts/universal/OptimismMintableERC721Factory.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* External Imports */ -import { ethers } from 'hardhat' -import { Signer, ContractFactory, Contract } from 'ethers' -import { - smock, - MockContractFactory, - MockContract, -} from '@defi-wonderland/smock' - -/* Internal Imports */ -import { expect } from '../../setup' - -const DUMMY_L2_BRIDGE_ADDRESS: string = ethers.utils.getAddress( - '0x' + 'acdc'.repeat(10) -) - -describe('OptimismMintableERC721Factory', () => { - let signer: Signer - let Factory__L1ERC721: MockContractFactory - let L1ERC721: MockContract - let OptimismMintableERC721Factory: Contract - let baseURI: string - const remoteChainId = 100 - - let Factory__OptimismMintableERC721Factory: ContractFactory - beforeEach(async () => { - ;[signer] = await ethers.getSigners() - - // deploy an ERC721 contract on L1 - Factory__L1ERC721 = await smock.mock( - '@openzeppelin/contracts/token/ERC721/ERC721.sol:ERC721' - ) - L1ERC721 = await Factory__L1ERC721.deploy('L1ERC721', 'ERC') - - Factory__OptimismMintableERC721Factory = await ethers.getContractFactory( - 'OptimismMintableERC721Factory' - ) - OptimismMintableERC721Factory = - await Factory__OptimismMintableERC721Factory.deploy( - DUMMY_L2_BRIDGE_ADDRESS, - remoteChainId - ) - - baseURI = ''.concat( - 'ethereum:', - L1ERC721.address.toLowerCase(), - '@', - remoteChainId.toString(), - '/tokenURI?uint256=' - ) - }) - - it('should revert if bridge is initialized as address(0)', async () => { - await expect( - Factory__OptimismMintableERC721Factory.deploy( - ethers.constants.AddressZero, - remoteChainId - ) - ).to.be.revertedWith( - 'OptimismMintableERC721Factory: bridge cannot be address(0)' - ) - }) - - it('should revert if remote chain id is initialized as zero', async () => { - await expect( - Factory__OptimismMintableERC721Factory.deploy(DUMMY_L2_BRIDGE_ADDRESS, 0) - ).to.be.revertedWith( - 'OptimismMintableERC721Factory: remote chain id cannot be zero' - ) - }) - - it('should be deployed with the correct constructor argument', async () => { - expect(await OptimismMintableERC721Factory.bridge()).to.equal( - DUMMY_L2_BRIDGE_ADDRESS - ) - }) - - it('should be able to create a standard ERC721 contract', async () => { - const tx = await OptimismMintableERC721Factory.createOptimismMintableERC721( - L1ERC721.address, - 'L2ERC721', - 'ERC' - ) - const receipt = await tx.wait() - - // Get the OptimismMintableERC721Created event - const erc721CreatedEvent = receipt.events[0] - - // Expect there to be an event emitted for the standard token creation - expect(erc721CreatedEvent.event).to.be.eq('OptimismMintableERC721Created') - - // Get the L2 ERC721 address from the emitted event and check it was created correctly - const l2ERC721Address = erc721CreatedEvent.args.localToken - const OptimismMintableERC721 = new Contract( - l2ERC721Address, - (await ethers.getContractFactory('OptimismMintableERC721')).interface, - signer - ) - - expect(await OptimismMintableERC721.bridge()).to.equal( - DUMMY_L2_BRIDGE_ADDRESS - ) - expect(await OptimismMintableERC721.remoteToken()).to.equal( - L1ERC721.address - ) - expect(await OptimismMintableERC721.name()).to.equal('L2ERC721') - expect(await OptimismMintableERC721.symbol()).to.equal('ERC') - expect(await OptimismMintableERC721.baseTokenURI()).to.equal(baseURI) - - expect( - await OptimismMintableERC721Factory.isOptimismMintableERC721( - OptimismMintableERC721.address - ) - ).to.equal(true) - }) - - it('should not be able to create a standard token with a 0 address for l1 token', async () => { - await expect( - OptimismMintableERC721Factory.createOptimismMintableERC721( - ethers.constants.AddressZero, - 'L2ERC721', - 'ERC' - ) - ).to.be.revertedWith( - 'OptimismMintableERC721Factory: L1 token address cannot be address(0)' - ) - }) -})