From 3ca2f8ac9e7dfc23d8008883d83a5381f39402d3 Mon Sep 17 00:00:00 2001 From: Amir Date: Sun, 12 Jan 2020 22:25:06 -0800 Subject: [PATCH] Split out transfer logic into library, add 1155 support --- .../contracts/src/Forwarder.sol | 4 +- .../contracts/src/MixinAssets.sol | 100 +------ .../contracts/src/MixinExchangeWrapper.sol | 7 +- .../contracts/src/MixinReceiver.sol | 76 ++++++ .../src/libs/LibAssetDataTransfer.sol | 258 ++++++++++++++++++ .../contracts/test/TestForwarder.sol | 14 +- 6 files changed, 351 insertions(+), 108 deletions(-) create mode 100644 contracts/exchange-forwarder/contracts/src/MixinReceiver.sol create mode 100644 contracts/exchange-forwarder/contracts/src/libs/LibAssetDataTransfer.sol diff --git a/contracts/exchange-forwarder/contracts/src/Forwarder.sol b/contracts/exchange-forwarder/contracts/src/Forwarder.sol index 8ed8fa0b28..f841bdd7f2 100644 --- a/contracts/exchange-forwarder/contracts/src/Forwarder.sol +++ b/contracts/exchange-forwarder/contracts/src/Forwarder.sol @@ -21,6 +21,7 @@ pragma experimental ABIEncoderV2; import "./MixinForwarderCore.sol"; import "./libs/LibConstants.sol"; +import "./MixinReceiver.sol"; // solhint-disable no-empty-blocks @@ -28,7 +29,8 @@ import "./libs/LibConstants.sol"; // MixinForwarderCore. contract Forwarder is LibConstants, - MixinForwarderCore + MixinForwarderCore, + MixinReceiver { constructor ( address _exchange, diff --git a/contracts/exchange-forwarder/contracts/src/MixinAssets.sol b/contracts/exchange-forwarder/contracts/src/MixinAssets.sol index 6fa6f69a8f..8e08e3c02a 100644 --- a/contracts/exchange-forwarder/contracts/src/MixinAssets.sol +++ b/contracts/exchange-forwarder/contracts/src/MixinAssets.sol @@ -20,12 +20,11 @@ pragma solidity ^0.5.9; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; -import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; import "@0x/contracts-utils/contracts/src/Ownable.sol"; import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; -import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol"; import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol"; import "./libs/LibConstants.sol"; +import "./libs/LibAssetDataTransfer.sol"; import "./libs/LibForwarderRichErrors.sol"; import "./interfaces/IAssets.sol"; @@ -36,7 +35,7 @@ contract MixinAssets is IAssets { using LibBytes for bytes; - using LibSafeMath for uint256; + using LibAssetDataTransfer for bytes; /// @dev Withdraws assets from this contract. It may be used by the owner to withdraw assets /// that were accidentally sent to this contract. @@ -49,7 +48,7 @@ contract MixinAssets is external onlyOwner { - _transferAssetToSender(assetData, amount); + assetData.transferOut(amount); } /// @dev Approves the respective proxy for a given asset to transfer tokens on the Forwarder contract's behalf. @@ -74,97 +73,4 @@ contract MixinAssets is LibERC20Token.approve(token, proxyAddress, MAX_UINT); } } - - /// @dev Transfers given amount of asset to sender. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of asset to transfer to sender. - function _transferAssetToSender( - bytes memory assetData, - uint256 amount - ) - internal - { - if (amount == 0) { - return; - } - - bytes4 proxyId = assetData.readBytes4(0); - - if ( - proxyId == IAssetData(address(0)).ERC20Token.selector || - proxyId == IAssetData(address(0)).ERC20Bridge.selector - ) { - _transferERC20Token(assetData, amount); - } else if (proxyId == IAssetData(address(0)).ERC721Token.selector) { - _transferERC721Token(assetData, amount); - } else if (proxyId == IAssetData(address(0)).MultiAsset.selector) { - _transferMultiAsset(assetData, amount); - } else if (proxyId != IAssetData(address(0)).StaticCall.selector) { - LibRichErrors.rrevert(LibForwarderRichErrors.UnsupportedAssetProxyError( - proxyId - )); - } - } - - /// @dev Decodes ERC20 or ERC20Bridge assetData and transfers given amount to sender. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of asset to transfer to sender. - function _transferERC20Token( - bytes memory assetData, - uint256 amount - ) - internal - { - address token = assetData.readAddress(16); - // Transfer tokens. - LibERC20Token.transfer(token, msg.sender, amount); - } - - /// @dev Decodes ERC721 assetData and transfers given amount to sender. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of asset to transfer to sender. - function _transferERC721Token( - bytes memory assetData, - uint256 amount - ) - internal - { - if (amount != 1) { - LibRichErrors.rrevert(LibForwarderRichErrors.Erc721AmountMustEqualOneError( - amount - )); - } - // Decode asset data. - address token = assetData.readAddress(16); - uint256 tokenId = assetData.readUint256(36); - - // Perform transfer. - IERC721Token(token).transferFrom( - address(this), - msg.sender, - tokenId - ); - } - - /// @dev Decodes MultiAsset assetData and recursively transfers assets to sender. - /// @param assetData Byte array encoded for the respective asset proxy. - /// @param amount Amount of asset to transfer to sender. - function _transferMultiAsset( - bytes memory assetData, - uint256 amount - ) - internal - { - // solhint-disable indent - (uint256[] memory nestedAmounts, bytes[] memory nestedAssetData) = abi.decode( - assetData.slice(4, assetData.length), - (uint256[], bytes[]) - ); - // solhint-enable indent - - uint256 numNestedAssets = nestedAssetData.length; - for (uint256 i = 0; i != numNestedAssets; i++) { - _transferAssetToSender(nestedAssetData[i], amount.safeMul(nestedAmounts[i])); - } - } } diff --git a/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol b/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol index 772b0887a1..9a8cd676e2 100644 --- a/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol +++ b/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol @@ -159,8 +159,7 @@ contract MixinExchangeWrapper is uint256 protocolFee = tx.gasprice.safeMul(EXCHANGE.protocolFeeMultiplier()); bytes4 erc20BridgeProxyId = IAssetData(address(0)).ERC20Bridge.selector; - uint256 ordersLength = orders.length; - for (uint256 i = 0; i != ordersLength; i++) { + for (uint256 i = 0; i != orders.length; i++) { // Preemptively skip to avoid division by zero in _marketSellSingleOrder if (orders[i].makerAssetAmount == 0 || orders[i].takerAssetAmount == 0) { continue; @@ -198,7 +197,7 @@ contract MixinExchangeWrapper is ); } - _transferAssetToSender(orders[i].makerAssetData, makerAssetAcquiredAmount); + orders[i].makerAssetData.transferOut(makerAssetAcquiredAmount); totalWethSpentAmount = totalWethSpentAmount .safeAdd(wethSpentAmount); @@ -346,7 +345,7 @@ contract MixinExchangeWrapper is ); } - _transferAssetToSender(orders[i].makerAssetData, makerAssetAcquiredAmount); + orders[i].makerAssetData.transferOut(makerAssetAcquiredAmount); totalWethSpentAmount = totalWethSpentAmount .safeAdd(wethSpentAmount); diff --git a/contracts/exchange-forwarder/contracts/src/MixinReceiver.sol b/contracts/exchange-forwarder/contracts/src/MixinReceiver.sol new file mode 100644 index 0000000000..3ca5c1c407 --- /dev/null +++ b/contracts/exchange-forwarder/contracts/src/MixinReceiver.sol @@ -0,0 +1,76 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; + + +contract MixinReceiver { + + bytes4 constant public ERC1155_RECEIVED = 0xf23a6e61; + bytes4 constant public ERC1155_BATCH_RECEIVED = 0xbc197c81; + + /// @notice Handle the receipt of a single ERC1155 token type + /// @dev The smart contract calls this function on the recipient + /// after a `safeTransferFrom`. This function MAY throw to revert and reject the + /// transfer. Return of other than the magic value MUST result in the + ///transaction being reverted + /// Note: the contract address is always the message sender + /// @param operator The address which called `safeTransferFrom` function + /// @param from The address which previously owned the token + /// @param id An array containing the ids of the token being transferred + /// @param value An array containing the amount of tokens being transferred + /// @param data Additional data with no specified format + /// @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) + external + returns (bytes4) + { + return ERC1155_RECEIVED; + } + + /// @notice Handle the receipt of multiple ERC1155 token types + /// @dev The smart contract calls this function on the recipient + /// after a `safeTransferFrom`. This function MAY throw to revert and reject the + /// transfer. Return of other than the magic value MUST result in the + /// transaction being reverted + /// Note: the contract address is always the message sender + /// @param operator The address which called `safeTransferFrom` function + /// @param from The address which previously owned the token + /// @param ids An array containing ids of each token being transferred + /// @param values An array containing amounts of each token 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 values, + bytes calldata data + ) + external + returns (bytes4) + { + return ERC1155_BATCH_RECEIVED; + } +} \ No newline at end of file diff --git a/contracts/exchange-forwarder/contracts/src/libs/LibAssetDataTransfer.sol b/contracts/exchange-forwarder/contracts/src/libs/LibAssetDataTransfer.sol new file mode 100644 index 0000000000..a9d27bdaba --- /dev/null +++ b/contracts/exchange-forwarder/contracts/src/libs/LibAssetDataTransfer.sol @@ -0,0 +1,258 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.9; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/LibBytes.sol"; +import "@0x/contracts-utils/contracts/src/LibRichErrors.sol"; +import "@0x/contracts-utils/contracts/src/LibSafeMath.sol"; +import "@0x/contracts-erc20/contracts/src/LibERC20Token.sol"; +import "@0x/contracts-erc721/contracts/src/interfaces/IERC721Token.sol"; +import "@0x/contracts-erc1155/contracts/src/interfaces/IERC1155.sol"; +import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol"; +import "./LibForwarderRichErrors.sol"; + + +library LibAssetDataTransfer { + + using LibBytes for bytes; + using LibSafeMath for uint256; + using LibAssetDataTransfer for bytes; + + /// @dev Transfers given amount of asset to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer to sender. + function transferFrom( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal + { + if (amount == 0) { + return; + } + + bytes4 proxyId = assetData.readBytes4(0); + + if ( + proxyId == IAssetData(address(0)).ERC20Token.selector || + proxyId == IAssetData(address(0)).ERC20Bridge.selector + ) { + assetData._transferERC20Token( + from, + to, + amount + ); + } else if (proxyId == IAssetData(address(0)).ERC721Token.selector) { + assetData._transferERC721Token( + from, + to, + amount + ); + } else if (proxyId == IAssetData(address(0)).ERC1155Assets.selector) { + assetData._transferERC1155Assets( + from, + to, + amount + ); + } else if (proxyId == IAssetData(address(0)).MultiAsset.selector) { + assetData._transferMultiAsset( + from, + to, + amount + ); + } else if (proxyId != IAssetData(address(0)).StaticCall.selector) { + LibRichErrors.rrevert(LibForwarderRichErrors.UnsupportedAssetProxyError( + proxyId + )); + } + } + + ///@dev Transfer asset from sender to this contract. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param amount Amount of asset to transfer to sender. + function transferIn( + bytes memory assetData, + uint256 amount + ) + internal + { + assetData.transferFrom( + msg.sender, + address(this), + amount + ); + } + + ///@dev Transfer asset from this contract to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param amount Amount of asset to transfer to sender. + function transferOut( + bytes memory assetData, + uint256 amount + ) + internal + { + assetData.transferFrom( + address(this), + msg.sender, + amount + ); + } + + /// @dev Decodes ERC20 or ERC20Bridge assetData and transfers given amount to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer to sender. + function _transferERC20Token( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal + { + address token = assetData.readAddress(16); + // Transfer tokens. + if (from == address(this)) { + LibERC20Token.transfer( + token, + to, + amount + ); + } else { + LibERC20Token.transferFrom( + token, + from, + to, + amount + ); + } + } + + /// @dev Decodes ERC721 assetData and transfers given amount to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer to sender. + function _transferERC721Token( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal + { + if (amount != 1) { + LibRichErrors.rrevert(LibForwarderRichErrors.Erc721AmountMustEqualOneError( + amount + )); + } + // Decode asset data. + address token = assetData.readAddress(16); + uint256 tokenId = assetData.readUint256(36); + + // Perform transfer. + IERC721Token(token).transferFrom( + from, + to, + tokenId + ); + } + + /// @dev Decodes ERC1155 assetData and transfers given amounts to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer to sender. + function _transferERC1155Assets( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal + { + // Decode assetData + // solhint-disable + ( + address token, + uint256[] memory ids, + uint256[] memory values, + bytes memory data + ) = abi.decode( + assetData.slice(4, assetData.length), + (address, uint256[], uint256[], bytes) + ); + // solhint-enable + + // Scale up values by `amount` + uint256 length = values.length; + uint256[] memory scaledValues = new uint256[](length); + for (uint256 i = 0; i != length; i++) { + scaledValues[i] = values[i].safeMul(amount); + } + + // Execute `safeBatchTransferFrom` call + // Either succeeds or throws + IERC1155(token).safeBatchTransferFrom( + from, + to, + ids, + scaledValues, + data + ); + } + + /// @dev Decodes MultiAsset assetData and recursively transfers assets to sender. + /// @param assetData Byte array encoded for the respective asset proxy. + /// @param from Address to transfer asset from. + /// @param to Address to transfer asset to. + /// @param amount Amount of asset to transfer to sender. + function _transferMultiAsset( + bytes memory assetData, + address from, + address to, + uint256 amount + ) + internal + { + // solhint-disable indent + (uint256[] memory nestedAmounts, bytes[] memory nestedAssetData) = abi.decode( + assetData.slice(4, assetData.length), + (uint256[], bytes[]) + ); + // solhint-enable indent + + uint256 numNestedAssets = nestedAssetData.length; + for (uint256 i = 0; i != numNestedAssets; i++) { + transferFrom( + nestedAssetData[i], + from, + to, + amount.safeMul(nestedAmounts[i]) + ); + } + } +} diff --git a/contracts/exchange-forwarder/contracts/test/TestForwarder.sol b/contracts/exchange-forwarder/contracts/test/TestForwarder.sol index fcbfb44b03..232911c0f9 100644 --- a/contracts/exchange-forwarder/contracts/test/TestForwarder.sol +++ b/contracts/exchange-forwarder/contracts/test/TestForwarder.sol @@ -21,12 +21,17 @@ pragma experimental ABIEncoderV2; import "../src/MixinExchangeWrapper.sol"; import "../src/libs/LibConstants.sol"; +import "../src/libs/LibAssetDataTransfer.sol"; +import "../src/MixinReceiver.sol"; contract TestForwarder is LibConstants, - MixinExchangeWrapper + MixinExchangeWrapper, + MixinReceiver { + using LibAssetDataTransfer for bytes; + // solhint-disable no-empty-blocks constructor () public @@ -50,15 +55,12 @@ contract TestForwarder is ); } - function transferAssetToSender( + function transferOut( bytes memory assetData, uint256 amount ) public { - _transferAssetToSender( - assetData, - amount - ); + assetData.transferOut(amount); } }