Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion scripts/checks/interfaces/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ func normalizeInternalType(internalType string) string {
var excludeContracts = []string{
"IProxy", "IEIP712", "IEAS", "ISchemaResolver", "ISchemaRegistry",

// ERC-165 interfaces that bridges check deployed tokens against. Must never become ABI mirrors,
// since any change to their functions changes the interface ID and strands existing tokens.
"IOptimismMintableERC20",

// TODO: Interfaces that need to be fixed
"IInitializable", "IOptimismMintableERC20", "IResolvedDelegateProxy",
"IInitializable", "IResolvedDelegateProxy",
}

// excludeSourceContracts is a list of contracts that are allowed to not have interfaces
Expand Down
12 changes: 6 additions & 6 deletions snapshots/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@
"sourceCodeHash": "0x12ea125038b87e259a0d203e119faa6e9726ab2bdbc30430f820ccd48fe87e14"
},
"src/L2/L2ERC721Bridge.sol:L2ERC721Bridge": {
"initCodeHash": "0xe9d79ab92871ff7d1336059a8756041e052bca05f2c0e98d20ed97f545162191",
"sourceCodeHash": "0x032bda19a2c0a05e8abfdac182982ad14256cbcebc1896ba503bb1a2a681e81f"
"initCodeHash": "0x6e3e9c0aee26dcdf2e866a076e04322c9fe358ed1b0cc278c3ebdd23227dd868",
"sourceCodeHash": "0x6f1e82203041282f13949525c092d1ce1221df5d29429148e3c1b20b20aa9f99"
},
"src/L2/L2StandardBridge.sol:L2StandardBridge": {
"initCodeHash": "0x61f1e19928b92a5455d04c990071728b006d2083887250f473f147cd9002345f",
Expand All @@ -96,12 +96,12 @@
"sourceCodeHash": "0xd6e94bc9df025855916aa4184d0bc739b0fbe786dfd037b99dbb51d0d3e46918"
},
"src/L2/OptimismMintableERC721.sol:OptimismMintableERC721": {
"initCodeHash": "0x363dcb59140ecfbcdff7b5f4e822f0fdc8492fc02b6d9abf3cb822ae75e8dcfb",
"sourceCodeHash": "0xe9271c174be0579d59221bdcd66e408c0327a72a7898175bf3df6919863ef1cb"
"initCodeHash": "0x2b0a211f46ad957a142a73d61760809494dc212cd0c366ca5407f24f211ea810",
"sourceCodeHash": "0x8ae7768566b70e28bd2529c070831095b4087802f9012a0e95121f21eec9e4cb"
},
"src/L2/OptimismMintableERC721Factory.sol:OptimismMintableERC721Factory": {
"initCodeHash": "0x3f31209e30a79f367b59b66e3ee6f43f5916ec26e6d1c5a08846d636884f020d",
"sourceCodeHash": "0xb0be3deac32956251adb37d3ca61f619ca4348a1355a41c856a3a95adde0e4ff"
"initCodeHash": "0x4a584d196f965d239cf31f63b54fde68dab1dc151b5e4b700380102380abe707",
"sourceCodeHash": "0x93ca86cc152da8633ceb9f35e171b418bd4d90e52ed05ccde3e1026039272cb4"
},
"src/L2/SequencerFeeVault.sol:SequencerFeeVault": {
"initCodeHash": "0xd80f5cee90a78db1f7a506d5b6eaa22ff672a78cb2fd7ce9b7130c1ce571ffa9",
Expand Down
11 changes: 8 additions & 3 deletions src/L2/L2ERC721Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ERC721Bridge } from "src/universal/ERC721Bridge.sol";
// Libraries
import { ERC165Checker } from "lib/openzeppelin-contracts/contracts/utils/introspection/ERC165Checker.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { Constants } from "src/libraries/Constants.sol";

// Interfaces
import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol";
Expand All @@ -24,8 +25,8 @@ import { ISemver } from "interfaces/universal/ISemver.sol";
/// **WARNING**: Do not bridge an ERC721 that was originally deployed on Optimism. This
/// bridge ONLY supports ERC721s originally deployed on Ethereum.
contract L2ERC721Bridge is ERC721Bridge, ISemver {
/// @custom:semver 1.10.0
string public constant version = "1.10.0";
/// @custom:semver 1.11.0
string public constant version = "1.11.0";

/// @notice Constructs the L2ERC721Bridge contract.
constructor() ERC721Bridge() {
Expand Down Expand Up @@ -67,7 +68,11 @@ contract L2ERC721Bridge is ERC721Bridge, ISemver {
// Note that supportsInterface makes a callback to the _localToken address which is user
// provided.
require(
ERC165Checker.supportsInterface(_localToken, type(IOptimismMintableERC721).interfaceId),
ERC165Checker.supportsInterface(_localToken, Constants.OPTIMISM_MINTABLE_ERC721_INTERFACE_ID)
|| ERC165Checker.supportsInterface(_localToken, Constants.BEDROCK_OPTIMISM_MINTABLE_ERC721_INTERFACE_ID)
|| ERC165Checker.supportsInterface(
_localToken, Constants.PRE_BEDROCK_OPTIMISM_MINTABLE_ERC721_INTERFACE_ID
),
"L2ERC721Bridge: local token interface is not compliant"
);

Expand Down
11 changes: 6 additions & 5 deletions src/L2/OptimismMintableERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { ERC721 } from "lib/openzeppelin-contracts/contracts/token/ERC721/ERC721

// Libraries
import { Strings } from "lib/openzeppelin-contracts/contracts/utils/Strings.sol";
import { Constants } from "src/libraries/Constants.sol";

// Interfaces
import { ISemver } from "interfaces/universal/ISemver.sol";
import { IOptimismMintableERC721 } from "interfaces/L2/IOptimismMintableERC721.sol";

/// @title OptimismMintableERC721
/// @notice This contract is the remote representation for some token that lives on another network,
Expand Down Expand Up @@ -46,8 +46,8 @@ contract OptimismMintableERC721 is ERC721Enumerable, ISemver {
}

/// @notice Semantic version.
/// @custom:semver 1.3.2
string public constant version = "1.3.2";
/// @custom:semver 1.4.0
string public constant version = "1.4.0";

/// @param _bridge Address of the bridge on this network.
/// @param _remoteChainId Chain ID where the remote token is deployed.
Expand Down Expand Up @@ -122,8 +122,9 @@ contract OptimismMintableERC721 is ERC721Enumerable, ISemver {
/// @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) returns (bool) {
bytes4 iface = type(IOptimismMintableERC721).interfaceId;
return _interfaceId == iface || super.supportsInterface(_interfaceId);
return _interfaceId == Constants.OPTIMISM_MINTABLE_ERC721_INTERFACE_ID
|| _interfaceId == Constants.BEDROCK_OPTIMISM_MINTABLE_ERC721_INTERFACE_ID
|| super.supportsInterface(_interfaceId);
}

/// @notice Returns the base token URI.
Expand Down
4 changes: 2 additions & 2 deletions src/L2/OptimismMintableERC721Factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ contract OptimismMintableERC721Factory is ISemver {
event OptimismMintableERC721Created(address indexed localToken, address indexed remoteToken, address deployer);

/// @notice Semantic version.
/// @custom:semver 1.4.2
string public constant version = "1.4.2";
/// @custom:semver 1.5.0
string public constant version = "1.5.0";

/// @notice The semver MUST be bumped any time that there is a change in
/// the OptimismMintableERC721 token contract since this contract
Expand Down
15 changes: 15 additions & 0 deletions src/libraries/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ library Constants {
/// transactions.
address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001;

/// @notice ERC-165 interface ID of OptimismMintableERC721 tokens built from source between
/// September 2022 and Bedrock. Those tokens can't be upgraded, so this ID must be
/// accepted forever.
bytes4 internal constant PRE_BEDROCK_OPTIMISM_MINTABLE_ERC721_INTERFACE_ID = 0xe49bc7f8;

/// @notice ERC-165 interface ID of OptimismMintableERC721 tokens built from source between
/// Bedrock and op-contracts v1.8.0. Those tokens can't be upgraded, so this ID must be
/// accepted forever. Newer tokens answer it too, so that older bridges accept them.
bytes4 internal constant BEDROCK_OPTIMISM_MINTABLE_ERC721_INTERFACE_ID = 0x74259ebf;

/// @notice ERC-165 interface ID answered by OptimismMintableERC721 tokens built from
/// op-contracts v1.8.0 source onwards. Written as a literal because
/// type(IOptimismMintableERC721) changes whenever the contract's ABI does.
bytes4 internal constant OPTIMISM_MINTABLE_ERC721_INTERFACE_ID = 0xaecafc23;

/// @notice Returns the default values for the ResourceConfig. These are the recommended values
/// for a production network.
function DEFAULT_RESOURCE_CONFIG() internal pure returns (IResourceMetering.ResourceConfig memory) {
Expand Down
55 changes: 55 additions & 0 deletions test/L2/L2ERC721Bridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity 0.8.15;
import { CommonTest } from "test/setup/CommonTest.sol";

// Contracts
import { ERC721 } from "lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
import { OptimismMintableERC721 } from "src/L2/OptimismMintableERC721.sol";

// Interfaces
Expand All @@ -23,6 +24,31 @@ contract L2ERC721Bridge_TestMintableERC721_Harness is OptimismMintableERC721 {
}
}

/// @title L2ERC721Bridge_FixedInterfaceIdERC721_Harness
/// @notice An OptimismMintableERC721 stand-in that answers a single, fixed interface ID, like a
/// token deployed by an earlier factory that can't be upgraded.
contract L2ERC721Bridge_FixedInterfaceIdERC721_Harness is ERC721 {
address internal immutable REMOTE_TOKEN;
bytes4 internal immutable INTERFACE_ID;

constructor(address _remoteToken, bytes4 _interfaceId) ERC721("Test", "TST") {
REMOTE_TOKEN = _remoteToken;
INTERFACE_ID = _interfaceId;
}

function remoteToken() external view returns (address) {
return REMOTE_TOKEN;
}

function safeMint(address _to, uint256 _tokenId) external {
_safeMint(_to, _tokenId);
}

function supportsInterface(bytes4 _interfaceId) public view override returns (bool) {
return _interfaceId == INTERFACE_ID || super.supportsInterface(_interfaceId);
}
}

/// @title NonCompliantERC721
/// @notice A non-compliant ERC721 token that does not implement the full ERC721 interface.
/// This is used to test that the bridge will revert if the token does not claim to
Expand Down Expand Up @@ -166,6 +192,35 @@ contract L2ERC721Bridge_FinalizeBridgeERC721_Test is L2ERC721Bridge_Bridge_TestI
_assertTokenOwnedBy(alice);
}

/// @notice Tests that `finalizeBridgeERC721` mints a token that only answers the interface ID
/// of tokens built from source between Bedrock and op-contracts v1.8.0.
function test_finalizeBridgeERC721_bedrockInterfaceId_succeeds() external {
_finalizeBridgeERC721ToFixedInterfaceIdToken(0x74259ebf);
}

/// @notice Tests that `finalizeBridgeERC721` mints a token that only answers the interface ID
/// of tokens built from source between September 2022 and Bedrock.
function test_finalizeBridgeERC721_preBedrockInterfaceId_succeeds() external {
_finalizeBridgeERC721ToFixedInterfaceIdToken(0xe49bc7f8);
}

/// @notice Tests that `finalizeBridgeERC721` mints a token that only answers the interface ID
/// of tokens built from op-contracts v1.8.0 source onwards.
function test_finalizeBridgeERC721_currentInterfaceId_succeeds() external {
_finalizeBridgeERC721ToFixedInterfaceIdToken(0xaecafc23);
}

function _finalizeBridgeERC721ToFixedInterfaceIdToken(bytes4 _interfaceId) internal {
L2ERC721Bridge_FixedInterfaceIdERC721_Harness token =
new L2ERC721Bridge_FixedInterfaceIdERC721_Harness(remoteToken, _interfaceId);

_mockOtherBridge();
vm.prank(address(l2ERC721Bridge.messenger()));
l2ERC721Bridge.finalizeBridgeERC721(address(token), remoteToken, alice, alice, tokenId, EXTRA_DATA);

assertEq(token.ownerOf(tokenId), alice);
}

/// @notice Tests that `finalizeBridgeERC721` reverts if the token is not compliant with the
/// `IOptimismMintableERC721` interface.
function test_finalizeBridgeERC721_interfaceNotCompliant_reverts() external {
Expand Down
10 changes: 6 additions & 4 deletions test/L2/OptimismMintableERC721.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { IERC721Enumerable } from "lib/openzeppelin-contracts/contracts/token/ER
import { IERC165 } from "lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol";
import { Strings } from "lib/openzeppelin-contracts/contracts/utils/Strings.sol";
import { CommonTest } from "test/setup/CommonTest.sol";
import { OptimismMintableERC721, IOptimismMintableERC721 } from "src/L2/OptimismMintableERC721.sol";
import { OptimismMintableERC721 } from "src/L2/OptimismMintableERC721.sol";

/// @title OptimismMintableERC721_TestInit
/// @notice Reusable test initialization for `OptimismMintableERC721` tests.
Expand Down Expand Up @@ -133,10 +133,12 @@ contract OptimismMintableERC721_Burn_Test is OptimismMintableERC721_TestInit {
/// @title OptimismMintableERC721_SupportsInterface_Test
/// @notice Tests the `supportsInterface` function of the `OptimismMintableERC721` contract.
contract OptimismMintableERC721_SupportsInterface_Test is OptimismMintableERC721_TestInit {
/// @notice Tests that the `supportsInterface` function returns true for
/// IOptimismMintableERC721, IERC721Enumerable, IERC721 and IERC165 interfaces.
/// @notice Tests that the `supportsInterface` function returns true for both
/// IOptimismMintableERC721 interface IDs, IERC721Enumerable, IERC721 and IERC165.
function test_supportsInterface_succeeds() external view {
assertTrue(L2NFT.supportsInterface(type(IOptimismMintableERC721).interfaceId));
// Bridges check tokens against these IDs, so they must never change.
assertTrue(L2NFT.supportsInterface(0x74259ebf));
assertTrue(L2NFT.supportsInterface(0xaecafc23));
assertTrue(L2NFT.supportsInterface(type(IERC721Enumerable).interfaceId));
assertTrue(L2NFT.supportsInterface(type(IERC721).interfaceId));
assertTrue(L2NFT.supportsInterface(type(IERC165).interfaceId));
Expand Down
7 changes: 7 additions & 0 deletions test/universal/OptimismMintableERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ contract OptimismMintableERC20_SupportsInterface_Test is OptimismMintableERC20_T
assertTrue(L2Token.supportsInterface(type(IERC165).interfaceId));
assertTrue(L2Token.supportsInterface(type(ILegacyMintableERC20).interfaceId));
assertTrue(L2Token.supportsInterface(type(IOptimismMintableERC20).interfaceId));

// Bridges check deployed tokens against these two IDs, so changing either one strands
// every existing token.
// l1Token() ^ mint(address,uint256) ^ burn(address,uint256)
assertEq(type(ILegacyMintableERC20).interfaceId, bytes4(0x1d1d8b63));
// remoteToken() ^ bridge() ^ mint(address,uint256) ^ burn(address,uint256)
assertEq(type(IOptimismMintableERC20).interfaceId, bytes4(0xec4fc8e3));
}
}

Expand Down
Loading