Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support bulk deposit/withdraw and ERC1155 #4

Open
wants to merge 2 commits into
base: mainnet
Choose a base branch
from
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
6 changes: 6 additions & 0 deletions src/interfaces/IERC1155Mintable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC1155Mintable {
function mint(address _to, uint256 _id, uint256 _amount, bytes memory _data) external returns (bool);
}
28 changes: 28 additions & 0 deletions src/interfaces/IMainchainGatewayV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ interface IMainchainGatewayV3 is SignatureConsumer, MappedTokenConsumer {
*/
error ErrQueryForInsufficientVoteWeight();

/**
* @dev Error indicating that a bulk deposit contains more than 1 native token
* deposit requests.
*/
error ErrMoreThanOneNativeTokenRequests();

/**
* @dev Error indicating that the withdrawal receipts and signatures lengths mismatch.
*/
error ErrReceiptAndSignatureLengthsMismatch();

/// @dev Emitted when the deposit is requested
event DepositRequested(bytes32 receiptHash, Transfer.Receipt receipt);
/// @dev Emitted when the assets are withdrawn
Expand Down Expand Up @@ -76,6 +87,11 @@ interface IMainchainGatewayV3 is SignatureConsumer, MappedTokenConsumer {
*/
function requestDepositFor(Transfer.Request calldata _request) external payable;

/**
* @dev Locks the assets and request multiple deposits.
*/
function bulkRequestDepositFor(Transfer.Request[] calldata _requests) external payable;

/**
* @dev Withdraws based on the receipt and the validator signatures.
* Returns whether the withdrawal is locked.
Expand All @@ -88,6 +104,18 @@ interface IMainchainGatewayV3 is SignatureConsumer, MappedTokenConsumer {
Signature[] memory _signatures
) external returns (bool _locked);

/**
* @dev Bulk withdraws based on the receipt and the validator signatures.
* Returns whether the withdrawal is locked.
*
* Emits the `Withdrew` once the assets are released.
*
*/
function bulkSubmitWithdrawal(
Transfer.Receipt[] memory _receipts,
Signature[][] memory _signatures
) external returns (bool[] memory _locked);

/**
* @dev Approves a specific withdrawal.
*
Expand Down
55 changes: 44 additions & 11 deletions src/libraries/Token.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "../interfaces/IWETH.sol";
import "../interfaces/IERC20Mintable.sol";
import "../interfaces/IERC721Mintable.sol";
import "../interfaces/IERC1155Mintable.sol";

library Token {
/// @dev Error indicating that the provided information is invalid.
Expand All @@ -15,6 +19,9 @@ library Token {
/// @dev Error indicating that the minting of ERC721 tokens has failed.
error ErrERC721MintingFailed();

/// @dev Error indicating that the minting of ERC1155 tokens has failed.
error ErrERC1155MintingFailed();

/// @dev Error indicating that an unsupported standard is encountered.
error ErrUnsupportedStandard();

Expand All @@ -37,13 +44,15 @@ library Token {

enum Standard {
ERC20,
ERC721
ERC721,
ERC1155
}

struct Info {
Standard erc;
// For ERC20: the id must be 0 and the quantity is larger than 0.
// For ERC721: the quantity must be 0.
// For ERC1155: the quantity is larger than 0.
uint256 id;
uint256 quantity;
}
Expand Down Expand Up @@ -72,7 +81,8 @@ library Token {
function validate(Info memory _info) internal pure {
if (
!((_info.erc == Standard.ERC20 && _info.quantity > 0 && _info.id == 0) ||
(_info.erc == Standard.ERC721 && _info.quantity == 0))
(_info.erc == Standard.ERC721 && _info.quantity == 0) ||
(_info.erc == Standard.ERC1155 && _info.quantity > 0))
) revert ErrInvalidInfo();
}

Expand All @@ -87,11 +97,12 @@ library Token {
bool _success;
bytes memory _data;
if (_info.erc == Standard.ERC20) {
(_success, _data) = _token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, _from, _to, _info.quantity));
(_success, _data) = _token.call(abi.encodeCall(IERC20.transferFrom, (_from, _to, _info.quantity)));
_success = _success && (_data.length == 0 || abi.decode(_data, (bool)));
} else if (_info.erc == Standard.ERC721) {
// bytes4(keccak256("transferFrom(address,address,uint256)"))
(_success, ) = _token.call(abi.encodeWithSelector(0x23b872dd, _from, _to, _info.id));
(_success, ) = _token.call(abi.encodeCall(IERC721.transferFrom, (_from, _to, _info.id)));
} else if (_info.erc == Standard.ERC1155) {
(_success, ) = _token.call(abi.encodeCall(IERC1155.safeTransferFrom, (_from, _to, _info.id, _info.quantity, "")));
} else revert ErrUnsupportedStandard();

if (!_success) revert ErrTokenCouldNotTransferFrom(_info, _from, _to, _token);
Expand All @@ -101,18 +112,31 @@ library Token {
* @dev Transfers ERC721 token and returns the result.
*/
function tryTransferERC721(address _token, address _to, uint256 _id) internal returns (bool _success) {
(_success, ) = _token.call(abi.encodeWithSelector(IERC721.transferFrom.selector, address(this), _to, _id));
(_success, ) = _token.call(abi.encodeCall(IERC721.transferFrom, (address(this), _to, _id)));
}

/**
* @dev Transfers ERC20 token and returns the result.
*/
function tryTransferERC20(address _token, address _to, uint256 _quantity) internal returns (bool _success) {
bytes memory _data;
(_success, _data) = _token.call(abi.encodeWithSelector(IERC20.transfer.selector, _to, _quantity));
(_success, _data) = _token.call(abi.encodeCall(IERC20.transfer, (_to, _quantity)));
_success = _success && (_data.length == 0 || abi.decode(_data, (bool)));
}

/**
* @dev Transfers ERC1155 token and returns the result.
*/
function tryTransferERC1155(address _token, address _to, uint256 _id, uint256 _quantity) internal returns (bool _success) {
// safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data)
(_success, ) = _token.call(abi.encodeCall(IERC1155.safeTransferFrom, (
address(this),
_to,
_id,
_quantity,
"")));
}

/**
* @dev Transfer assets from current address to `_to` address.
*/
Expand All @@ -122,6 +146,8 @@ library Token {
_success = tryTransferERC20(_token, _to, _info.quantity);
} else if (_info.erc == Standard.ERC721) {
_success = tryTransferERC721(_token, _to, _info.id);
} else if (_info.erc == Standard.ERC1155) {
_success = tryTransferERC1155(_token, _to, _info.id, _info.quantity);
} else revert ErrUnsupportedStandard();

if (!_success) revert ErrTokenCouldNotTransfer(_info, _to, _token);
Expand Down Expand Up @@ -150,18 +176,25 @@ library Token {
uint256 _balance = IERC20(_token).balanceOf(address(this));

if (_balance < _info.quantity) {
// bytes4(keccak256("mint(address,uint256)"))
(_success, ) = _token.call(abi.encodeWithSelector(0x40c10f19, address(this), _info.quantity - _balance));
(_success, ) = _token.call(abi.encodeCall(IERC20Mintable.mint, (address(this), _info.quantity - _balance)));
if (!_success) revert ErrERC20MintingFailed();
}

transfer(_info, _to, _token);
} else if (_info.erc == Token.Standard.ERC721) {
if (!tryTransferERC721(_token, _to, _info.id)) {
// bytes4(keccak256("mint(address,uint256)"))
(_success, ) = _token.call(abi.encodeWithSelector(0x40c10f19, _to, _info.id));
(_success, ) = _token.call(abi.encodeCall(IERC721Mintable.mint, (_to, _info.id)));
if (!_success) revert ErrERC721MintingFailed();
}
} else if (_info.erc == Token.Standard.ERC1155) {
uint256 _balance = IERC1155(_token).balanceOf(address(this), _info.id);

if (_balance < _info.quantity) {
(_success, ) = _token.call(abi.encodeCall(IERC1155Mintable.mint, (address(this), _info.id, _info.quantity - _balance, "")));
if (!_success) revert ErrERC1155MintingFailed();
}

transfer(_info, _to, _token);
} else revert ErrUnsupportedStandard();
}

Expand Down
105 changes: 101 additions & 4 deletions src/mainchain/MainchainGatewayV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import { IBridgeManager } from "../interfaces/bridge/IBridgeManager.sol";
import { IBridgeManagerCallback } from "../interfaces/bridge/IBridgeManagerCallback.sol";
import { HasContracts, ContractType } from "../extensions/collections/HasContracts.sol";
Expand All @@ -15,7 +17,9 @@ contract MainchainGatewayV3 is
Initializable,
AccessControlEnumerable,
IMainchainGatewayV3,
HasContracts
HasContracts,
IERC721Receiver,
IERC1155Receiver
{
using Token for Token.Info;
using Transfer for Transfer.Request;
Expand Down Expand Up @@ -129,9 +133,43 @@ contract MainchainGatewayV3 is
* @inheritdoc IMainchainGatewayV3
*/
function requestDepositFor(Transfer.Request calldata _request) external payable virtual whenNotPaused {
// A non-native deposit request with msg.value is invalid
if (_request.tokenAddr != address(0) && msg.value != 0) {
revert ErrInvalidRequest();
}
_requestDepositFor(_request, msg.sender);
}

/**
* @inheritdoc IMainchainGatewayV3
*/
function bulkRequestDepositFor(Transfer.Request[] calldata _requests) external payable virtual whenNotPaused {
// The _requestDepositFor does not work correctly when there are
// more than 1 native Ether deposit request in the bulk deposit.
// So we need to check here and revert in that case.
bool _hasNativeTokenRequest = false;
for (uint256 _i; _i < _requests.length;) {
if (_requests[_i].tokenAddr == address(0)) {
if (_hasNativeTokenRequest) {
revert ErrMoreThanOneNativeTokenRequests();
} else {
_hasNativeTokenRequest = true;
}
}

_requestDepositFor(_requests[_i], msg.sender);

unchecked {
_i++;
}
}

// Non-native deposit requests with msg.value are invalid
if (!_hasNativeTokenRequest && msg.value != 0) {
revert ErrInvalidRequest();
}
}

/**
* @inheritdoc IMainchainGatewayV3
*/
Expand All @@ -142,6 +180,29 @@ contract MainchainGatewayV3 is
return _submitWithdrawal(_receipt, _signatures);
}

/**
* @inheritdoc IMainchainGatewayV3
*/
function bulkSubmitWithdrawal(
Transfer.Receipt[] calldata _receipts,
Signature[][] calldata _signatures
) external virtual whenNotPaused returns (bool[] memory _locked) {
if (_receipts.length != _signatures.length) {
revert ErrReceiptAndSignatureLengthsMismatch();
}

_locked = new bool[](_receipts.length);
for (uint256 _i; _i < _receipts.length;) {
_locked[_i] = _submitWithdrawal(_receipts[_i], _signatures[_i]);

unchecked {
_i++;
}
}

return _locked;
}

/**
* @inheritdoc IMainchainGatewayV3
*/
Expand Down Expand Up @@ -277,7 +338,7 @@ contract MainchainGatewayV3 is

if (withdrawalHash[_id] != 0) revert ErrQueryForProcessedWithdrawal();

if (!(_receipt.info.erc == Token.Standard.ERC721 || !_reachedWithdrawalLimit(_tokenAddr, _quantity))) {
if (_receipt.info.erc == Token.Standard.ERC20 && _reachedWithdrawalLimit(_tokenAddr, _quantity)) {
revert ErrReachedDailyWithdrawalLimit();
}

Expand Down Expand Up @@ -350,8 +411,6 @@ contract MainchainGatewayV3 is

_request.tokenAddr = _weth;
} else {
if (msg.value != 0) revert ErrInvalidRequest();

_token = getRoninToken(_request.tokenAddr);
if (_token.erc != _request.info.erc) revert ErrInvalidTokenStandard();

Expand Down Expand Up @@ -456,4 +515,42 @@ contract MainchainGatewayV3 is
function _getWeight(address _addr) internal view returns (uint256) {
return IBridgeManager(getContract(ContractType.BRIDGE_MANAGER)).getBridgeOperatorWeight(_addr);
}

/**
* @inheritdoc IERC1155Receiver
*/
function onERC1155Received(
address /* operator */,
address /* from */,
uint256 /* id */,
uint256 /* value */,
bytes calldata /* data */
) external pure returns (bytes4) {
return IERC1155Receiver.onERC1155Received.selector;
}

/**
* @inheritdoc IERC1155Receiver
*/
function onERC1155BatchReceived(
address /* operator */,
address /* from */,
uint256[] calldata /* ids */,
uint256[] calldata /* values */,
bytes calldata /* data */
) external pure returns (bytes4) {
return IERC1155Receiver.onERC1155Received.selector;
}

/**
* @inheritdoc IERC721Receiver
*/
function onERC721Received(
address /* operator */,
address /* from */,
uint256 /* tokenId */,
bytes calldata /* data */
) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
}
Loading