190 lines
8.9 KiB
Solidity
190 lines
8.9 KiB
Solidity
pragma solidity 0.6.12;
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
import "../interfaces/IDepositExecute.sol";
|
|
import "./HandlerHelpers.sol";
|
|
import "../ERC721Safe.sol";
|
|
import "@openzeppelin/contracts/introspection/ERC165Checker.sol";
|
|
import "@openzeppelin/contracts/token/ERC721/IERC721Metadata.sol";
|
|
|
|
|
|
/**
|
|
@title Handles ERC721 deposits and deposit executions.
|
|
@author ChainSafe Systems.
|
|
@notice This contract is intended to be used with the Bridge contract.
|
|
*/
|
|
contract ERC721Handler is IDepositExecute, HandlerHelpers, ERC721Safe {
|
|
using ERC165Checker for address;
|
|
|
|
bytes4 private constant _INTERFACE_ERC721_METADATA = 0x5b5e139f;
|
|
|
|
struct DepositRecord {
|
|
address _tokenAddress;
|
|
uint8 _destinationChainID;
|
|
bytes32 _resourceID;
|
|
bytes _destinationRecipientAddress;
|
|
address _depositer;
|
|
uint _tokenID;
|
|
bytes _metaData;
|
|
}
|
|
|
|
// destId => depositNonce => Deposit Record
|
|
mapping (uint8 => mapping (uint64 => DepositRecord)) public _depositRecords;
|
|
|
|
/**
|
|
@param bridgeAddress Contract address of previously deployed Bridge.
|
|
@param initialResourceIDs Resource IDs are used to identify a specific contract address.
|
|
These are the Resource IDs this contract will initially support.
|
|
@param initialContractAddresses These are the addresses the {initialResourceIDs} will point to, and are the contracts that will be
|
|
called to perform various deposit calls.
|
|
@param burnableContractAddresses These addresses will be set as burnable and when {deposit} is called, the deposited token will be burned.
|
|
When {executeProposal} is called, new tokens will be minted.
|
|
|
|
@dev {initialResourceIDs} and {initialContractAddresses} must have the same length (one resourceID for every address).
|
|
Also, these arrays must be ordered in the way that {initialResourceIDs}[0] is the intended resourceID for {initialContractAddresses}[0].
|
|
*/
|
|
constructor(
|
|
address bridgeAddress,
|
|
bytes32[] memory initialResourceIDs,
|
|
address[] memory initialContractAddresses,
|
|
address[] memory burnableContractAddresses
|
|
) public {
|
|
require(initialResourceIDs.length == initialContractAddresses.length,
|
|
"initialResourceIDs and initialContractAddresses len mismatch");
|
|
|
|
_bridgeAddress = bridgeAddress;
|
|
|
|
for (uint256 i = 0; i < initialResourceIDs.length; i++) {
|
|
_setResource(initialResourceIDs[i], initialContractAddresses[i]);
|
|
}
|
|
|
|
for (uint256 i = 0; i < burnableContractAddresses.length; i++) {
|
|
_setBurnable(burnableContractAddresses[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@param depositNonce This ID will have been generated by the Bridge contract.
|
|
@param destId ID of chain deposit will be bridged to.
|
|
@return DepositRecord which consists of:
|
|
- _tokenAddress Address used when {deposit} was executed.
|
|
- _destinationChainID ChainID deposited tokens are intended to end up on.
|
|
- _resourceID ResourceID used when {deposit} was executed.
|
|
- _destinationRecipientAddress Address tokens are intended to be deposited to on desitnation chain.
|
|
- _depositer Address that initially called {deposit} in the Bridge contract.
|
|
- _tokenID ID of ERC721.
|
|
- _metaData Optional ERC721 metadata.
|
|
*/
|
|
function getDepositRecord(uint64 depositNonce, uint8 destId) external view returns (DepositRecord memory) {
|
|
return _depositRecords[destId][depositNonce];
|
|
}
|
|
|
|
/**
|
|
@notice A deposit is initiatied by making a deposit in the Bridge contract.
|
|
@param destinationChainID Chain ID of chain token is expected to be bridged to.
|
|
@param depositNonce This value is generated as an ID by the Bridge contract.
|
|
@param depositer Address of account making the deposit in the Bridge contract.
|
|
@param data Consists of: {resourceID}, {tokenID}, {lenDestinationRecipientAddress},
|
|
and {destinationRecipientAddress} all padded to 32 bytes.
|
|
@notice Data passed into the function should be constructed as follows:
|
|
tokenID uint256 bytes 0 - 32
|
|
destinationRecipientAddress length uint256 bytes 32 - 64
|
|
destinationRecipientAddress bytes bytes 64 - (64 + len(destinationRecipientAddress))
|
|
@notice If the corresponding {tokenAddress} for the parsed {resourceID} supports {_INTERFACE_ERC721_METADATA},
|
|
then {metaData} will be set according to the {tokenURI} method in the token contract.
|
|
@dev Depending if the corresponding {tokenAddress} for the parsed {resourceID} is
|
|
marked true in {_burnList}, deposited tokens will be burned, if not, they will be locked.
|
|
*/
|
|
function deposit(bytes32 resourceID,
|
|
uint8 destinationChainID,
|
|
uint64 depositNonce,
|
|
address depositer,
|
|
bytes calldata data
|
|
) external override onlyBridge {
|
|
uint tokenID;
|
|
uint lenDestinationRecipientAddress;
|
|
bytes memory destinationRecipientAddress;
|
|
bytes memory metaData;
|
|
|
|
(tokenID, lenDestinationRecipientAddress) = abi.decode(data, (uint, uint));
|
|
destinationRecipientAddress = bytes(data[64:64 + lenDestinationRecipientAddress]);
|
|
|
|
address tokenAddress = _resourceIDToTokenContractAddress[resourceID];
|
|
require(_contractWhitelist[tokenAddress], "provided tokenAddress is not whitelisted");
|
|
|
|
// Check if the contract supports metadata, fetch it if it does
|
|
if (tokenAddress.supportsInterface(_INTERFACE_ERC721_METADATA)) {
|
|
IERC721Metadata erc721 = IERC721Metadata(tokenAddress);
|
|
metaData = bytes(erc721.tokenURI(tokenID));
|
|
}
|
|
|
|
if (_burnList[tokenAddress]) {
|
|
burnERC721(tokenAddress, tokenID);
|
|
} else {
|
|
lockERC721(tokenAddress, depositer, address(this), tokenID);
|
|
}
|
|
|
|
_depositRecords[destinationChainID][depositNonce] = DepositRecord(
|
|
tokenAddress,
|
|
uint8(destinationChainID),
|
|
resourceID,
|
|
destinationRecipientAddress,
|
|
depositer,
|
|
tokenID,
|
|
metaData
|
|
);
|
|
}
|
|
|
|
/**
|
|
@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 {tokenID}, {resourceID}, {lenDestinationRecipientAddress},
|
|
{destinationRecipientAddress}, {lenMeta}, and {metaData} all padded to 32 bytes.
|
|
@notice Data passed into the function should be constructed as follows:
|
|
tokenID uint256 bytes 0 - 32
|
|
destinationRecipientAddress length uint256 bytes 32 - 64
|
|
destinationRecipientAddress bytes bytes 64 - (64 + len(destinationRecipientAddress))
|
|
metadata length uint256 bytes (64 + len(destinationRecipientAddress)) - (64 + len(destinationRecipientAddress) + 32)
|
|
metadata bytes bytes (64 + len(destinationRecipientAddress) + 32) - END
|
|
*/
|
|
function executeProposal(bytes32 resourceID, bytes calldata data) external override onlyBridge {
|
|
uint tokenID;
|
|
uint lenDestinationRecipientAddress;
|
|
bytes memory destinationRecipientAddress;
|
|
uint offsetMetaData;
|
|
uint lenMetaData;
|
|
bytes memory metaData;
|
|
|
|
(tokenID, lenDestinationRecipientAddress) = abi.decode(data, (uint, uint));
|
|
offsetMetaData = 64 + lenDestinationRecipientAddress;
|
|
destinationRecipientAddress = bytes(data[64:offsetMetaData]);
|
|
lenMetaData = abi.decode(data[offsetMetaData:], (uint));
|
|
metaData = bytes(data[offsetMetaData + 32:offsetMetaData + 32 + lenMetaData]);
|
|
|
|
bytes20 recipientAddress;
|
|
|
|
assembly {
|
|
recipientAddress := mload(add(destinationRecipientAddress, 0x20))
|
|
}
|
|
|
|
address tokenAddress = _resourceIDToTokenContractAddress[resourceID];
|
|
require(_contractWhitelist[address(tokenAddress)], "provided tokenAddress is not whitelisted");
|
|
|
|
if (_burnList[tokenAddress]) {
|
|
mintERC721(tokenAddress, address(recipientAddress), tokenID, metaData);
|
|
} else {
|
|
releaseERC721(tokenAddress, address(this), address(recipientAddress), tokenID);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@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.
|
|
*/
|
|
function withdraw(address tokenAddress, address recipient, uint tokenID) external override onlyBridge {
|
|
releaseERC721(tokenAddress, address(this), recipient, tokenID);
|
|
}
|
|
}
|