Skip to content

Instantly share code, notes, and snippets.

@z0r0z
Created August 18, 2022 05:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save z0r0z/ec346ab5c51e246870168e24c02085f7 to your computer and use it in GitHub Desktop.
Save z0r0z/ec346ab5c51e246870168e24c02085f7 to your computer and use it in GitHub Desktop.
Turns any token into a permittable ERC-1155 token.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC721.sol";
import "@openzeppelin/contracts/interfaces/IERC721Receiver.sol";
import "@openzeppelin/contracts/interfaces/IERC1155.sol";
import "@openzeppelin/contracts/interfaces/IERC1155Receiver.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
/// @notice Turns any token into a permittable ERC-1155 token.
/// @author Modified from @amxx (https://gist.github.com/Amxx/90e760a4bc98fea87b5b78903b7d584c)
/// @author and Primitive (https://github.com/primitivefinance/rmm-manager/blob/main/contracts/base/ERC1155Permit.sol)
/// @author by z0r0z.eth
contract UniversalWrapper is
ERC1155(""),
EIP712("UniversalWrapper", "1"),
IERC721Receiver,
IERC1155Receiver
{
mapping(address => uint256) public nonces;
bytes32 private immutable _PERMIT_TYPEHASH =
keccak256("Permit(address owner,address operator,bool approved,uint256 nonce,uint256 deadline)");
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC1155) returns (bool) {
return interfaceId == type(IERC721Receiver).interfaceId
|| interfaceId == type(IERC1155Receiver).interfaceId
|| super.supportsInterface(interfaceId);
}
/**
* ERC20
*/
function wrapERC20(IERC20 instance, uint256 amount, address to)
public
{
wrapERC20(instance, amount, to, bytes(""));
}
function wrapERC20(IERC20 instance, uint256 amount, address to, bytes memory data)
public
{
// check that we are not trying to hide an ERC721 transfer and get multiple wrapped instances.
require(!ERC165Checker.supportsInterface(address(instance), type(IERC721).interfaceId));
instance.transferFrom(msg.sender, address(this), amount);
_mint(to, getTokenId(address(instance), 0), amount, data);
}
function unwrapERC20(IERC20 instance, uint256 amount, address from, address to)
public
{
require(from == msg.sender || isApprovedForAll(from, msg.sender));
_burn(from, getTokenId(address(instance), 0), amount);
instance.transfer(to, amount);
}
/**
* ERC721
*/
function onERC721Received(address /* operator */, address /* from */, uint256 tokenId, bytes calldata data)
public
override
returns (bytes4)
{
address to = abi.decode(data, (address));
_mint(to, getTokenId(msg.sender, tokenId), 1, bytes(""));
return IERC721Receiver.onERC721Received.selector;
}
function unwrapERC721(IERC721 instance, uint256 tokenId, address from, address to)
public
{
unwrapERC721(instance, tokenId, from, to, bytes(""));
}
function unwrapERC721(IERC721 instance, uint256 tokenId, address from, address to, bytes memory data)
public
{
require(from == msg.sender || isApprovedForAll(from, msg.sender));
_burn(from, getTokenId(address(instance), tokenId), 1);
instance.safeTransferFrom(address(this), to, tokenId, data);
}
/**
* ERC1155
*/
function onERC1155Received(address /* operator */, address /* from */, uint256 tokenId, uint256 value, bytes calldata data)
public
override
returns (bytes4)
{
address to = abi.decode(data, (address));
_mint(to, getTokenId(msg.sender, tokenId), value, bytes(""));
return IERC1155Receiver.onERC1155Received.selector;
}
function onERC1155BatchReceived(address /* operator */, address /* from */, uint256[] calldata tokenIds, uint256[] calldata values, bytes calldata data)
public
override
returns (bytes4)
{
address to = abi.decode(data, (address));
uint256[] memory ids = new uint256[](tokenIds.length);
for (uint256 i = 0; i < tokenIds.length; ++i) {
ids[i] = getTokenId(msg.sender, tokenIds[i]);
}
_mintBatch(to, ids, values, bytes(""));
return IERC1155Receiver.onERC1155Received.selector;
}
function unwrapERC1155(IERC1155 instance, uint256 tokenId, uint256 amount, address from, address to)
public
{
unwrapERC1155(instance, tokenId, amount, from, to, bytes(""));
}
function unwrapERC1155(IERC1155 instance, uint256 tokenId, uint256 amount, address from, address to, bytes memory data)
public
{
require(from == msg.sender || isApprovedForAll(from, msg.sender));
_burn(from, getTokenId(address(instance), tokenId), amount);
instance.safeTransferFrom(address(this), to, tokenId, amount, data);
}
function unwrapERC1155Batch(IERC1155 instance, uint256[] memory tokenIds, uint256[] memory amounts, address from, address to)
public
{
unwrapERC1155Batch(instance, tokenIds, amounts, from, to, bytes(""));
}
function unwrapERC1155Batch(IERC1155 instance, uint256[] memory tokenIds, uint256[] memory amounts, address from, address to, bytes memory data)
public
{
require(from == msg.sender || isApprovedForAll(from, msg.sender));
uint256[] memory ids = new uint256[](tokenIds.length);
for (uint256 i = 0; i < tokenIds.length; ++i) {
ids[i] = getTokenId(address(instance), tokenIds[i]);
}
_burnBatch(from, ids, amounts);
instance.safeBatchTransferFrom(address(this), to, ids, amounts, data);
}
/**
* Utils
*/
function getTokenId(address instance, uint256 tokenId) public pure returns (uint256) {
return uint256(keccak256(abi.encode(instance, tokenId)));
}
function permit(
address owner,
address operator,
bool approved,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
bytes32 structHash = keccak256(
abi.encode(_PERMIT_TYPEHASH, owner, operator, approved, nonces[owner]++, deadline)
);
bytes32 hash = _hashTypedDataV4(structHash);
address recoveredAddress = ECDSA.recover(hash, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
}
_setApprovalForAll(owner, operator, approved);
}
function DOMAIN_SEPARATOR() public view returns (bytes32) {
return _domainSeparatorV4();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment