From 196613d916bbb866f87391b43bc76cb2259abd4a Mon Sep 17 00:00:00 2001 From: Disco <131301107+0xDiscotech@users.noreply.github.com> Date: Thu, 24 Oct 2024 05:01:40 -0300 Subject: [PATCH] feat: support permit2 on superchainweth (#12596) * feat: support permit2 on superchainweth * chore: run pre-pr --------- Co-authored-by: agusduha Co-authored-by: gotzenx <78360669+gotzenx@users.noreply.github.com> --- packages/contracts-bedrock/semver-lock.json | 12 +-- .../snapshots/abi/DelayedWETH.json | 6 +- .../snapshots/abi/SuperchainWETH.json | 6 +- .../contracts-bedrock/snapshots/abi/WETH.json | 6 +- .../snapshots/abi/WETH98.json | 6 +- .../snapshots/storageLayout/DelayedWETH.json | 4 +- .../storageLayout/SuperchainWETH.json | 4 +- .../snapshots/storageLayout/WETH.json | 4 +- .../snapshots/storageLayout/WETH98.json | 4 +- .../src/L2/SuperchainWETH.sol | 15 +++- packages/contracts-bedrock/src/L2/WETH.sol | 4 +- .../src/dispute/DelayedWETH.sol | 6 +- .../src/universal/WETH98.sol | 35 +++++--- .../test/L2/SuperchainWETH.t.sol | 80 +++++++++++++++++++ 14 files changed, 145 insertions(+), 47 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 5b62da033bf0..26828dd40f8e 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -132,12 +132,12 @@ "sourceCodeHash": "0x4f539e9d9096d31e861982b8f751fa2d7de0849590523375cf92e175294d1036" }, "src/L2/SuperchainWETH.sol": { - "initCodeHash": "0xc120d1fd9edd8aa392cf112859a3a3907c3b5d55132d6a5edd80ff1d6b6baeaa", - "sourceCodeHash": "0x445f088aa91fafde43f558f34da4b9f85b82e6a76f8b2dec9563838eb4335928" + "initCodeHash": "0xc72cb486b815a65daa8bd5d0af8c965b6708cf8caf03de0a18023a63a6e6c604", + "sourceCodeHash": "0x39fff1d4702a2fec3dcba29c7f9604eabf20d32e9c5bf4377edeb620624aa467" }, "src/L2/WETH.sol": { - "initCodeHash": "0xfb253765520690623f177941c2cd9eba23e4c6d15063bccdd5e98081329d8956", - "sourceCodeHash": "0x2ab6be69795109a1ee04c5693a34d6ce0ff90b62e404cdeb18178bab18d06784" + "initCodeHash": "0x17ea1b1c5d5a622d51c2961fde886a5498de63584e654ed1d69ee80dddbe0b17", + "sourceCodeHash": "0x0fa0633a769e73f5937514c0003ba7947a1c275bbe5b85d78879c42f0ed8895b" }, "src/cannon/MIPS.sol": { "initCodeHash": "0x696c3ce334c11d0f9633945babac22b1b65848ff00d2cf6c4cb18116bbf138b2", @@ -156,8 +156,8 @@ "sourceCodeHash": "0x1d918a536d9f6c900efdf069e96c2a27bb49340d6d1ebaa92dd6b481835a9a82" }, "src/dispute/DelayedWETH.sol": { - "initCodeHash": "0x835b322de7d5c84b415e99f2cb1000411df18995b5476f2116ac6f897f2d0910", - "sourceCodeHash": "0xdbd64724b73f8f9d6f1cc72bb662a99b9955ab72950a8f6ffeb1d2454997f60b" + "initCodeHash": "0xb31e0ff80fd69bc3f3b7d53f3fa42da4cdae393e41b8816719ce5ebe3d248688", + "sourceCodeHash": "0x1dfc68560c0805faa78360e3d4ef2d768e2f3d6c0c7183d2077a2c4277c778db" }, "src/dispute/DisputeGameFactory.sol": { "initCodeHash": "0xd72eced7cb5400d93188038a707fe6c1b04077f059cd8e2f5253e871de2cee3b", diff --git a/packages/contracts-bedrock/snapshots/abi/DelayedWETH.json b/packages/contracts-bedrock/snapshots/abi/DelayedWETH.json index a84394e6b003..a6f0dd660468 100644 --- a/packages/contracts-bedrock/snapshots/abi/DelayedWETH.json +++ b/packages/contracts-bedrock/snapshots/abi/DelayedWETH.json @@ -22,12 +22,12 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "owner", "type": "address" }, { "internalType": "address", - "name": "", + "name": "spender", "type": "address" } ], @@ -70,7 +70,7 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "src", "type": "address" } ], diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json index d76246faf218..fe53a4cbfb5e 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json @@ -11,12 +11,12 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "owner", "type": "address" }, { "internalType": "address", - "name": "", + "name": "spender", "type": "address" } ], @@ -59,7 +59,7 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "src", "type": "address" } ], diff --git a/packages/contracts-bedrock/snapshots/abi/WETH.json b/packages/contracts-bedrock/snapshots/abi/WETH.json index 9430e99a4c85..03cf2880ccce 100644 --- a/packages/contracts-bedrock/snapshots/abi/WETH.json +++ b/packages/contracts-bedrock/snapshots/abi/WETH.json @@ -11,12 +11,12 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "owner", "type": "address" }, { "internalType": "address", - "name": "", + "name": "spender", "type": "address" } ], @@ -59,7 +59,7 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "src", "type": "address" } ], diff --git a/packages/contracts-bedrock/snapshots/abi/WETH98.json b/packages/contracts-bedrock/snapshots/abi/WETH98.json index 364d6b0591dc..019b5c37a1af 100644 --- a/packages/contracts-bedrock/snapshots/abi/WETH98.json +++ b/packages/contracts-bedrock/snapshots/abi/WETH98.json @@ -11,12 +11,12 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "owner", "type": "address" }, { "internalType": "address", - "name": "", + "name": "spender", "type": "address" } ], @@ -59,7 +59,7 @@ "inputs": [ { "internalType": "address", - "name": "", + "name": "src", "type": "address" } ], diff --git a/packages/contracts-bedrock/snapshots/storageLayout/DelayedWETH.json b/packages/contracts-bedrock/snapshots/storageLayout/DelayedWETH.json index 4e8b771cd9bb..6fdbd4718b69 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/DelayedWETH.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/DelayedWETH.json @@ -36,14 +36,14 @@ }, { "bytes": "32", - "label": "balanceOf", + "label": "_balanceOf", "offset": 0, "slot": "101", "type": "mapping(address => uint256)" }, { "bytes": "32", - "label": "allowance", + "label": "_allowance", "offset": 0, "slot": "102", "type": "mapping(address => mapping(address => uint256))" diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SuperchainWETH.json b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainWETH.json index ac5f38a75a04..93eac8f28429 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SuperchainWETH.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainWETH.json @@ -1,14 +1,14 @@ [ { "bytes": "32", - "label": "balanceOf", + "label": "_balanceOf", "offset": 0, "slot": "0", "type": "mapping(address => uint256)" }, { "bytes": "32", - "label": "allowance", + "label": "_allowance", "offset": 0, "slot": "1", "type": "mapping(address => mapping(address => uint256))" diff --git a/packages/contracts-bedrock/snapshots/storageLayout/WETH.json b/packages/contracts-bedrock/snapshots/storageLayout/WETH.json index ac5f38a75a04..93eac8f28429 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/WETH.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/WETH.json @@ -1,14 +1,14 @@ [ { "bytes": "32", - "label": "balanceOf", + "label": "_balanceOf", "offset": 0, "slot": "0", "type": "mapping(address => uint256)" }, { "bytes": "32", - "label": "allowance", + "label": "_allowance", "offset": 0, "slot": "1", "type": "mapping(address => mapping(address => uint256))" diff --git a/packages/contracts-bedrock/snapshots/storageLayout/WETH98.json b/packages/contracts-bedrock/snapshots/storageLayout/WETH98.json index ac5f38a75a04..93eac8f28429 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/WETH98.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/WETH98.json @@ -1,14 +1,14 @@ [ { "bytes": "32", - "label": "balanceOf", + "label": "_balanceOf", "offset": 0, "slot": "0", "type": "mapping(address => uint256)" }, { "bytes": "32", - "label": "allowance", + "label": "_allowance", "offset": 0, "slot": "1", "type": "mapping(address => mapping(address => uint256))" diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index a9242d9d0ab3..e03b689ba150 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -6,6 +6,7 @@ import { WETH98 } from "src/universal/WETH98.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; +import { Preinstalls } from "src/libraries/Preinstalls.sol"; // Interfaces import { ISemver } from "src/universal/interfaces/ISemver.sol"; @@ -22,8 +23,8 @@ import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErro /// do not use a custom gas token. contract SuperchainWETH is WETH98, ICrosschainERC20, ISemver { /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.8 - string public constant version = "1.0.0-beta.8"; + /// @custom:semver 1.0.0-beta.9 + string public constant version = "1.0.0-beta.9"; /// @inheritdoc WETH98 function deposit() public payable override { @@ -37,11 +38,17 @@ contract SuperchainWETH is WETH98, ICrosschainERC20, ISemver { super.withdraw(_amount); } + /// @inheritdoc WETH98 + function allowance(address owner, address spender) public view override returns (uint256) { + if (spender == Preinstalls.Permit2) return type(uint256).max; + return super.allowance(owner, spender); + } + /// @notice Mints WETH to an address. /// @param _to The address to mint WETH to. /// @param _amount The amount of WETH to mint. function _mint(address _to, uint256 _amount) internal { - balanceOf[_to] += _amount; + _balanceOf[_to] += _amount; emit Transfer(address(0), _to, _amount); } @@ -49,7 +56,7 @@ contract SuperchainWETH is WETH98, ICrosschainERC20, ISemver { /// @param _from The address to burn WETH from. /// @param _amount The amount of WETH to burn. function _burn(address _from, uint256 _amount) internal { - balanceOf[_from] -= _amount; + _balanceOf[_from] -= _amount; emit Transfer(_from, address(0), _amount); } diff --git a/packages/contracts-bedrock/src/L2/WETH.sol b/packages/contracts-bedrock/src/L2/WETH.sol index fb24f0747338..dacd62c36de9 100644 --- a/packages/contracts-bedrock/src/L2/WETH.sol +++ b/packages/contracts-bedrock/src/L2/WETH.sol @@ -14,8 +14,8 @@ import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; /// @title WETH contract that reads the name and symbol from the L1Block contract. /// Allows for nice rendering of token names for chains using custom gas token. contract WETH is WETH98, ISemver { - /// @custom:semver 1.1.0-beta.2 - string public constant version = "1.1.0-beta.2"; + /// @custom:semver 1.1.0-beta.3 + string public constant version = "1.1.0-beta.3"; /// @notice Returns the name of the wrapped native asset. Will be "Wrapped Ether" /// if the native asset is Ether. diff --git a/packages/contracts-bedrock/src/dispute/DelayedWETH.sol b/packages/contracts-bedrock/src/dispute/DelayedWETH.sol index 42b6022d12fe..3438c22e3679 100644 --- a/packages/contracts-bedrock/src/dispute/DelayedWETH.sol +++ b/packages/contracts-bedrock/src/dispute/DelayedWETH.sol @@ -32,8 +32,8 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver { event Unwrap(address indexed src, uint256 wad); /// @notice Semantic version. - /// @custom:semver 1.2.0-beta.2 - string public constant version = "1.2.0-beta.2"; + /// @custom:semver 1.2.0-beta.3 + string public constant version = "1.2.0-beta.3"; /// @notice Returns a withdrawal request for the given address. mapping(address => mapping(address => WithdrawalRequest)) public withdrawals; @@ -112,7 +112,7 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver { /// @param _wad The amount of WETH to recover. function hold(address _guy, uint256 _wad) external { require(msg.sender == owner(), "DelayedWETH: not owner"); - allowance[_guy][msg.sender] = _wad; + _allowance[_guy][msg.sender] = _wad; emit Approval(_guy, msg.sender, _wad); } } diff --git a/packages/contracts-bedrock/src/universal/WETH98.sol b/packages/contracts-bedrock/src/universal/WETH98.sol index 5665e5f9210f..e211db22b473 100644 --- a/packages/contracts-bedrock/src/universal/WETH98.sol +++ b/packages/contracts-bedrock/src/universal/WETH98.sol @@ -26,8 +26,8 @@ import { IWETH } from "src/universal/interfaces/IWETH.sol"; contract WETH98 is IWETH { uint8 public constant decimals = 18; - mapping(address => uint256) public balanceOf; - mapping(address => mapping(address => uint256)) public allowance; + mapping(address => uint256) internal _balanceOf; + mapping(address => mapping(address => uint256)) internal _allowance; /// @notice Pipes to deposit. receive() external payable { @@ -49,16 +49,26 @@ contract WETH98 is IWETH { return "WETH"; } + /// @inheritdoc IWETH + function allowance(address owner, address spender) public view virtual returns (uint256) { + return _allowance[owner][spender]; + } + + /// @inheritdoc IWETH + function balanceOf(address src) public view returns (uint256) { + return _balanceOf[src]; + } + /// @inheritdoc IWETH function deposit() public payable virtual { - balanceOf[msg.sender] += msg.value; + _balanceOf[msg.sender] += msg.value; emit Deposit(msg.sender, msg.value); } /// @inheritdoc IWETH function withdraw(uint256 wad) public virtual { - require(balanceOf[msg.sender] >= wad); - balanceOf[msg.sender] -= wad; + require(_balanceOf[msg.sender] >= wad); + _balanceOf[msg.sender] -= wad; payable(msg.sender).transfer(wad); emit Withdrawal(msg.sender, wad); } @@ -70,7 +80,7 @@ contract WETH98 is IWETH { /// @inheritdoc IWETH function approve(address guy, uint256 wad) external returns (bool) { - allowance[msg.sender][guy] = wad; + _allowance[msg.sender][guy] = wad; emit Approval(msg.sender, guy, wad); return true; } @@ -82,15 +92,16 @@ contract WETH98 is IWETH { /// @inheritdoc IWETH function transferFrom(address src, address dst, uint256 wad) public returns (bool) { - require(balanceOf[src] >= wad); + require(_balanceOf[src] >= wad); - if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { - require(allowance[src][msg.sender] >= wad); - allowance[src][msg.sender] -= wad; + uint256 senderAllowance = allowance(src, msg.sender); + if (src != msg.sender && senderAllowance != type(uint256).max) { + require(senderAllowance >= wad); + _allowance[src][msg.sender] -= wad; } - balanceOf[src] -= wad; - balanceOf[dst] += wad; + _balanceOf[src] -= wad; + _balanceOf[dst] += wad; emit Transfer(src, dst, wad); diff --git a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol index 4710274baac5..b428dc4cd191 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol @@ -7,6 +7,7 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; +import { Preinstalls } from "src/libraries/Preinstalls.sol"; // Interfaces import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; @@ -373,4 +374,83 @@ contract SuperchainWETH_Test is CommonTest { // Assert assertFalse(success); } + + /// @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 _src The funds owner. + function testFuzz_allowance_fromPermit2_succeeds(address _randomCaller, address _src) public { + vm.prank(_randomCaller); + uint256 _allowance = superchainWeth.allowance(_src, 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 _src The funds owner. + /// @param _guy The address of the spender - It cannot be Permit2. + function testFuzz_allowance_succeeds(address _randomCaller, address _src, address _guy, uint256 _wad) public { + // Assume + vm.assume(_guy != Preinstalls.Permit2); + + // Arrange + vm.prank(_src); + superchainWeth.approve(_guy, _wad); + + // Act + vm.prank(_randomCaller); + uint256 _allowance = superchainWeth.allowance(_src, _guy); + + // Assert + assertEq(_allowance, _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. + /// @param _wad The amount of WETH to transfer. + function testFuzz_transferFrom_whenPermit2IsCaller_succeeds(address _src, address _dst, uint256 _wad) public { + vm.assume(_src != _dst); + + // Arrange + deal(address(superchainWeth), _src, _wad); + + vm.expectEmit(address(superchainWeth)); + emit Transfer(_src, _dst, _wad); + + // Act + vm.prank(Preinstalls.Permit2); + superchainWeth.transferFrom(_src, _dst, _wad); + + // Assert + assertEq(superchainWeth.balanceOf(_src), 0); + assertEq(superchainWeth.balanceOf(_dst), _wad); + } + + /// @notice Tests that `transferFrom` works when the caller (spender) is Permit2, and `_src` equals `_dst` without + /// an explicit approval. + /// The balance should remain the same on this scenario. + /// @param _user The source and destination address. + /// @param _wad The amount of WETH to transfer. + function testFuzz_transferFrom_whenPermit2IsCallerAndSourceIsDestination_succeeds( + address _user, + uint256 _wad + ) + public + { + // Arrange + deal(address(superchainWeth), _user, _wad); + + vm.expectEmit(address(superchainWeth)); + emit Transfer(_user, _user, _wad); + + // Act + vm.prank(Preinstalls.Permit2); + superchainWeth.transferFrom(_user, _user, _wad); + + // Assert + assertEq(superchainWeth.balanceOf(_user), _wad); + } }