diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 1d3f5c04399fd..7013535f0ee86 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -208,12 +208,12 @@ "sourceCodeHash": "0xd1479c60087f352385b6d5379ef3cc07839f671d617626b4c94ece91da781ef2" }, "src/universal/OptimismMintableERC20.sol": { - "initCodeHash": "0xfc77e4db406c232d8b84a3f77b939fb08fa27852faa5f4b0d78d998402caf308", - "sourceCodeHash": "0xd7957c662ef03fc0cc3440a6ec6737a55f90b52a977262a260cd99fe96494267" + "initCodeHash": "0x28c88484e1932253d6d12954492ac8a70744dc15c84429089af9944e5b158fd9", + "sourceCodeHash": "0x740b4043436d1b314ee3ba145acfcde60b6abd8416ea594f2b8e890b5d0bce6b" }, "src/universal/OptimismMintableERC20Factory.sol": { - "initCodeHash": "0x1cc94179ce28fb34c8e28b8d2015b95588e93a45730dae9ee7da859a9f66e0e6", - "sourceCodeHash": "0x46d1d4a9ed1b1f4c60d42bf6c9982ffc72cbd759a4aae5246f89ccbb8699c2a1" + "initCodeHash": "0x3ebd2297c0af2856a432daf29d186d0751f7edb1c777abbe136953038cf5d1ba", + "sourceCodeHash": "0xf8425f65eb5520d55710907d67a9d6fa277263285e1b79ba299815d1c76919a3" }, "src/universal/OptimismMintableERC721.sol": { "initCodeHash": "0x5a995fc043f8268a6d5c6284ad85b0de21328cd47277114aeba2c03484deaf91", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json b/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json index 3c6f5e9ab3480..5a5763c73962b 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismMintableERC20.json @@ -43,6 +43,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PERMIT2", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, { "inputs": [], "name": "REMOTE_TOKEN", @@ -60,12 +86,12 @@ "inputs": [ { "internalType": "address", - "name": "owner", + "name": "_owner", "type": "address" }, { "internalType": "address", - "name": "spender", + "name": "_spender", "type": "address" } ], @@ -272,6 +298,68 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "remoteToken", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimismMintableERC20.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismMintableERC20.json index 418a98546cf77..2ad020cb1c4fd 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimismMintableERC20.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismMintableERC20.json @@ -33,5 +33,19 @@ "offset": 0, "slot": "4", "type": "string" + }, + { + "bytes": "32", + "label": "_nonces", + "offset": 0, + "slot": "5", + "type": "mapping(address => struct Counters.Counter)" + }, + { + "bytes": "32", + "label": "_PERMIT_TYPEHASH_DEPRECATED_SLOT", + "offset": 0, + "slot": "6", + "type": "bytes32" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol b/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol index dd207caf167fb..838a21af56626 100644 --- a/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol +++ b/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol @@ -2,9 +2,11 @@ pragma solidity 0.8.15; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { ILegacyMintableERC20, IOptimismMintableERC20 } from "src/universal/interfaces/IOptimismMintableERC20.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { Preinstalls } from "src/libraries/Preinstalls.sol"; /// @title OptimismMintableERC20 /// @notice OptimismMintableERC20 is a standard extension of the base ERC20 token contract designed @@ -12,7 +14,7 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; /// use an OptimismMintablERC20 as the L2 representation of an L1 token, or vice-versa. /// Designed to be backwards compatible with the older StandardL2ERC20 token which was only /// meant for use on L2. -contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, ERC20, ISemver { +contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, ERC20Permit, ISemver { /// @notice Address of the corresponding version of this token on the remote chain. address public immutable REMOTE_TOKEN; @@ -39,8 +41,16 @@ contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, } /// @notice Semantic version. - /// @custom:semver 1.3.1-beta.1 - string public constant version = "1.3.1-beta.1"; + /// @custom:semver 1.4.0-beta.1 + string public constant version = "1.4.0-beta.1"; + + /// @notice Getter function for the permit2 address. It deterministically deployed + /// so it will always be at the same address. It is also included as a preinstall, + /// so it exists in the genesis state of chains. + /// @return Address of permit2 on this network. + function PERMIT2() public pure returns (address) { + return Preinstalls.Permit2; + } /// @param _bridge Address of the L2 standard bridge. /// @param _remoteToken Address of the corresponding L1 token. @@ -54,12 +64,35 @@ contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, uint8 _decimals ) ERC20(_name, _symbol) + ERC20Permit(_name) { REMOTE_TOKEN = _remoteToken; BRIDGE = _bridge; DECIMALS = _decimals; } + /// @dev Returns the number of decimals used to get its user representation. + /// For example, if `decimals` equals `2`, a balance of `505` tokens should + /// be displayed to a user as `5.05` (`505 / 10 ** 2`). + /// NOTE: This information is only used for _display_ purposes: it in + /// no way affects any of the arithmetic of the contract, including + /// {IERC20-balanceOf} and {IERC20-transfer}. + function decimals() public view override returns (uint8) { + return DECIMALS; + } + + /// @notice Returns the allowance for a spender on the owner's tokens. + /// If the spender is the permit2 address, returns the maximum uint256 value. + /// @param _owner owner of the tokens. + /// @param _spender spender of the tokens. + /// @return Allowance for the spender. + function allowance(address _owner, address _spender) public view override returns (uint256) { + if (_spender == PERMIT2()) { + return type(uint256).max; + } + return super.allowance(_owner, _spender); + } + /// @notice Allows the StandardBridge on this network to mint tokens. /// @param _to Address to mint tokens to. /// @param _amount Amount of tokens to mint. @@ -127,14 +160,4 @@ contract OptimismMintableERC20 is IOptimismMintableERC20, ILegacyMintableERC20, function bridge() public view returns (address) { return BRIDGE; } - - /// @dev Returns the number of decimals used to get its user representation. - /// For example, if `decimals` equals `2`, a balance of `505` tokens should - /// be displayed to a user as `5.05` (`505 / 10 ** 2`). - /// NOTE: This information is only used for _display_ purposes: it in - /// no way affects any of the arithmetic of the contract, including - /// {IERC20-balanceOf} and {IERC20-transfer}. - function decimals() public view override returns (uint8) { - return DECIMALS; - } } diff --git a/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol b/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol index 790c1f828dfbd..6ae2716033127 100644 --- a/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol +++ b/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol @@ -49,7 +49,7 @@ contract OptimismMintableERC20Factory is ISemver, Initializable, IOptimismERC20F /// is responsible for deploying OptimismMintableERC20 contracts. /// @notice Semantic version. /// @custom:semver 1.10.1-beta.1 - string public constant version = "1.10.1-beta.1"; + string public constant version = "1.10.1-beta.2"; /// @notice Constructs the OptimismMintableERC20Factory contract. constructor() { diff --git a/packages/contracts-bedrock/test/universal/OptimismMintableERC20.t.sol b/packages/contracts-bedrock/test/universal/OptimismMintableERC20.t.sol index fcacc13a05f7c..1e84ce2958cf3 100644 --- a/packages/contracts-bedrock/test/universal/OptimismMintableERC20.t.sol +++ b/packages/contracts-bedrock/test/universal/OptimismMintableERC20.t.sol @@ -46,6 +46,20 @@ contract OptimismMintableERC20_Test is Bridge_Initializer { assertEq(L2Token.balanceOf(alice), 100); } + function test_allowance_permit2_max() external view { + assertEq(L2Token.allowance(alice, L2Token.PERMIT2()), type(uint256).max); + } + + function test_permit2_transferFrom() external { + vm.prank(address(l2StandardBridge)); + L2Token.mint(alice, 100); + + assertEq(L2Token.balanceOf(bob), 0); + vm.prank(L2Token.PERMIT2()); + L2Token.transferFrom(alice, bob, 100); + assertEq(L2Token.balanceOf(bob), 100); + } + function test_mint_notBridge_reverts() external { // NOT the bridge vm.expectRevert("OptimismMintableERC20: only bridge can mint and burn");