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

ERC1155 and ERC721 Floor Wrappers #79

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e6d172b
Remove contracts build task as no artifacts in git
ScreamingHawk Apr 5, 2023
8b91146
Add ERC721FloorWrapper
ScreamingHawk Mar 22, 2023
b7254c9
Add ERC1155FloorWrapper
ScreamingHawk Mar 23, 2023
447496c
Add metadata to wrappers
ScreamingHawk Mar 23, 2023
2ff8a7f
Wrapper swap tests
ScreamingHawk Mar 23, 2023
2586b1b
Gas optimisation for ERC721 Wrapper
ScreamingHawk Mar 28, 2023
466521b
Move wrappers to subfolder
ScreamingHawk Mar 29, 2023
c9a4068
ERC721 wrapper single token support with Factory
ScreamingHawk Mar 29, 2023
9db626c
Use a proxy for wrapper deployments
ScreamingHawk Mar 29, 2023
26bac19
Use Factory for ERC1155 Wrapper
ScreamingHawk Mar 29, 2023
f9da946
Predict addresses of wrappers
ScreamingHawk Mar 30, 2023
e8ae101
Optimise Wrapper initiation
ScreamingHawk Mar 30, 2023
36fd7a6
Use calldata in Wrapper funcs
ScreamingHawk Mar 30, 2023
f29aa90
Add token set check to init
ScreamingHawk Apr 2, 2023
65c76d3
Optimise loops
ScreamingHawk Apr 2, 2023
f7847e5
Fix doc typo
ScreamingHawk Apr 2, 2023
e7c5d51
Use receiver hook for ERC1155FloorWrapper
ScreamingHawk Apr 2, 2023
c89bf42
Bump version 6.0.2
ScreamingHawk Apr 5, 2023
4368934
Add audit for wrappers
ScreamingHawk May 3, 2023
e7b6453
Addressed audit comments
ScreamingHawk May 3, 2023
eec3379
Use ERC1967 Proxy
ScreamingHawk May 7, 2023
8316aab
ERC721Wrapper uses Receiver
ScreamingHawk May 7, 2023
5250b81
Check values before transfers
ScreamingHawk Jun 6, 2023
fdd647f
Fix Immutable variables cannot be initialized inside a try/catch stat…
ScreamingHawk Jun 6, 2023
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
22 changes: 0 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,28 +131,6 @@ jobs:
- name: Run tests
run: FOUNDRY_FUZZ_RUNS=1024 forge test -vvv

build:
name: Contracts built
runs-on: ubuntu-latest
needs: [install]
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions/setup-node@v1
with:
node-version: 18
- uses: actions/cache@master
id: yarn-cache
with:
path: |
node_modules
*/*/node_modules
key: ${{ runner.os }}-lerna-${{ hashFiles('**/package.json', '**/yarn.lock') }}
- run: yarn build
- run: yarn format:ts
- run: git diff --exit-code

# coverage:
# name: Coverage
# runs-on: ubuntu-latest
Expand Down
21 changes: 21 additions & 0 deletions src/contracts/interfaces/IERC1155FloorFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

interface IERC1155FloorFactory {
event NewERC1155FloorWrapper(address indexed token);
event MetadataContractChanged(address indexed metadataContract);

/**
* Creates an ERC-1155 Floor Wrapper for given token contract
* @param tokenAddr The address of the ERC-1155 token contract
* @return The address of the ERC-1155 Floor Wrapper
*/
function createWrapper(address tokenAddr) external returns (address);

/**
* Return address of the ERC-1155 Floor Wrapper for a given token contract
* @param tokenAddr The address of the ERC-1155 token contract
* @return The address of the ERC-1155 Floor Wrapper
*/
function tokenToWrapper(address tokenAddr) external view returns (address);
}
55 changes: 55 additions & 0 deletions src/contracts/interfaces/IERC1155FloorWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol";
import {IERC1155TokenReceiver} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155TokenReceiver.sol";

interface IERC1155FloorWrapper is IERC1155, IERC1155TokenReceiver {
event TokensDeposited(uint256[] tokenIds, uint256[] tokenAmounts);

event TokensWithdrawn(uint256[] tokenIds, uint256[] tokenAmounts);

struct DepositRequestObj {
address recipient;
bytes data;
}

struct WithdrawRequestObj {
uint256[] tokenIds;
uint256[] tokenAmounts;
address recipient;
bytes data;
}

/**
* Accepts ERC-1155 tokens to wrap and wrapped ERC-1155 tokens to unwrap.
* @notice Unwrapped ERC-1155 tokens are treated as deposits. Wrapped ERC-1155 tokens are treated as withdrawals.
* @param operator The address which called `safeTransferFrom` function.
* @param from The address which previously owned the token.
* @param id The ID of the token being transferred.
* @param amount The amount of tokens being transferred.
* @param data Additional data with no specified format.
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
*/
function onERC1155Received(address operator, address from, uint256 id, uint256 amount, bytes calldata data)
external
returns (bytes4);

/**
* Accepts ERC-1155 tokens to wrap and wrapped ERC-1155 tokens to unwrap.
* @notice Unwrapped ERC-1155 tokens are treated as deposits. Wrapped ERC-1155 tokens are treated as withdrawals.
* @param operator The address which called `safeTransferFrom` function.
* @param from The address which previously owned the token.
* @param ids The IDs of the tokens being transferred.
* @param amounts The amounts of tokens being transferred.
* @param data Additional data with no specified format.
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
*/
function onERC1155BatchReceived(
address operator,
address from,
uint256[] calldata ids,
uint256[] calldata amounts,
bytes calldata data
) external returns (bytes4);
}
18 changes: 18 additions & 0 deletions src/contracts/interfaces/IERC721.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

interface IERC721 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool _approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
}
21 changes: 21 additions & 0 deletions src/contracts/interfaces/IERC721FloorFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

interface IERC721FloorFactory {
event NewERC721FloorWrapper(address indexed token);
event MetadataContractChanged(address indexed metadataContract);

/**
* Creates an ERC-721 Floor Wrapper for given token contract
* @param tokenAddr The address of the ERC-721 token contract
* @return The address of the ERC-721 Floor Wrapper
*/
function createWrapper(address tokenAddr) external returns (address);

/**
* Return address of the ERC-721 Floor Wrapper for a given token contract
* @param tokenAddr The address of the ERC-721 token contract
* @return The address of the ERC-721 Floor Wrapper
*/
function tokenToWrapper(address tokenAddr) external view returns (address);
}
28 changes: 28 additions & 0 deletions src/contracts/interfaces/IERC721FloorWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

import {IERC1155} from "@0xsequence/erc-1155/contracts/interfaces/IERC1155.sol";

interface IERC721FloorWrapper is IERC1155 {
event TokensDeposited(uint256[] tokenIds);

event TokensWithdrawn(uint256[] tokenIds);

/**
* Deposit and wrap ERC-721 tokens.
* @param tokenIds The ERC-721 token ids to deposit.
* @param recipient The recipient of the wrapped tokens.
* @param data Data to pass to ERC-1155 receiver.
* @notice Users must first approve this contract address on the ERC-721 contract.
* @dev This contract intentionally does not support IERC721Receiver for gas optimisations.
*/
function deposit(uint256[] calldata tokenIds, address recipient, bytes calldata data) external;

/**
* Unwrap and withdraw ERC-721 tokens.
* @param tokenIds The ERC-721 token ids to withdraw.
* @param recipient The recipient of the unwrapped tokens.
* @param data Data to pass to ERC-1155 receiver.
*/
function withdraw(uint256[] calldata tokenIds, address recipient, bytes calldata data) external;
}
2 changes: 1 addition & 1 deletion src/contracts/interfaces/IWrapAndNiftyswap.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface IWrapAndNiftyswap {

/**
* @notice Accepts only tokenWrapper tokens
* @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
* @return `bytes4(keccak256("onERC1155Received(address,address,uint256[],uint256[],bytes)"))`
*/
function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _amount, bytes calldata _data)
external
Expand Down
23 changes: 23 additions & 0 deletions src/contracts/mocks/ERC721Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";

/**
* This is a test ERC721 contract with unlimited free minting.
*/
contract ERC721Mock is ERC721 {
uint256 public minted;

constructor() ERC721("ERC721Mock", "") {} // solhint-disable-line no-empty-blocks

/**
* Public and unlimited mint function
*/
function mintMock(address to, uint256 amount) external {
for (uint256 i; i < amount; i++) {
_safeMint(to, minted + i);
}
minted += amount;
}
}
35 changes: 35 additions & 0 deletions src/contracts/utils/Proxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

contract Proxy {
address public implementation;

constructor(address _implementation) {
implementation = _implementation;
}

receive() external payable {
proxy();
}

fallback() external payable {
proxy();
}

function proxy() private {
address target;
assembly {
target := sload(implementation.slot)
}
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), target, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
}
20 changes: 20 additions & 0 deletions src/contracts/utils/WrapperErrors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

/**
* Errors for the ERC-1155 and ERC-721 Wrapper and Factory contracts.
*/
abstract contract WrapperErrors {
// Factories
error WrapperAlreadyCreated(address tokenAddr, address wrapperAddr);
error WrapperCreationFailed(address tokenAddr);

// ERC1155
error InvalidERC1155Received();
error InvalidDepositRequest();
error InvalidWithdrawRequest();

// General
error UnsupportedMethod();
error InvalidInitialization();
}
66 changes: 66 additions & 0 deletions src/contracts/utils/WrapperProxyDeployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

import {IERC721FloorFactory} from "../interfaces/IERC721FloorFactory.sol";
import {ERC721FloorWrapper} from "../wrappers/ERC721FloorWrapper.sol";
import {WrapperErrors} from "../utils/WrapperErrors.sol";
import {Ownable} from "../utils/Ownable.sol";
import {Proxy} from "../utils/Proxy.sol";
import {IDelegatedERC1155Metadata, IERC1155Metadata} from "../interfaces/IDelegatedERC1155Metadata.sol";

abstract contract WrapperProxyDeployer is WrapperErrors {
/**
* Creates a proxy contract for a given implementation
* @param implAddr The address of the proxy implementation
* @param tokenAddr The address of the token contract
* @return proxyAddr The address of the deployed proxy
*/
function deployProxy(address implAddr, address tokenAddr) internal returns (address proxyAddr) {
bytes memory code = getProxyCode(implAddr);
implAddr = predictWrapperAddress(code, tokenAddr);
if (isContract(implAddr)) {
revert WrapperAlreadyCreated(tokenAddr, implAddr);
}

// Compute the address of the proxy contract using create2
bytes32 salt = getProxySalt(tokenAddr);

// Deploy it
assembly {
implAddr := create2(0, add(code, 32), mload(code), salt)
}
if (implAddr == address(0)) {
revert WrapperCreationFailed(tokenAddr);
}
return implAddr;
}

function predictWrapperAddress(address implAddr, address tokenAddr) internal view returns (address) {
bytes memory code = getProxyCode(implAddr);
return predictWrapperAddress(code, tokenAddr);
}

function predictWrapperAddress(bytes memory code, address tokenAddr) private view returns (address) {
bytes32 salt = getProxySalt(tokenAddr);
address deployer = address(this);
bytes32 _data = keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(code)));
return address(uint160(uint256(_data)));
}

function getProxyCode(address implAddr) private pure returns (bytes memory) {
return abi.encodePacked(type(Proxy).creationCode, uint256(uint160(address(implAddr))));
}

function getProxySalt(address tokenAddr) private pure returns (bytes32) {
return keccak256(abi.encodePacked(tokenAddr));
}

function isContract(address addr) internal view returns (bool) {
uint256 csize;
// solhint-disable-next-line no-inline-assembly
assembly {
csize := extcodesize(addr)
}
return csize != 0;
}
}
Loading