Skip to content

Commit

Permalink
feat: mintable with permit (#11868)
Browse files Browse the repository at this point in the history
* 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: Mark Toda <[email protected]>
  • Loading branch information
tynes and marktoda authored Sep 13, 2024
1 parent e9ba6ac commit 9e1f077
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 20 deletions.
8 changes: 4 additions & 4 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -60,12 +86,12 @@
"inputs": [
{
"internalType": "address",
"name": "owner",
"name": "_owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"name": "_spender",
"type": "address"
}
],
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
49 changes: 36 additions & 13 deletions packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
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
/// to allow the StandardBridge contracts to mint and burn tokens. This makes it possible to
/// 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;

Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down

0 comments on commit 9e1f077

Please sign in to comment.