Skip to content

Commit

Permalink
feat: support permit2 on optimism superchain erc20 and upgrade solady…
Browse files Browse the repository at this point in the history
…'s erc20 implementation (#97)


---
Co-Authored-by: AgusDuha <81362284+agusduha@users.noreply.github.com>
  • Loading branch information
0xDiscotech authored Oct 15, 2024
1 parent cb2066b commit f0c6d1e
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 16 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@
[submodule "packages/contracts-bedrock/lib/openzeppelin-contracts-v5"]
path = packages/contracts-bedrock/lib/openzeppelin-contracts-v5
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "packages/contracts-bedrock/lib/solady-v0.0.245"]
path = packages/contracts-bedrock/lib/solady-v0.0.245
url = https://github.com/vectorized/solady
1 change: 1 addition & 0 deletions packages/contracts-bedrock/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ remappings = [
'@rari-capital/solmate/=lib/solmate',
'@lib-keccak/=lib/lib-keccak/contracts/lib',
'@solady/=lib/solady/src',
'@solady-v0.0.245/=lib/solady-v0.0.245/src',
'forge-std/=lib/forge-std/src',
'ds-test/=lib/forge-std/lib/ds-test/src',
'safe-contracts/=lib/safe-contracts/contracts',
Expand Down
1 change: 1 addition & 0 deletions packages/contracts-bedrock/lib/solady-v0.0.245
Submodule solady-v0.0.245 added at e0ef35
6 changes: 3 additions & 3 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@
"sourceCodeHash": "0xfea53344596d735eff3be945ed1300dc75a6f8b7b2c02c0043af5b0036f5f239"
},
"src/L2/OptimismSuperchainERC20.sol": {
"initCodeHash": "0xd5c84e45746fd741d541a917ddc1cc0c7043c6b21d5c18040d4bc999d6a7b2db",
"sourceCodeHash": "0xf32130f0b46333daba062c50ff6dcfadce1f177ff753bed2374d499ea9c2d98a"
"initCodeHash": "0x4e25579079d73c93f1d494e1976334b77fc4ec181c67f376d8e2613c7b207f52",
"sourceCodeHash": "0xe41cf3b005f1ea007fc1b5f69f630be5f6ef12d6e5e94a50e3160b0ebe0a1613"
},
"src/L2/OptimismSuperchainERC20Beacon.sol": {
"initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7",
Expand All @@ -125,7 +125,7 @@
},
"src/L2/SuperchainERC20.sol": {
"initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"sourceCodeHash": "0x75d061633a141af11a19b86e599a1725dfae8d245dcddfb6bb244a50d5e53f96"
"sourceCodeHash": "0x6a384ccfb6f2f7316c1b33873a1630b5179e52475951d31771656e06d2b11519"
},
"src/L2/SuperchainTokenBridge.sol": {
"initCodeHash": "0x07fc1d495928d9c13bd945a049d17e1d105d01c2082a7719e5d18cbc0e1c7d9e",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,11 @@
"name": "NotInitializing",
"type": "error"
},
{
"inputs": [],
"name": "Permit2AllowanceIsFixedAtInfinity",
"type": "error"
},
{
"inputs": [],
"name": "PermitExpired",
Expand Down
9 changes: 7 additions & 2 deletions packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165 {
}

/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.6
string public constant override version = "1.0.0-beta.6";
/// @custom:semver 1.0.0-beta.7
string public constant override version = "1.0.0-beta.7";

/// @notice Constructs the OptimismSuperchainERC20 contract.
constructor() {
Expand Down Expand Up @@ -141,4 +141,9 @@ contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165 {
function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) {
return _interfaceId == type(IOptimismSuperchainERC20).interfaceId || super.supportsInterface(_interfaceId);
}

/// @notice Sets Permit2 contract's allowance at infinity.
function _givePermit2InfiniteAllowance() internal view virtual override returns (bool) {
return true;
}
}
6 changes: 3 additions & 3 deletions packages/contracts-bedrock/src/L2/SuperchainERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.25;
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { ERC20 } from "@solady/tokens/ERC20.sol";
import { ERC20 } from "@solady-v0.0.245/tokens/ERC20.sol";
import { Unauthorized } from "src/libraries/errors/CommonErrors.sol";

/// @title SuperchainERC20
Expand All @@ -19,9 +19,9 @@ abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver {
}

/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.1
/// @custom:semver 1.0.0-beta.2
function version() external view virtual returns (string memory) {
return "1.0.0-beta.1";
return "1.0.0-beta.2";
}

/// @notice Allows the SuperchainTokenBridge to mint tokens.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ interface IERC20Solady {
/// @dev The permit has expired.
error PermitExpired();

/// @dev The allowance of Permit2 is fixed at infinity.
error Permit2AllowanceIsFixedAtInfinity();

/// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 amount);

Expand Down
72 changes: 67 additions & 5 deletions packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol";

// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol";
import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol";

import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol";
import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.sol";
import { IBeacon } from "@openzeppelin/contracts-v5/proxy/beacon/IBeacon.sol";
import { BeaconProxy } from "@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol";
import { Unauthorized } from "src/libraries/errors/CommonErrors.sol";
import { Preinstalls } from "src/libraries/Preinstalls.sol";

// Target contract
import { OptimismSuperchainERC20 } from "src/L2/OptimismSuperchainERC20.sol";
Expand Down Expand Up @@ -142,12 +144,12 @@ contract OptimismSuperchainERC20Test is Test {
vm.assume(_to != ZERO_ADDRESS);

// Get the total supply and balance of `_to` before the mint to compare later on the assertions
uint256 _totalSupplyBefore = IERC20(address(optimismSuperchainERC20)).totalSupply();
uint256 _toBalanceBefore = IERC20(address(optimismSuperchainERC20)).balanceOf(_to);
uint256 _totalSupplyBefore = IERC20Solady(address(optimismSuperchainERC20)).totalSupply();
uint256 _toBalanceBefore = IERC20Solady(address(optimismSuperchainERC20)).balanceOf(_to);

// Look for the emit of the `Transfer` event
vm.expectEmit(address(optimismSuperchainERC20));
emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount);
emit IERC20Solady.Transfer(ZERO_ADDRESS, _to, _amount);

// Look for the emit of the `Mint` event
vm.expectEmit(address(optimismSuperchainERC20));
Expand Down Expand Up @@ -200,7 +202,7 @@ contract OptimismSuperchainERC20Test is Test {

// Look for the emit of the `Transfer` event
vm.expectEmit(address(optimismSuperchainERC20));
emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount);
emit IERC20Solady.Transfer(_from, ZERO_ADDRESS, _amount);

// Look for the emit of the `Burn` event
vm.expectEmit(address(optimismSuperchainERC20));
Expand Down Expand Up @@ -252,4 +254,64 @@ contract OptimismSuperchainERC20Test is Test {
vm.assume(_interfaceId != type(IOptimismSuperchainERC20).interfaceId);
assertFalse(optimismSuperchainERC20.supportsInterface(_interfaceId));
}

/// @notice Tests that the allowance function returns the max uint256 value when the spender is Permit.
/// @param _randomCaller The address that will call the function - used to fuzz better since the behaviour should be
/// the same regardless of the caller.
/// @param _owner The funds owner.
function testFuzz_allowance_fromPermit2_succeeds(address _randomCaller, address _owner) public {
vm.prank(_randomCaller);
uint256 _allowance = optimismSuperchainERC20.allowance(_owner, Preinstalls.Permit2);

assertEq(_allowance, type(uint256).max);
}

/// @notice Tests that the allowance function returns the correct allowance when the spender is not Permit.
/// @param _randomCaller The address that will call the function - used to fuzz better
/// since the behaviour should be the same regardless of the caller.
/// @param _owner The funds owner.
/// @param _guy The address of the spender - It cannot be Permit2.
function testFuzz_allowance_succeeds(address _randomCaller, address _owner, address _guy, uint256 _amount) public {
// Assume
vm.assume(_guy != Preinstalls.Permit2);

// Arrange
vm.prank(_owner);
optimismSuperchainERC20.approve(_guy, _amount);

// Act
vm.prank(_randomCaller);
uint256 _allowance = optimismSuperchainERC20.allowance(_owner, _guy);

// Assert
assertEq(_allowance, _amount);
}

/// @notice Tests that `transferFrom` works when the caller (spender) is Permit2, without any explicit approval.
/// @param _owner The funds owner.
/// @param _recipient The address of the recipient.
/// @param _amount The amount of tokens to transfer.
function testFuzz_transferFrom_whenPermit2IsCaller_succeeds(
address _owner,
address _recipient,
uint256 _amount
)
public
{
// Arrange
deal(address(optimismSuperchainERC20), _owner, _amount);

vm.expectEmit(address(optimismSuperchainERC20));
emit IERC20Solady.Transfer(_owner, _recipient, _amount);

// Act
vm.prank(Preinstalls.Permit2);
optimismSuperchainERC20.transferFrom(_owner, _recipient, _amount);

// Assert
assertEq(optimismSuperchainERC20.balanceOf(_recipient), _amount);
// Handle the case where the source and destination are the same to check the source balance.
if (_owner != _recipient) assertEq(optimismSuperchainERC20.balanceOf(_owner), 0);
else assertEq(optimismSuperchainERC20.balanceOf(_owner), _amount);
}
}
6 changes: 3 additions & 3 deletions packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Test } from "forge-std/Test.sol";

// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { IERC20 } from "@openzeppelin/contracts-v5/token/ERC20/IERC20.sol";
import { IERC20Solady } from "src/vendor/interfaces/IERC20Solady.sol";

// Target contract
import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
Expand Down Expand Up @@ -58,7 +58,7 @@ contract SuperchainERC20Test is Test {

// Look for the emit of the `Transfer` event
vm.expectEmit(address(superchainERC20));
emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount);
emit IERC20Solady.Transfer(ZERO_ADDRESS, _to, _amount);

// Look for the emit of the `CrosschainMinted` event
vm.expectEmit(address(superchainERC20));
Expand Down Expand Up @@ -101,7 +101,7 @@ contract SuperchainERC20Test is Test {

// Look for the emit of the `Transfer` event
vm.expectEmit(address(superchainERC20));
emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount);
emit IERC20Solady.Transfer(_from, ZERO_ADDRESS, _amount);

// Look for the emit of the `CrosschainBurnt` event
vm.expectEmit(address(superchainERC20));
Expand Down

0 comments on commit f0c6d1e

Please sign in to comment.