Skip to content
Open
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
25 changes: 24 additions & 1 deletion src/NativeConverter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -106,11 +107,33 @@ 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);

emit Deconvert(msg.sender, 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
Expand Down
3 changes: 3 additions & 0 deletions test/Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
34 changes: 34 additions & 0 deletions test/integration/ConvertFlows.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,38 @@ 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);

// 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
uint256 wrappedBalance2 = _erc20L2Wusdc.balanceOf(_frank);
assertEq(wrappedBalance2, amount2);

_assertUsdcSupplyAndBalancesMatch();
}
}