diff --git a/contracts/exchange-forwarder/contracts/src/MixinAssets.sol b/contracts/exchange-forwarder/contracts/src/MixinAssets.sol index 9b6a896d13..cfea0598bc 100644 --- a/contracts/exchange-forwarder/contracts/src/MixinAssets.sol +++ b/contracts/exchange-forwarder/contracts/src/MixinAssets.sol @@ -20,6 +20,7 @@ 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"; @@ -35,6 +36,7 @@ contract MixinAssets is IAssets { using LibBytes for bytes; + using LibSafeMath for uint256; /// @dev Withdraws assets from this contract. It may be used by the owner to withdraw assets /// that were accidentally sent to this contract. @@ -95,7 +97,9 @@ contract MixinAssets is _transferERC20Token(assetData, amount); } else if (proxyId == IAssetData(address(0)).ERC721Token.selector) { _transferERC721Token(assetData, amount); - } else { + } else if (proxyId == IAssetData(address(0)).MultiAsset.selector) { + _transferMultiAsset(assetData, amount); + } else if (proxyId != IAssetData(address(0)).StaticCall.selector) { LibRichErrors.rrevert(LibForwarderRichErrors.UnsupportedAssetProxyError( proxyId )); @@ -141,4 +145,23 @@ contract MixinAssets is tokenId ); } + + 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 e05a407ad3..0312c6b617 100644 --- a/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol +++ b/contracts/exchange-forwarder/contracts/src/MixinExchangeWrapper.sol @@ -88,9 +88,10 @@ contract MixinExchangeWrapper is uint256 makerAssetAcquiredAmount ) { + bool noTakerFee = _noTakerFee(order.takerFee, order.takerFeeAssetData); // No taker fee or percentage fee if ( - order.takerFee == 0 || + noTakerFee || _areUnderlyingAssetsEqual(order.takerFeeAssetData, order.makerAssetData) ) { // Attempt to sell the remaining amount of WETH @@ -105,7 +106,7 @@ contract MixinExchangeWrapper is // Subtract fee from makerAssetFilledAmount for the net amount acquired. makerAssetAcquiredAmount = singleFillResults.makerAssetFilledAmount - .safeSub(singleFillResults.takerFeePaid); + .safeSub(noTakerFee ? 0 : singleFillResults.takerFeePaid); // WETH fee } else if (_areUnderlyingAssetsEqual(order.takerFeeAssetData, order.takerAssetData)) { @@ -230,9 +231,10 @@ contract MixinExchangeWrapper is uint256 makerAssetAcquiredAmount ) { + bool noTakerFee = _noTakerFee(order.takerFee, order.takerFeeAssetData); // No taker fee or WETH fee if ( - order.takerFee == 0 || + noTakerFee || _areUnderlyingAssetsEqual(order.takerFeeAssetData, order.takerAssetData) ) { // Calculate the remaining amount of takerAsset to sell @@ -251,7 +253,7 @@ contract MixinExchangeWrapper is // WETH is also spent on the protocol and taker fees, so we add it here. wethSpentAmount = singleFillResults.takerAssetFilledAmount - .safeAdd(singleFillResults.takerFeePaid) + .safeAdd(noTakerFee ? 0 : singleFillResults.takerFeePaid) .safeAdd(singleFillResults.protocolFeePaid); makerAssetAcquiredAmount = singleFillResults.makerAssetFilledAmount; @@ -419,7 +421,7 @@ contract MixinExchangeWrapper is return fillResults; } - /// @dev Fills the input ExchangeV3 order. + /// @dev Fills the input ExchangeV3 order. /// Returns false if the transaction would otherwise revert. /// @param order Order struct containing order specifications. /// @param takerAssetFillAmount Desired amount of takerAsset to sell. @@ -480,7 +482,7 @@ contract MixinExchangeWrapper is address token2 = assetData2.readAddress(16); return (token1 == token2); } else { - return false; + return assetData1.equals(assetData2); } } @@ -494,4 +496,24 @@ contract MixinExchangeWrapper is { return order.makerFeeAssetData.length > 3 && order.makerFeeAssetData.readBytes4(0) == EXCHANGE_V2_ORDER_ID; } + + /// @dev Checks whether one asset is effectively equal to another asset. + /// This is the case if they have the same ERC20Proxy/ERC20BridgeProxy asset data, or if + /// one is the ERC20Bridge equivalent of the other. + /// @param takerFee Byte array encoded for the takerFee asset proxy. + /// @param takerFeeAssetData Byte array encoded for the maker asset proxy. + /// @return Whether or not the underlying assets are equal. + function _noTakerFee( + uint256 takerFee, + bytes memory takerFeeAssetData + ) + internal + pure + returns (bool) + { + return ( + takerFee == 0 || + takerFeeAssetData.readBytes4(0) == IAssetData(address(0)).StaticCall.selector + ); + } }