Split out transfer logic into library, add 1155 support

This commit is contained in:
Amir
2020-01-12 22:25:06 -08:00
parent 7172432084
commit 3ca2f8ac9e
6 changed files with 351 additions and 108 deletions

View File

@@ -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,

View File

@@ -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]));
}
}
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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])
);
}
}
}

View File

@@ -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);
}
}