Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface ISuperchainWETH is IWETH98, IERC7802, ISemver {
error Unauthorized();
error InvalidCrossDomainSender();
error ZeroAddress();
error Permit2AllowanceIsFixedAtInfinity();

event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination);

Expand Down
5 changes: 5 additions & 0 deletions packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,11 @@
"name": "InvalidCrossDomainSender",
"type": "error"
},
{
"inputs": [],
"name": "Permit2AllowanceIsFixedAtInfinity",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
Expand Down
6 changes: 3 additions & 3 deletions packages/contracts-bedrock/snapshots/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,15 @@
},
"src/L2/SuperchainERC20.sol": {
"initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"sourceCodeHash": "0x981dca5b09da9038a9dff071b40a880e1b52b20268c6780ef54be3bc98a4f629"
"sourceCodeHash": "0x8beaea08eef20031b8b77226144479eac9e2f14bbd620ab35f0863c5ce48a8c0"
},
"src/L2/SuperchainTokenBridge.sol": {
"initCodeHash": "0x6b568ed564aede82a3a4cbcdb51282cad0e588a3fe6d91cf76616d3113df3901",
"sourceCodeHash": "0xcd2b49cb7cf6d18616ee8bec9183fe5b5b460941875bc0b4158c4d5390ec3b0c"
},
"src/L2/SuperchainWETH.sol": {
"initCodeHash": "0x545686820e440d72529c815b7406844272d5ec33b741b2be6ebbe3a3db1ca8ad",
"sourceCodeHash": "0x6145e61cc0a0c95db882a76ecffea15c358c2b574d5157e53b85a69908701613"
"initCodeHash": "0xf75310a99b112a6d8f639c4981105b746dbfa80e13c5846bc28cdb5732bebf61",
"sourceCodeHash": "0xc8d46e4a22a1c13b44a71f33e1a56cdb1831887de1570f6bed4b8176d0fe1cad"
},
"src/L2/WETH.sol": {
"initCodeHash": "0x38b396fc35d72e8013bad2fe8d7dea5285499406d4c4b62e27c54252e1e0f00a",
Expand Down
7 changes: 5 additions & 2 deletions packages/contracts-bedrock/src/L2/SuperchainERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ import { ISemver } from "interfaces/universal/ISemver.sol";
import { IERC7802, IERC165 } from "interfaces/L2/IERC7802.sol";

/// @title SuperchainERC20
/// @notice A standard ERC20 extension implementing IERC7802 for unified cross-chain fungibility across
/// the Superchain. Allows the SuperchainTokenBridge to mint and burn tokens as needed.
/// @notice A standard ERC20 extension implementing IERC7802 for unified cross-chain fungibility
/// across the Superchain. Gives the SuperchainTokenBridge mint and burn permissions.
/// @dev This contract inherits from Solady@v0.0.245 ERC20. Carefully review Solady's,
/// documentation including all warnings, comments and natSpec, before extending or
/// interacting with this contract.
abstract contract SuperchainERC20 is ERC20, IERC7802, ISemver {
/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.8
Expand Down
9 changes: 9 additions & 0 deletions packages/contracts-bedrock/src/L2/SuperchainWETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver {
/// @notice Thrown when attempting to relay a message and the cross domain message sender is not SuperchainWETH.
error InvalidCrossDomainSender();

/// @notice Thrown when trying to approve Permit2 with a non-infinite allowance.
error Permit2AllowanceIsFixedAtInfinity();

/// @notice Emitted when ETH is sent from one chain to another.
/// @param from Address of the sender.
/// @param to Address of the recipient.
Expand Down Expand Up @@ -67,6 +70,12 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver {
emit Transfer(_from, address(0), _amount);
}

/// @inheritdoc WETH98
function approve(address guy, uint256 wad) public virtual override returns (bool) {
if (guy == Preinstalls.Permit2 && wad != type(uint256).max) revert Permit2AllowanceIsFixedAtInfinity();
return super.approve(guy, wad);
}

/// @notice Allows the SuperchainTokenBridge to mint tokens.
/// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint.
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts-bedrock/src/universal/WETH98.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ contract WETH98 {
/// @param guy The address that is approved to transfer the WETH.
/// @param wad The amount that is approved to transfer.
/// @return True if the approval was successful.
function approve(address guy, uint256 wad) external returns (bool) {
function approve(address guy, uint256 wad) public virtual returns (bool) {
_allowance[msg.sender][guy] = wad;
emit Approval(msg.sender, guy, wad);
return true;
Expand Down
2 changes: 2 additions & 0 deletions packages/contracts-bedrock/test/L2/CrossL2Inbox.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ contract CrossL2InboxTest is CommonTest {
crossL2Inbox = ICrossL2Inbox(Predeploys.CROSS_L2_INBOX);
}

/// Tests that validateMessage succeeds for a non-deposit transaction.
function testFuzz_validateMessage_succeeds(Identifier memory _id, bytes32 _messageHash) external {
// Ensure is not a deposit transaction
vm.mockCall({
Expand All @@ -45,6 +46,7 @@ contract CrossL2InboxTest is CommonTest {
crossL2Inbox.validateMessage(_id, _messageHash);
}

/// Tests that validateMessage reverts for a deposit transaction.
function testFuzz_validateMessage_isDeposit_reverts(Identifier calldata _id, bytes32 _messageHash) external {
// Ensure it is a deposit transaction
vm.mockCall({
Expand Down
37 changes: 32 additions & 5 deletions packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,22 @@ import { IL2ToL2CrossDomainMessenger } from "interfaces/L2/IL2ToL2CrossDomainMes
/// @title SuperchainWETH_Test
/// @notice Contract for testing the SuperchainWETH contract.
contract SuperchainWETH_Test is CommonTest {
/// @notice Emitted when a transfer is made.
event Transfer(address indexed src, address indexed dst, uint256 wad);

/// @notice Emitted when a deposit is made.
event Deposit(address indexed dst, uint256 wad);

/// @notice Emitted when a withdrawal is made.
event Withdrawal(address indexed src, uint256 wad);

/// @notice Emitted when a crosschain transfer mints tokens.
event CrosschainMint(address indexed to, uint256 amount, address indexed sender);

/// @notice Emitted when a crosschain transfer burns tokens.
event CrosschainBurn(address indexed from, uint256 amount, address indexed sender);

event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination);

event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source);

event Approval(address indexed src, address indexed guy, uint256 wad);

address internal constant ZERO_ADDRESS = address(0);

/// @notice Test setup.
Expand Down Expand Up @@ -296,6 +293,36 @@ contract SuperchainWETH_Test is CommonTest {
assertEq(_allowance, _wad);
}

/// @notice Tests that the `approve` function reverts when the spender is Permit2 and the allowance is not infinite.
function testFuzz_approve_permit2NonInfiniteAllowance_reverts(uint256 _wad) public {
vm.assume(_wad != type(uint256).max);
vm.expectRevert(ISuperchainWETH.Permit2AllowanceIsFixedAtInfinity.selector);
superchainWeth.approve(Preinstalls.Permit2, _wad);
}

/// @notice Tests that the `approve` function succeeds when the spender is Permit2 and the allowance is infinite.
function testFuzz_approve_permit2InfiniteAllowance_succeeds(address _src) public {
vm.expectEmit(address(superchainWeth));
emit Approval(_src, Preinstalls.Permit2, type(uint256).max);

vm.prank(_src);
superchainWeth.approve(Preinstalls.Permit2, type(uint256).max);
assertEq(superchainWeth.allowance(_src, Preinstalls.Permit2), type(uint256).max);
}

/// @notice Tests that the `approve` function succeeds correctly updating the allowance.
function testFuzz_approve_succeeds(address _src, address _guy, uint256 _wad) public {
if (_guy == Preinstalls.Permit2) _wad = type(uint256).max;

vm.expectEmit(address(superchainWeth));
emit Approval(_src, _guy, _wad);

vm.prank(_src);
superchainWeth.approve(_guy, _wad);

assertEq(superchainWeth.allowance(_src, _guy), _wad);
}

/// @notice Tests that `transferFrom` works when the caller (spender) is Permit2, without any explicit approval.
/// @param _src The funds owner.
/// @param _dst The address of the recipient.
Expand Down