Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mintable with permit #11868

Merged
merged 9 commits into from
Sep 13, 2024
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
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": "0x3a0a294932d6deba043f6a2b46b4e8477ee96e7fb054d7e7229a43ce4352c68d"
},
"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"
}
]
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()) {
tynes marked this conversation as resolved.
Show resolved Hide resolved
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