Commit 9e1f077f authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

feat: mintable with permit (#11868)

* contracts-bedrock: add permit functionality to mintable erc20

Adds `permit` functionality to `OptimismMintableERC20` tokens
that are deployed by the `OptimismMintableERC20Factory`.

* feat: max approve permit2 in ERC20Mintable

This commit max approves permit2 automatically for all holders of the
ERC20 bridge token. This enables all users on OP stack chains to skip an
approval step for actions on protocols utilizing permit2 e.g. Uniswap

* fix: constants first

* semver-lock: update

* snapshots: fix

* contracts: add natspec

* semver-lock: update

* contracts-bedrock: fix nits

* snapshots: update

---------
Co-authored-by: default avatarMark Toda <toda.mark@gmail.com>
parent e9ba6ac0
......@@ -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",
......
......@@ -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",
......
......@@ -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
......@@ -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;
}
}
......@@ -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() {
......
......@@ -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");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment