From 1bd196e4fd81f840c370c2408d8708fc172a6d90 Mon Sep 17 00:00:00 2001 From: omnifient <90161786+omnifient@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:34:09 +0000 Subject: [PATCH 1/3] implement NativeConvert.deconvert (aka convertToWrappedUSDC) --- src/NativeConverter.sol | 22 +++++++++++++++++++++- test/integration/ConvertFlows.t.sol | 29 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/NativeConverter.sol b/src/NativeConverter.sol index 0b606a3..fbb1fa4 100644 --- a/src/NativeConverter.sol +++ b/src/NativeConverter.sol @@ -106,11 +106,31 @@ contract NativeConverter is CommonAdminOwner { emit Convert(msg.sender, receiver, amount); } + function deconvert( + address receiver, + uint256 amount, + bytes calldata permitData + ) external whenNotPaused { + require(receiver != address(0), "INVALID_RECEIVER"); + require(amount > 0, "INVALID_AMOUNT"); + require(amount < zkBWUSDC.balanceOf(address(this)), "AMOUNT_TOO_LARGE"); + + if (permitData.length > 0) + LibPermit.permit(address(zkUSDCe), amount, permitData); + + // transfer native usdc from user to the converter, and burn it + zkUSDCe.safeTransferFrom(msg.sender, address(this), amount); + zkUSDCe.burn(amount); + + // and then send bridge wrapped usdc to the user + zkBWUSDC.safeTransfer(receiver, amount); + } + /// @notice Migrates L2 BridgeWrappedUSDC USDC to L1 USDC /// @dev Any BridgeWrappedUSDC transfered in by previous calls to /// `convert` will be burned and the corresponding /// L1 USDC will be sent to the L1Escrow via a message to the bridge - function migrate() external whenNotPaused { + function migrate() external onlyOwner whenNotPaused { // Anyone can call migrate() on NativeConverter to // have all zkBridgeWrappedUSDC withdrawn via the PolygonZkEVMBridge // moving the L1_USDC held in the PolygonZkEVMBridge to L1Escrow diff --git a/test/integration/ConvertFlows.t.sol b/test/integration/ConvertFlows.t.sol index c2553fe..17821d1 100644 --- a/test/integration/ConvertFlows.t.sol +++ b/test/integration/ConvertFlows.t.sol @@ -186,4 +186,33 @@ contract ConvertFlows is Base { _assertUsdcSupplyAndBalancesMatch(); } + + function testConverNativeUsdcToWrappedUsdc() public { + vm.selectFork(_l2Fork); + vm.startPrank(_alice); + + // setup: alice converts wrapped to native ("seeding" the nativeconverter) and sends to bob + uint256 amount = _toUSDC(10000); + _erc20L2Wusdc.approve(address(_nativeConverter), amount); + _nativeConverter.convert(_bob, amount, _emptyBytes); + vm.stopPrank(); + + // deconvert + vm.startPrank(_bob); + + // frank has no wrapped + uint256 wrappedBalance1 = _erc20L2Wusdc.balanceOf(_frank); + assertEq(wrappedBalance1, 0); + + // bob converts 8k native to wrapped, with frank as the receiver + uint256 amount2 = _toUSDC(8000); + _erc20L2Usdc.approve(address(_nativeConverter), amount2); + _nativeConverter.deconvert(_frank, amount2, _emptyBytes); + + // frank has 8k wrapped + uint256 wrappedBalance2 = _erc20L2Wusdc.balanceOf(_frank); + assertEq(wrappedBalance2, amount2); + + _assertUsdcSupplyAndBalancesMatch(); + } } From de0ec4e5e3a7be213c375e2f20ed5eb00056715d Mon Sep 17 00:00:00 2001 From: omnifient <90161786+omnifient@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:18:32 +0000 Subject: [PATCH 2/3] add deconvert event (NOTE: migrate tests will fail because it's now onlyOwner) --- src/NativeConverter.sol | 3 +++ test/Base.sol | 3 +++ test/integration/ConvertFlows.t.sol | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/src/NativeConverter.sol b/src/NativeConverter.sol index fbb1fa4..fdd37b6 100644 --- a/src/NativeConverter.sol +++ b/src/NativeConverter.sol @@ -21,6 +21,7 @@ contract NativeConverter is CommonAdminOwner { using SafeERC20Upgradeable for IUSDC; event Convert(address indexed from, address indexed to, uint256 amount); + event Deconvert(address indexed from, address indexed to, uint256 amount); event Migrate(uint256 amount); /// @notice the PolygonZkEVMBridge deployed on the zkEVM @@ -124,6 +125,8 @@ contract NativeConverter is CommonAdminOwner { // and then send bridge wrapped usdc to the user zkBWUSDC.safeTransfer(receiver, amount); + + emit Deconvert(msg.sender, receiver, amount); } /// @notice Migrates L2 BridgeWrappedUSDC USDC to L1 USDC diff --git a/test/Base.sol b/test/Base.sol index eb433d5..04c3b35 100644 --- a/test/Base.sol +++ b/test/Base.sol @@ -32,6 +32,9 @@ library Events { // copy of NativeConverter.Convert event Convert(address indexed from, address indexed to, uint256 amount); + // copy of NativeConverter.Deconvert + event Deconvert(address indexed from, address indexed to, uint256 amount); + // copy of L1Escrow.Deposit event Deposit(address indexed from, address indexed to, uint256 amount); diff --git a/test/integration/ConvertFlows.t.sol b/test/integration/ConvertFlows.t.sol index 17821d1..5a1db21 100644 --- a/test/integration/ConvertFlows.t.sol +++ b/test/integration/ConvertFlows.t.sol @@ -207,6 +207,11 @@ contract ConvertFlows is Base { // bob converts 8k native to wrapped, with frank as the receiver uint256 amount2 = _toUSDC(8000); _erc20L2Usdc.approve(address(_nativeConverter), amount2); + + // check that our convert event is emitted + vm.expectEmit(address(_nativeConverter)); + emit Events.Deconvert(_bob, _frank, amount2); + _nativeConverter.deconvert(_frank, amount2, _emptyBytes); // frank has 8k wrapped From 963824a30c4627ff6e086d8b5233f2ab44a9b413 Mon Sep 17 00:00:00 2001 From: omnifient <90161786+omnifient@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:35:24 +0000 Subject: [PATCH 3/3] fix deconvert <= balance --- src/NativeConverter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NativeConverter.sol b/src/NativeConverter.sol index fdd37b6..2db1a84 100644 --- a/src/NativeConverter.sol +++ b/src/NativeConverter.sol @@ -114,7 +114,7 @@ contract NativeConverter is CommonAdminOwner { ) external whenNotPaused { require(receiver != address(0), "INVALID_RECEIVER"); require(amount > 0, "INVALID_AMOUNT"); - require(amount < zkBWUSDC.balanceOf(address(this)), "AMOUNT_TOO_LARGE"); + require(amount <= zkBWUSDC.balanceOf(address(this)), "AMOUNT_TOO_LARGE"); if (permitData.length > 0) LibPermit.permit(address(zkUSDCe), amount, permitData);