Skip to content

Commit

Permalink
Merge pull request #7 from ronin-chain/fix/handle-deposit-weth
Browse files Browse the repository at this point in the history
fix(MainchainGateway): handle deposit weth
  • Loading branch information
nxqbao authored Mar 25, 2024
2 parents 74b5cea + 515692c commit 6b20944
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 45 deletions.
2 changes: 1 addition & 1 deletion script/BridgeMigration.sol
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,4 @@ contract BridgeMigration is BaseMigration {
}
}
}
}
}
2 changes: 2 additions & 0 deletions script/GeneralConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ contract GeneralConfig is BaseGeneralConfig, Utils {

_contractNameMap[Contract.RoninPauseEnforcer.key()] = "PauseEnforcer";
_contractNameMap[Contract.MainchainPauseEnforcer.key()] = "PauseEnforcer";

_contractNameMap[Contract.MainchainWethUnwrapper.key()] = "WethUnwrapper";
}

function _mapContractName(Contract contractEnum) internal {
Expand Down
23 changes: 23 additions & 0 deletions script/contracts/MainchainWethUnwrapperDeploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import { WethUnwrapper } from "@ronin/contracts/extensions/WethUnwrapper.sol";
import { Contract } from "../utils/Contract.sol";
import { ISharedArgument } from "../interfaces/ISharedArgument.sol";
import { Migration } from "../Migration.s.sol";

contract MainchainWethUnwrapperDeploy is Migration {
function _defaultArguments() internal virtual override returns (bytes memory args) {
ISharedArgument.WethUnwrapperParam memory param = config.sharedArguments().mainchainWethUnwrapper;

args = abi.encode(param.weth);
}

function run() public virtual returns (WethUnwrapper) {
return WethUnwrapper(_deployImmutable(Contract.MainchainWethUnwrapper.key()));
}

function runWithArgs(bytes memory args) public virtual returns (WethUnwrapper) {
return WethUnwrapper(_deployImmutable(Contract.MainchainWethUnwrapper.key(), args));
}
}
6 changes: 6 additions & 0 deletions script/interfaces/ISharedArgument.sol
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,17 @@ interface ISharedArgument is IGeneralConfig {
uint256[] governorPKs;
}

struct WethUnwrapperParam {
address weth;
address owner;
}

struct SharedParameter {
// mainchain
BridgeManagerParam mainchainBridgeManager;
MainchainGatewayV3Param mainchainGatewayV3;
PauseEnforcerParam mainchainPauseEnforcer;
WethUnwrapperParam mainchainWethUnwrapper;
// ronin
BridgeManagerParam roninBridgeManager;
RoninGatewayV3Param roninGatewayV3;
Expand Down
4 changes: 3 additions & 1 deletion script/utils/Contract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ enum Contract {
RoninBridgeManager,
MainchainPauseEnforcer,
MainchainGatewayV3,
MainchainBridgeManager
MainchainBridgeManager,
MainchainWethUnwrapper
}

using { key, name } for Contract global;
Expand All @@ -45,6 +46,7 @@ function name(Contract contractEnum) pure returns (string memory) {
if (contractEnum == Contract.MainchainPauseEnforcer) return "PauseEnforcer";
if (contractEnum == Contract.MainchainGatewayV3) return "MainchainGatewayV3";
if (contractEnum == Contract.MainchainBridgeManager) return "MainchainBridgeManager";
if (contractEnum == Contract.MainchainWethUnwrapper) return "WethUnwrapper";

revert("Contract: Unknown contract");
}
54 changes: 54 additions & 0 deletions src/extensions/WethUnwrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "../interfaces/IWETH.sol";

contract WethUnwrapper is ReentrancyGuard {
IWETH public immutable weth;

error ErrCannotTransferFrom();
error ErrNotWrappedContract();
error ErrExternalCallFailed(address sender, bytes4 sig);

constructor(address weth_) {
if (address(weth_).code.length == 0) revert ErrNotWrappedContract();
weth = IWETH(weth_);
}

fallback() external payable {
_fallback();
}

receive() external payable {
_fallback();
}

function unwrap(uint256 amount) external nonReentrant {
_deductWrappedAndWithdraw(amount);
_sendNativeTo(payable(msg.sender), amount);
}

function unwrapTo(uint256 amount, address payable to) external nonReentrant {
_deductWrappedAndWithdraw(amount);
_sendNativeTo(payable(to), amount);
}

function _deductWrappedAndWithdraw(uint256 amount) internal {
(bool success,) = address(weth).call(abi.encodeCall(IWETH.transferFrom, (msg.sender, address(this), amount)));
if (!success) revert ErrCannotTransferFrom();

weth.withdraw(amount);
}

function _sendNativeTo(address payable to, uint256 val) internal {
(bool success,) = to.call{ value: val }("");
if (!success) {
revert ErrExternalCallFailed(to, msg.sig);
}
}

function _fallback() internal view {
if (msg.sender != address(weth)) revert ErrNotWrappedContract();
}
}
8 changes: 8 additions & 0 deletions src/interfaces/IWETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@
pragma solidity ^0.8.0;

interface IWETH {
event Transfer(address indexed src, address indexed dst, uint wad);

function deposit() external payable;

function transfer(address dst, uint wad) external returns (bool);

function approve(address guy, uint wad) external returns (bool);

function transferFrom(address src, address dst, uint wad) external returns (bool);

function withdraw(uint256 _wad) external;

function balanceOf(address) external view returns (uint256);
Expand Down
34 changes: 26 additions & 8 deletions src/mainchain/MainchainGatewayV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { IBridgeManager } from "../interfaces/bridge/IBridgeManager.sol";
import { IBridgeManagerCallback } from "../interfaces/bridge/IBridgeManagerCallback.sol";
import { HasContracts, ContractType } from "../extensions/collections/HasContracts.sol";
import "../extensions/WethUnwrapper.sol";
import "../extensions/WithdrawalLimitation.sol";
import "../libraries/Transfer.sol";
import "../interfaces/IMainchainGatewayV3.sol";
Expand Down Expand Up @@ -40,6 +41,7 @@ contract MainchainGatewayV3 is WithdrawalLimitation, Initializable, AccessContro

uint96 private _totalOperatorWeight;
mapping(address operator => uint96 weight) private _operatorWeight;
WethUnwrapper public wethUnwrapper;

fallback() external payable {
_fallback();
Expand Down Expand Up @@ -111,6 +113,10 @@ contract MainchainGatewayV3 is WithdrawalLimitation, Initializable, AccessContro
_totalOperatorWeight = totalWeight;
}

function initializeV4(address payable wethUnwrapper_) external reinitializer(4) {
wethUnwrapper = WethUnwrapper(wethUnwrapper_);
}

/**
* @dev Receives ether without doing anything. Use this function to topup native token.
*/
Expand Down Expand Up @@ -340,9 +346,12 @@ contract MainchainGatewayV3 is WithdrawalLimitation, Initializable, AccessContro
if (_token.erc != _request.info.erc) revert ErrInvalidTokenStandard();

_request.info.transferFrom(_requester, address(this), _request.tokenAddr);

// Withdraw if token is WETH
// The withdraw of WETH must go via `WethUnwrapper`, because `WETH.withdraw` only sends 2300 gas, which is insufficient when recipient is a proxy.
if (_roninWeth == _request.tokenAddr) {
IWETH(_roninWeth).withdraw(_request.info.quantity);
wrappedNativeToken.approve(address(wethUnwrapper), _request.info.quantity);
wethUnwrapper.unwrap(_request.info.quantity);
}
}

Expand Down Expand Up @@ -407,15 +416,24 @@ contract MainchainGatewayV3 is WithdrawalLimitation, Initializable, AccessContro
}

/**
* @dev Receives ETH from WETH or creates deposit request.
* @dev Receives ETH from WETH or creates deposit request if sender is not WETH or WETHUnwrapper.
*/
function _fallback() internal virtual whenNotPaused {
if (msg.sender != address(wrappedNativeToken)) {
Transfer.Request memory _request;
_request.recipientAddr = msg.sender;
_request.info.quantity = msg.value;
_requestDepositFor(_request, _request.recipientAddr);
function _fallback() internal virtual {
if (msg.sender == address(wrappedNativeToken) || msg.sender == address(wethUnwrapper)) {
return;
}

_createDepositOnFallback();
}

/**
* @dev Creates deposit request.
*/
function _createDepositOnFallback() internal virtual whenNotPaused {
Transfer.Request memory _request;
_request.recipientAddr = msg.sender;
_request.info.quantity = msg.value;
_requestDepositFor(_request, _request.recipientAddr);
}

/**
Expand Down
8 changes: 7 additions & 1 deletion test/bridge/integration/BaseIntegration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { BridgeSlash } from "@ronin/contracts/ronin/gateway/BridgeSlash.sol";
import { BridgeReward } from "@ronin/contracts/ronin/gateway/BridgeReward.sol";
import { MainchainGatewayV3 } from "@ronin/contracts/mainchain/MainchainGatewayV3.sol";
import { MainchainBridgeManager } from "@ronin/contracts/mainchain/MainchainBridgeManager.sol";
import { WethUnwrapper } from "@ronin/contracts/extensions/WethUnwrapper.sol";
import { MockERC20 } from "@ronin/contracts/mocks/token/MockERC20.sol";
import { MockERC721 } from "@ronin/contracts/mocks/token/MockERC721.sol";

Expand Down Expand Up @@ -49,6 +50,7 @@ import { RoninPauseEnforcerDeploy } from "@ronin/script/contracts/RoninPauseEnfo
import { MainchainGatewayV3Deploy } from "@ronin/script/contracts/MainchainGatewayV3Deploy.s.sol";
import { MainchainBridgeManagerDeploy } from "@ronin/script/contracts/MainchainBridgeManagerDeploy.s.sol";
import { MainchainPauseEnforcerDeploy } from "@ronin/script/contracts/MainchainPauseEnforcerDeploy.s.sol";
import { MainchainWethUnwrapperDeploy } from "@ronin/script/contracts/MainchainWethUnwrapperDeploy.s.sol";
import { WETHDeploy } from "@ronin/script/contracts/token/WETHDeploy.s.sol";
import { WRONDeploy } from "@ronin/script/contracts/token/WRONDeploy.s.sol";
import { AXSDeploy } from "@ronin/script/contracts/token/AXSDeploy.s.sol";
Expand All @@ -75,6 +77,7 @@ contract BaseIntegration_Test is Base_Test {
PauseEnforcer _mainchainPauseEnforcer;
MainchainGatewayV3 _mainchainGatewayV3;
MainchainBridgeManager _mainchainBridgeManager;
WethUnwrapper _mainchainWethUnwrapper;

MockWrappedToken _roninWeth;
MockWrappedToken _roninWron;
Expand Down Expand Up @@ -143,6 +146,9 @@ contract BaseIntegration_Test is Base_Test {
_mainchainUsdc = new USDCDeploy().run();
_mainchainMockERC721 = new MockERC721Deploy().run();

bytes memory args = abi.encode(_mainchainWeth);
_mainchainWethUnwrapper = new MainchainWethUnwrapperDeploy().runWithArgs(args);

_param = ISharedArgument(LibSharedAddress.CONFIG).sharedArguments();
_mainchainProposalUtils = new MainchainBridgeAdminUtils(_param.test.governorPKs, _mainchainBridgeManager, _param.mainchainBridgeManager.governors[0]);
}
Expand Down Expand Up @@ -540,13 +546,13 @@ contract BaseIntegration_Test is Base_Test {

_mainchainGatewayV3.initializeV2(address(_mainchainBridgeManager));
_mainchainGatewayV3.initializeV3();
_mainchainGatewayV3.initializeV4(payable(address(_mainchainWethUnwrapper)));
}

function _mainchainPauseEnforcerInitialize() internal {
_param.mainchainPauseEnforcer.target = address(_mainchainGatewayV3);

ISharedArgument.PauseEnforcerParam memory param = _param.mainchainPauseEnforcer;

_mainchainPauseEnforcer.initialize(IPauseTarget(param.target), param.admin, param.sentries);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ contract RequestDepositFor_MainchainGatewayV3_Test is BaseIntegration_Test {
cachedRequest.tokenAddr = address(_mainchainWeth);

vm.expectEmit(address(_mainchainGatewayV3));
LibTransfer.Receipt memory receipt = cachedRequest.into_deposit_receipt(
_sender, _mainchainGatewayV3.depositCount(), address(_roninWeth), block.chainid
);
LibTransfer.Receipt memory receipt = cachedRequest.into_deposit_receipt(_sender, _mainchainGatewayV3.depositCount(), address(_roninWeth), block.chainid);
emit DepositRequested(receipt.hash(), receipt);

vm.prank(_sender);
Expand All @@ -70,9 +68,7 @@ contract RequestDepositFor_MainchainGatewayV3_Test is BaseIntegration_Test {
_depositRequest.tokenAddr = address(_mainchainAxs);

vm.expectEmit(address(_mainchainGatewayV3));
LibTransfer.Receipt memory receipt = _depositRequest.into_deposit_receipt(
_sender, _mainchainGatewayV3.depositCount(), address(_roninAxs), block.chainid
);
LibTransfer.Receipt memory receipt = _depositRequest.into_deposit_receipt(_sender, _mainchainGatewayV3.depositCount(), address(_roninAxs), block.chainid);
emit DepositRequested(receipt.hash(), receipt);

vm.prank(_sender);
Expand All @@ -94,9 +90,8 @@ contract RequestDepositFor_MainchainGatewayV3_Test is BaseIntegration_Test {
_depositRequest.info.id = tokenId;
_depositRequest.info.quantity = 0;

LibTransfer.Receipt memory receipt = _depositRequest.into_deposit_receipt(
_sender, _mainchainGatewayV3.depositCount(), address(_roninMockERC721), block.chainid
);
LibTransfer.Receipt memory receipt =
_depositRequest.into_deposit_receipt(_sender, _mainchainGatewayV3.depositCount(), address(_roninMockERC721), block.chainid);
vm.expectEmit(address(_mainchainGatewayV3));
emit DepositRequested(receipt.hash(), receipt);

Expand All @@ -118,15 +113,13 @@ contract RequestDepositFor_MainchainGatewayV3_Test is BaseIntegration_Test {

_depositRequest.tokenAddr = address(_mainchainWeth);

LibTransfer.Receipt memory receipt = _depositRequest.into_deposit_receipt(
_sender, _mainchainGatewayV3.depositCount(), address(_roninWeth), block.chainid
);
LibTransfer.Receipt memory receipt = _depositRequest.into_deposit_receipt(_sender, _mainchainGatewayV3.depositCount(), address(_roninWeth), block.chainid);
vm.expectEmit(address(_mainchainGatewayV3));
emit DepositRequested(receipt.hash(), receipt);

assertEq(address(_mainchainWeth).balance, _quantity);

vm.prank(_sender);
vm.startPrank(_sender);
_mainchainGatewayV3.requestDepositFor(_depositRequest);

assertEq(address(_mainchainGatewayV3).balance, _quantity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { ContractType } from "@ronin/contracts/utils/ContractType.sol";
import { Transfer } from "@ronin/contracts/libraries/Transfer.sol";
import { Token } from "@ronin/contracts/libraries/Token.sol";
import { SignatureConsumer } from "@ronin/contracts/interfaces/consumers/SignatureConsumer.sol";
import { MockDiscardEther } from "@ronin/test/mocks/MockDiscardEther.sol";
import "../../BaseIntegration.t.sol";

contract SubmitWithdrawal_MainchainGatewayV3_Test is BaseIntegration_Test{
contract SubmitWithdrawal_MainchainGatewayV3_Test is BaseIntegration_Test {
using Transfer for Transfer.Receipt;

Transfer.Receipt _withdrawalReceipt;
Expand All @@ -30,17 +31,29 @@ contract SubmitWithdrawal_MainchainGatewayV3_Test is BaseIntegration_Test{
_withdrawalReceipt.info.erc = Token.Standard.ERC20;
_withdrawalReceipt.info.id = 0;
_withdrawalReceipt.info.quantity = 0;

vm.deal(address(_mainchainGatewayV3), 10 ether);
vm.prank(address(_mainchainGatewayV3));
_mainchainWeth.deposit{ value: 10 ether }();
}

function test_submitWithdrawal_Native() public {
_withdrawalReceipt.info.quantity = 10;

SignatureConsumer.Signature[] memory signatures = _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs, _domainSeparator);

vm.deal(address(_mainchainGatewayV3), 10 ether);
_mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures);
}

function test_submitWithdrawal_WETH() public {
MockDiscardEther notReceiveEtherRecipient = new MockDiscardEther();

_withdrawalReceipt.mainchain.addr = address(notReceiveEtherRecipient);

vm.deal(address(_mainchainGatewayV3), 10 ether);

_withdrawalReceipt.info.quantity = 10;
SignatureConsumer.Signature[] memory signatures = _generateSignaturesFor(_withdrawalReceipt, _param.test.operatorPKs, _domainSeparator);

vm.expectEmit(address(_mainchainWeth));
emit IWETH.Transfer(address(_mainchainGatewayV3), address(notReceiveEtherRecipient), _withdrawalReceipt.info.quantity);
_mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures);
}

Expand All @@ -65,4 +78,4 @@ contract SubmitWithdrawal_MainchainGatewayV3_Test is BaseIntegration_Test{

_mainchainGatewayV3.submitWithdrawal(_withdrawalReceipt, signatures);
}
}
}
Loading

0 comments on commit 6b20944

Please sign in to comment.