Skip to content
This repository has been archived by the owner on May 9, 2024. It is now read-only.

Commit

Permalink
Add erc1155Handler, erc1155Safe, and modify HandlerHelpers (#416)
Browse files Browse the repository at this point in the history
* add erc1155Handler, erc1155Safe, and modify HandlerHelpers

* update old version withdraw functions

* add using normal address in handler withdraw

* add comments

* modify withdraw test and add tests for erc1155
  • Loading branch information
andersonlee725 authored Nov 4, 2021
1 parent cbfaf9c commit 6869c8d
Show file tree
Hide file tree
Showing 13 changed files with 662 additions and 34 deletions.
10 changes: 3 additions & 7 deletions contracts/Bridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -304,18 +304,14 @@ contract Bridge is Pausable, AccessControl, SafeMath {
/**
@notice Used to manually withdraw funds from ERC safes.
@param handlerAddress Address of handler to withdraw from.
@param tokenAddress Address of token to withdraw.
@param recipient Address to withdraw tokens to.
@param amountOrTokenID Either the amount of ERC20 tokens or the ERC721 token ID to withdraw.
@param data ABI-encoded withdrawal params relevant to the specified handler.
*/
function adminWithdraw(
address handlerAddress,
address tokenAddress,
address recipient,
uint256 amountOrTokenID
bytes memory data
) external onlyAdmin {
IERCHandler handler = IERCHandler(handlerAddress);
handler.withdraw(tokenAddress, recipient, amountOrTokenID);
handler.withdraw(data);
}

/**
Expand Down
68 changes: 68 additions & 0 deletions contracts/ERC1155Safe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.6.12;

import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155Burnable.sol";
import "@openzeppelin/contracts/presets/ERC1155PresetMinterPauser.sol";

/**
@title Manages deposited ERC1155s.
@author ChainSafe Systems.
@notice This contract is intended to be used with ERC1155Handler contract.
*/
contract ERC1155Safe {
using SafeMath for uint256;

/**
@notice Used to gain custoday of deposited token with batching.
@param tokenAddress Address of ERC1155 to transfer.
@param owner Address of current token owner.
@param recipient Address to transfer token to.
@param tokenIDs IDs of tokens to transfer.
@param amounts Amounts of tokens to transfer.
@param data Additional data.
*/
function lockBatchERC1155(address tokenAddress, address owner, address recipient, uint[] memory tokenIDs, uint[] memory amounts, bytes memory data) internal {
IERC1155 erc1155 = IERC1155(tokenAddress);
erc1155.safeBatchTransferFrom(owner, recipient, tokenIDs, amounts, data);
}

/**
@notice Transfers custody of token to recipient with batching.
@param tokenAddress Address of ERC1155 to transfer.
@param owner Address of current token owner.
@param recipient Address to transfer token to.
@param tokenIDs IDs of tokens to transfer.
@param amounts Amounts of tokens to transfer.
@param data Additional data.
*/
function releaseBatchERC1155(address tokenAddress, address owner, address recipient, uint256[] memory tokenIDs, uint[] memory amounts, bytes memory data) internal {
IERC1155 erc1155 = IERC1155(tokenAddress);
erc1155.safeBatchTransferFrom(owner, recipient, tokenIDs, amounts, data);
}

/**
@notice Used to create new ERC1155s with batching.
@param tokenAddress Address of ERC1155 to mint.
@param recipient Address to mint token to.
@param tokenIDs IDs of tokens to mint.
@param amounts Amounts of token to mint.
@param data Additional data.
*/
function mintBatchERC1155(address tokenAddress, address recipient, uint[] memory tokenIDs, uint[] memory amounts, bytes memory data) internal {
ERC1155PresetMinterPauser erc1155 = ERC1155PresetMinterPauser(tokenAddress);
erc1155.mintBatch(recipient, tokenIDs, amounts, data);
}

/**
@notice Used to burn ERC1155s with batching.
@param tokenAddress Address of ERC1155 to burn.
@param tokenIDs IDs of tokens to burn.
@param amounts Amounts of tokens to burn.
*/
function burnBatchERC1155(address tokenAddress, address owner, uint[] memory tokenIDs, uint[] memory amounts) internal {
ERC1155Burnable erc1155 = ERC1155Burnable(tokenAddress);
erc1155.burnBatch(owner, tokenIDs, amounts);
}
}
94 changes: 94 additions & 0 deletions contracts/handlers/ERC1155Handler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;

import "../interfaces/IDepositExecute.sol";
import "./HandlerHelpers.sol";
import "../ERC1155Safe.sol";
import "@openzeppelin/contracts/introspection/ERC165Checker.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155Holder.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155MetadataURI.sol";

contract ERC1155Handler is IDepositExecute, HandlerHelpers, ERC1155Safe, ERC1155Holder {
using ERC165Checker for address;

bytes4 private constant _INTERFACE_ERC1155_METADATA = 0x0e89341c;
bytes private constant EMPTY_BYTES = "";

/**
@param bridgeAddress Contract address of previously deployed Bridge.
*/
constructor(
address bridgeAddress
) public HandlerHelpers(bridgeAddress) {
}

/**
@notice A deposit is initiatied by making a deposit in the Bridge contract.
@param resourceID ResourceID used to find address of token to be used for deposit.
@param depositer Address of account making the deposit in the Bridge contract.
@param data Consists of ABI-encoded arrays of tokenIDs and amounts.
*/
function deposit(bytes32 resourceID, address depositer, bytes calldata data) external override onlyBridge returns (bytes memory metaData) {
uint[] memory tokenIDs;
uint[] memory amounts;

(tokenIDs, amounts) = abi.decode(data, (uint[], uint[]));

address tokenAddress = _resourceIDToTokenContractAddress[resourceID];
require(tokenAddress != address(0), "provided resourceID does not exist");

if (_burnList[tokenAddress]) {
burnBatchERC1155(tokenAddress, depositer, tokenIDs, amounts);
} else {
lockBatchERC1155(tokenAddress, depositer, address(this), tokenIDs, amounts, EMPTY_BYTES);
}
}

/**
@notice Proposal execution should be initiated when a proposal is finalized in the Bridge contract.
by a relayer on the deposit's destination chain.
@param data Consists of ABI-encoded {tokenIDs}, {amounts}, {recipient},
and {transferData} of types uint[], uint[], bytes, bytes.
*/
function executeProposal(bytes32 resourceID, bytes calldata data) external override onlyBridge {
uint[] memory tokenIDs;
uint[] memory amounts;
bytes memory recipient;
bytes memory transferData;

(tokenIDs, amounts, recipient, transferData) = abi.decode(data, (uint[], uint[], bytes, bytes));

bytes20 recipientAddress;

assembly {
recipientAddress := mload(add(recipient, 0x20))
}

address tokenAddress = _resourceIDToTokenContractAddress[resourceID];
require(_contractWhitelist[address(tokenAddress)], "provided tokenAddress is not whitelisted");

if (_burnList[tokenAddress]) {
mintBatchERC1155(tokenAddress, address(recipientAddress), tokenIDs, amounts, transferData);
} else {
releaseBatchERC1155(tokenAddress, address(this), address(recipientAddress), tokenIDs, amounts, transferData);
}
}

/**
@notice Used to manually release ERC1155 tokens from ERC1155Safe.
@param data Consists of ABI-encoded {tokenAddress}, {recipient}, {tokenIDs},
{amounts}, and {transferData} of types address, address, uint[], uint[], bytes.
*/
function withdraw(bytes memory data) external override onlyBridge {
address tokenAddress;
address recipient;
uint[] memory tokenIDs;
uint[] memory amounts;
bytes memory transferData;

(tokenAddress, recipient, tokenIDs, amounts, transferData) = abi.decode(data, (address, address, uint[], uint[], bytes));

releaseBatchERC1155(tokenAddress, address(this), recipient, tokenIDs, amounts, transferData);
}
}
16 changes: 12 additions & 4 deletions contracts/handlers/ERC20Handler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,19 @@ contract ERC20Handler is IDepositExecute, HandlerHelpers, ERC20Safe {

/**
@notice Used to manually release ERC20 tokens from ERC20Safe.
@param tokenAddress Address of token contract to release.
@param recipient Address to release tokens to.
@param amount The amount of ERC20 tokens to release.
@param data Consists of {tokenAddress}, {recipient}, and {amount} all padded to 32 bytes.
@notice Data passed into the function should be constructed as follows:
tokenAddress address bytes 0 - 32
recipient address bytes 32 - 64
amount uint bytes 64 - 96
*/
function withdraw(address tokenAddress, address recipient, uint amount) external override onlyBridge {
function withdraw(bytes memory data) external override onlyBridge {
address tokenAddress;
address recipient;
uint amount;

(tokenAddress, recipient, amount) = abi.decode(data, (address, address, uint));

releaseERC20(tokenAddress, recipient, amount);
}
}
16 changes: 12 additions & 4 deletions contracts/handlers/ERC721Handler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,19 @@ contract ERC721Handler is IDepositExecute, HandlerHelpers, ERC721Safe {

/**
@notice Used to manually release ERC721 tokens from ERC721Safe.
@param tokenAddress Address of token contract to release.
@param recipient Address to release token to.
@param tokenID The ERC721 token ID to release.
@param data Consists of {tokenAddress}, {recipient}, and {tokenID} all padded to 32 bytes.
@notice Data passed into the function should be constructed as follows:
tokenAddress address bytes 0 - 32
recipient address bytes 32 - 64
tokenID uint bytes 64 - 96
*/
function withdraw(address tokenAddress, address recipient, uint tokenID) external override onlyBridge {
function withdraw(bytes memory data) external override onlyBridge {
address tokenAddress;
address recipient;
uint tokenID;

(tokenAddress, recipient, tokenID) = abi.decode(data, (address, address, uint));

releaseERC721(tokenAddress, address(this), recipient, tokenID);
}
}
8 changes: 1 addition & 7 deletions contracts/handlers/HandlerHelpers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,7 @@ contract HandlerHelpers is IERCHandler {
_setBurnable(contractAddress);
}

/**
@notice Used to manually release funds from ERC safes.
@param tokenAddress Address of token contract to release.
@param recipient Address to release tokens to.
@param amountOrTokenID Either the amount of ERC20 tokens or the ERC721 token ID to release.
*/
function withdraw(address tokenAddress, address recipient, uint256 amountOrTokenID) external virtual override {}
function withdraw(bytes memory data) external virtual override {}

function _setResource(bytes32 resourceID, address contractAddress) internal {
_resourceIDToTokenContractAddress[resourceID] = contractAddress;
Expand Down
9 changes: 4 additions & 5 deletions contracts/interfaces/IERCHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ interface IERCHandler {
@param contractAddress Address of contract to be used when making or executing deposits.
*/
function setBurnable(address contractAddress) external;

/**
@notice Used to manually release funds from ERC safes.
@param tokenAddress Address of token contract to release.
@param recipient Address to release tokens to.
@param amountOrTokenID Either the amount of ERC20 tokens or the ERC721 token ID to release.
@notice Withdraw funds from ERC safes.
@param data ABI-encoded withdrawal params relevant to the handler.
*/
function withdraw(address tokenAddress, address recipient, uint256 amountOrTokenID) external;
function withdraw(bytes memory data) external;
}
8 changes: 6 additions & 2 deletions test/contractBridge/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ contract('Bridge - [admin]', async accounts => {

let BridgeInstance;

let withdrawData = '';

const assertOnlyAdmin = (method, ...params) => {
return TruffleAssert.reverts(method(...params, {from: initialRelayers[1]}), "sender doesn't have admin role");
};
Expand Down Expand Up @@ -211,13 +213,15 @@ contract('Bridge - [admin]', async accounts => {
handlerBalance = await ERC20MintableInstance.balanceOf(ERC20HandlerInstance.address);
assert.equal(handlerBalance, numTokens);

await BridgeInstance.adminWithdraw(ERC20HandlerInstance.address, ERC20MintableInstance.address, tokenOwner, numTokens);
withdrawData = Helpers.createERCWithdrawData(ERC20MintableInstance.address, tokenOwner, numTokens);

await BridgeInstance.adminWithdraw(ERC20HandlerInstance.address, withdrawData);
ownerBalance = await ERC20MintableInstance.balanceOf(tokenOwner);
assert.equal(ownerBalance, numTokens);
});

it('Should require admin role to withdraw funds', async () => {
await assertOnlyAdmin(BridgeInstance.adminWithdraw, someAddress, someAddress, someAddress, 0);
await assertOnlyAdmin(BridgeInstance.adminWithdraw, someAddress, "0x0");
});

// Set nonce
Expand Down
Loading

0 comments on commit 6869c8d

Please sign in to comment.