Combine mixins

This commit is contained in:
Amir Bandeali
2018-07-06 15:08:14 -07:00
parent 1c80bba4dd
commit 462f1f00d8
6 changed files with 218 additions and 284 deletions

View File

@@ -19,9 +19,8 @@
pragma solidity 0.4.24;
pragma experimental ABIEncoderV2;
import "./MixinWethFees.sol";
import "./MixinMarketSellTokens.sol";
import "./MixinMarketBuyTokens.sol";
import "./MixinFees.sol";
import "./MixinForwarderCore.sol";
import "./MixinConstants.sol";
import "../utils/Ownable/Ownable.sol";
@@ -29,10 +28,9 @@ import "../utils/Ownable/Ownable.sol";
contract Forwarder is
Ownable,
MixinConstants,
MixinWethFees,
MixinFees,
MixinMarketBuyZrx,
MixinMarketBuyTokens,
MixinMarketSellTokens
MixinForwarderCore
{
uint256 constant internal MAX_UINT = 2**256 - 1;

View File

@@ -1,66 +0,0 @@
/*
Copyright 2018 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.4.24;
pragma experimental ABIEncoderV2;
import "../utils/LibBytes/LibBytes.sol";
import "../tokens/ERC721Token/IERC721Token.sol";
contract MixinERC721 {
using LibBytes for bytes;
bytes4 constant internal ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,uint256,bytes)"));
bytes4 constant internal ERC721_RECEIVED_OPERATOR = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
function onERC721Received(address, uint256, bytes memory)
public
pure
returns(bytes4)
{
return ERC721_RECEIVED;
}
function onERC721Received(address, address, uint256, bytes memory)
public
pure
returns(bytes4)
{
return ERC721_RECEIVED_OPERATOR;
}
function transferERC721Token(
bytes memory assetData,
address to
)
internal
{
// Decode asset data.
address token = assetData.readAddress(16);
uint256 tokenId = assetData.readUint256(36);
bytes memory receiverData = assetData.readBytesWithLength(100);
IERC721Token(token).safeTransferFrom(
address(this),
to,
tokenId,
receiverData
);
}
}

View File

@@ -22,14 +22,14 @@ import "../protocol/Exchange/libs/LibMath.sol";
import "./MixinConstants.sol";
contract MixinWethFees is
contract MixinFees is
LibMath,
MixinConstants
{
uint16 constant public PERCENTAGE_DENOMINATOR = 10000; // 9800 == 98%, 10000 == 100%
uint16 constant public MAX_FEE = 1000; // 10%
uint16 constant public ALLOWABLE_EXCHANGE_PERCENTAGE = 9500; // 95%
uint16 constant public PERCENTAGE_DENOMINATOR = 10000; // 9800 == 98%, 10000 == 100%
uint16 constant public MAX_FEE = 1000; // 10%
uint16 constant public ALLOWABLE_EXCHANGE_PERCENTAGE = 9500; // 95%
/// @dev Pays the feeRecipient feeProportion of the total takerEthAmount, denominated in ETH
/// @param takerEthAmount The total amount that was transacted in WETH, fees are calculated from this value.

View File

@@ -20,26 +20,104 @@ pragma solidity 0.4.24;
pragma experimental ABIEncoderV2;
import "../utils/LibBytes/LibBytes.sol";
import "./MixinWethFees.sol";
import "./MixinFees.sol";
import "./MixinMarketBuyZrx.sol";
import "./MixinExpectedResults.sol";
import "./MixinERC20.sol";
import "./MixinERC721.sol";
import "./MixinTransfer.sol";
import "./MixinConstants.sol";
import "../protocol/Exchange/libs/LibOrder.sol";
contract MixinMarketBuyTokens is
contract MixinForwarderCore is
MixinConstants,
MixinWethFees,
MixinMarketBuyZrx,
MixinExpectedResults,
MixinERC20,
MixinERC721
MixinFees,
MixinMarketBuyZrx,
MixinTransfer
{
bytes4 public constant ERC20_DATA_ID = bytes4(keccak256("ERC20Token(address)"));
bytes4 public constant ERC721_DATA_ID = bytes4(keccak256("ERC721Token(address,uint256,bytes)"));
/// @dev Market sells ETH for ERC20 tokens, performing fee abstraction if required. This does not support ERC721 tokens. This function is payable
/// and will convert all incoming ETH into WETH and perform the trade on behalf of the caller.
/// This function allows for a deduction of a proportion of incoming ETH sent to the feeRecipient.
/// The caller is sent all tokens from the operation.
/// If the purchased token amount does not meet an acceptable threshold then this function reverts.
/// @param orders An array of Order struct containing order specifications.
/// @param signatures An array of Proof that order has been created by maker.
/// @param feeOrders An array of Order struct containing order specifications for fees.
/// @param feeSignatures An array of Proof that order has been created by maker for the fee orders.
/// @param feeProportion A proportion deducted off the incoming ETH and sent to feeRecipient. The maximum value for this
/// is 1000, aka 10%. Supports up to 2 decimal places. I.e 0.59% is 59.
/// @param feeRecipient An address of the fee recipient whom receives feeProportion of ETH.
/// @return FillResults amounts filled and fees paid by maker and taker.
function marketSellEthForERC20(
LibOrder.Order[] memory orders,
bytes[] memory signatures,
LibOrder.Order[] memory feeOrders,
bytes[] memory feeSignatures,
uint16 feeProportion,
address feeRecipient
)
public
payable
returns (FillResults memory totalFillResults)
{
uint256 takerEthAmount = msg.value;
require(
takerEthAmount > 0,
"VALUE_GREATER_THAN_ZERO"
);
// Deduct the fee from the total amount of ETH sent in
uint256 ethFeeAmount = payEthFee(
takerEthAmount,
feeProportion,
feeRecipient
);
uint256 wethSellAmount = safeSub(takerEthAmount, ethFeeAmount);
// Deposit the remaining to be used for trading
ETHER_TOKEN.deposit.value(wethSellAmount)();
// Populate the known assetData, as it is always WETH the caller can provide null bytes to save gas
// marketSellOrders fills the remaining
address makerTokenAddress = LibBytes.readAddress(orders[0].makerAssetData, 16);
orders[0].takerAssetData = WETH_ASSET_DATA;
if (makerTokenAddress == address(ZRX_TOKEN)) {
// If this is ZRX then we market sell from the orders, rather than a 2 step of buying ZRX fees from feeOrders
// then buying ZRX from orders
totalFillResults = marketSellEthForZRXInternal(
orders,
signatures,
wethSellAmount
);
} else {
totalFillResults = marketSellEthForERC20Internal(
orders,
signatures,
feeOrders,
feeSignatures,
wethSellAmount
);
}
// Prevent accidental WETH owned by this contract and it being spent
require(
takerEthAmount >= totalFillResults.takerAssetFilledAmount,
"INVALID_MSG_VALUE"
);
// Ensure no WETH is left in this contract
require(
wethSellAmount == totalFillResults.takerAssetFilledAmount,
"UNACCEPTABLE_THRESHOLD"
);
// Transfer all tokens to msg.sender
transferERC20Token(
makerTokenAddress,
msg.sender,
totalFillResults.makerAssetFilledAmount
);
return totalFillResults;
}
/// @dev Buys the exact amount of assets (ERC20 and ERC721), performing fee abstraction if required.
/// All order assets must be of the same type. Deducts a proportional fee to fee recipient.
/// This function is payable and will convert all incoming ETH into WETH and perform the trade on behalf of the caller.
@@ -113,6 +191,85 @@ contract MixinMarketBuyTokens is
return totalFillResults;
}
/// @dev Market sells WETH for ERC20 tokens.
/// @param orders An array of Order struct containing order specifications.
/// @param signatures An array of Proof that order has been created by maker.
/// @param feeOrders An array of Order struct containing order specifications for fees.
/// @param feeSignatures An array of Proof that order has been created by maker for the fee orders.
/// @param wethSellAmount The amount of WETH to sell.
/// @return FillResults amounts filled and fees paid by maker and taker.
function marketSellEthForERC20Internal(
LibOrder.Order[] memory orders,
bytes[] memory signatures,
LibOrder.Order[] memory feeOrders,
bytes[] memory feeSignatures,
uint256 wethSellAmount
)
internal
returns (FillResults memory totalFillResults)
{
uint256 remainingWethSellAmount = wethSellAmount;
FillResults memory calculatedMarketSellResults = calculateMarketSellResults(orders, wethSellAmount);
if (calculatedMarketSellResults.takerFeePaid > 0) {
// Fees are required for these orders. Buy enough ZRX to cover the future market buy
FillResults memory feeTokensResults = marketBuyZrxInternal(
feeOrders,
feeSignatures,
calculatedMarketSellResults.takerFeePaid
);
// Ensure the token abstraction was fair if fees were proportionally too high, we fail
require(
isAcceptableThreshold(
wethSellAmount,
safeSub(wethSellAmount, feeTokensResults.takerAssetFilledAmount)
),
"UNACCEPTABLE_THRESHOLD"
);
remainingWethSellAmount = safeSub(remainingWethSellAmount, feeTokensResults.takerAssetFilledAmount);
totalFillResults.takerFeePaid = feeTokensResults.takerFeePaid;
totalFillResults.takerAssetFilledAmount = feeTokensResults.takerAssetFilledAmount;
}
// Make our market sell to buy the requested tokens with the remaining balance
FillResults memory requestedTokensResults = EXCHANGE.marketSellOrders(
orders,
remainingWethSellAmount,
signatures
);
// Update our return FillResult with the market sell
addFillResults(totalFillResults, requestedTokensResults);
return totalFillResults;
}
/// @dev Market sells WETH for ZRX tokens.
/// @param orders An array of Order struct containing order specifications.
/// @param signatures An array of Proof that order has been created by maker.
/// @param wethSellAmount The amount of WETH to sell.
/// @return FillResults amounts filled and fees paid by maker and taker.
function marketSellEthForZRXInternal(
LibOrder.Order[] memory orders,
bytes[] memory signatures,
uint256 wethSellAmount
)
internal
returns (FillResults memory totalFillResults)
{
// Make our market sell to buy the requested tokens with the remaining balance
totalFillResults = EXCHANGE.marketSellOrders(
orders,
wethSellAmount,
signatures
);
// Exchange does not special case ZRX in the makerAssetFilledAmount, if fees were deducted then using this amount
// for future transfers is invalid.
uint256 zrxAmountBought = safeSub(totalFillResults.makerAssetFilledAmount, totalFillResults.takerFeePaid);
require(
isAcceptableThreshold(totalFillResults.makerAssetFilledAmount, zrxAmountBought),
"UNACCEPTABLE_THRESHOLD"
);
totalFillResults.makerAssetFilledAmount = zrxAmountBought;
return totalFillResults;
}
/// @dev Buys an exact amount of an ERC20 token using WETH.
/// @param orders Orders to fill. The maker asset is the ERC20 token to buy. The taker asset is WETH.
/// @param signatures Proof that the orders were created by their respective makers.
@@ -189,7 +346,7 @@ contract MixinMarketBuyTokens is
);
}
// Transfer all purchased tokens to msg.sender
transferToken(
transferERC20Token(
makerTokenAddress,
msg.sender,
marketBuyResults.makerAssetFilledAmount

View File

@@ -1,197 +0,0 @@
/*
Copyright 2018 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.4.24;
pragma experimental ABIEncoderV2;
import "../protocol/Exchange/libs/LibOrder.sol";
import "../utils/LibBytes/LibBytes.sol";
import "./MixinWethFees.sol";
import "./MixinExpectedResults.sol";
import "./MixinERC20.sol";
import "./MixinConstants.sol";
import "./MixinMarketBuyZrx.sol";
contract MixinMarketSellTokens is
MixinConstants,
MixinWethFees,
MixinMarketBuyZrx,
MixinExpectedResults,
MixinERC20
{
/// @dev Market sells ETH for ERC20 tokens, performing fee abstraction if required. This does not support ERC721 tokens. This function is payable
/// and will convert all incoming ETH into WETH and perform the trade on behalf of the caller.
/// This function allows for a deduction of a proportion of incoming ETH sent to the feeRecipient.
/// The caller is sent all tokens from the operation.
/// If the purchased token amount does not meet an acceptable threshold then this function reverts.
/// @param orders An array of Order struct containing order specifications.
/// @param signatures An array of Proof that order has been created by maker.
/// @param feeOrders An array of Order struct containing order specifications for fees.
/// @param feeSignatures An array of Proof that order has been created by maker for the fee orders.
/// @param feeProportion A proportion deducted off the incoming ETH and sent to feeRecipient. The maximum value for this
/// is 1000, aka 10%. Supports up to 2 decimal places. I.e 0.59% is 59.
/// @param feeRecipient An address of the fee recipient whom receives feeProportion of ETH.
/// @return FillResults amounts filled and fees paid by maker and taker.
function marketSellEthForERC20(
LibOrder.Order[] memory orders,
bytes[] memory signatures,
LibOrder.Order[] memory feeOrders,
bytes[] memory feeSignatures,
uint16 feeProportion,
address feeRecipient
)
public
payable
returns (FillResults memory totalFillResults)
{
uint256 takerEthAmount = msg.value;
require(
takerEthAmount > 0,
"VALUE_GREATER_THAN_ZERO"
);
// Deduct the fee from the total amount of ETH sent in
uint256 ethFeeAmount = payEthFee(
takerEthAmount,
feeProportion,
feeRecipient
);
uint256 wethSellAmount = safeSub(takerEthAmount, ethFeeAmount);
// Deposit the remaining to be used for trading
ETHER_TOKEN.deposit.value(wethSellAmount)();
// Populate the known assetData, as it is always WETH the caller can provide null bytes to save gas
// marketSellOrders fills the remaining
address makerTokenAddress = LibBytes.readAddress(orders[0].makerAssetData, 16);
orders[0].takerAssetData = WETH_ASSET_DATA;
if (makerTokenAddress == address(ZRX_TOKEN)) {
// If this is ZRX then we market sell from the orders, rather than a 2 step of buying ZRX fees from feeOrders
// then buying ZRX from orders
totalFillResults = marketSellEthForZRXInternal(
orders,
signatures,
wethSellAmount
);
} else {
totalFillResults = marketSellEthForERC20Internal(
orders,
signatures,
feeOrders,
feeSignatures,
wethSellAmount
);
}
// Prevent accidental WETH owned by this contract and it being spent
require(
takerEthAmount >= totalFillResults.takerAssetFilledAmount,
"INVALID_MSG_VALUE"
);
// Ensure no WETH is left in this contract
require(
wethSellAmount == totalFillResults.takerAssetFilledAmount,
"UNACCEPTABLE_THRESHOLD"
);
// Transfer all tokens to msg.sender
transferToken(
makerTokenAddress,
msg.sender,
totalFillResults.makerAssetFilledAmount
);
return totalFillResults;
}
/// @dev Market sells WETH for ERC20 tokens.
/// @param orders An array of Order struct containing order specifications.
/// @param signatures An array of Proof that order has been created by maker.
/// @param feeOrders An array of Order struct containing order specifications for fees.
/// @param feeSignatures An array of Proof that order has been created by maker for the fee orders.
/// @param wethSellAmount The amount of WETH to sell.
/// @return FillResults amounts filled and fees paid by maker and taker.
function marketSellEthForERC20Internal(
LibOrder.Order[] memory orders,
bytes[] memory signatures,
LibOrder.Order[] memory feeOrders,
bytes[] memory feeSignatures,
uint256 wethSellAmount
)
internal
returns (FillResults memory totalFillResults)
{
uint256 remainingWethSellAmount = wethSellAmount;
FillResults memory calculatedMarketSellResults = calculateMarketSellResults(orders, wethSellAmount);
if (calculatedMarketSellResults.takerFeePaid > 0) {
// Fees are required for these orders. Buy enough ZRX to cover the future market buy
FillResults memory feeTokensResults = marketBuyZrxInternal(
feeOrders,
feeSignatures,
calculatedMarketSellResults.takerFeePaid
);
// Ensure the token abstraction was fair if fees were proportionally too high, we fail
require(
isAcceptableThreshold(
wethSellAmount,
safeSub(wethSellAmount, feeTokensResults.takerAssetFilledAmount)
),
"UNACCEPTABLE_THRESHOLD"
);
remainingWethSellAmount = safeSub(remainingWethSellAmount, feeTokensResults.takerAssetFilledAmount);
totalFillResults.takerFeePaid = feeTokensResults.takerFeePaid;
totalFillResults.takerAssetFilledAmount = feeTokensResults.takerAssetFilledAmount;
}
// Make our market sell to buy the requested tokens with the remaining balance
FillResults memory requestedTokensResults = EXCHANGE.marketSellOrders(
orders,
remainingWethSellAmount,
signatures
);
// Update our return FillResult with the market sell
addFillResults(totalFillResults, requestedTokensResults);
return totalFillResults;
}
/// @dev Market sells WETH for ZRX tokens.
/// @param orders An array of Order struct containing order specifications.
/// @param signatures An array of Proof that order has been created by maker.
/// @param wethSellAmount The amount of WETH to sell.
/// @return FillResults amounts filled and fees paid by maker and taker.
function marketSellEthForZRXInternal(
LibOrder.Order[] memory orders,
bytes[] memory signatures,
uint256 wethSellAmount
)
internal
returns (FillResults memory totalFillResults)
{
// Make our market sell to buy the requested tokens with the remaining balance
totalFillResults = EXCHANGE.marketSellOrders(
orders,
wethSellAmount,
signatures
);
// Exchange does not special case ZRX in the makerAssetFilledAmount, if fees were deducted then using this amount
// for future transfers is invalid.
uint256 zrxAmountBought = safeSub(totalFillResults.makerAssetFilledAmount, totalFillResults.takerFeePaid);
require(
isAcceptableThreshold(totalFillResults.makerAssetFilledAmount, zrxAmountBought),
"UNACCEPTABLE_THRESHOLD"
);
totalFillResults.makerAssetFilledAmount = zrxAmountBought;
return totalFillResults;
}
}

View File

@@ -17,13 +17,37 @@
*/
pragma solidity 0.4.24;
pragma experimental ABIEncoderV2;
import "../utils/LibBytes/LibBytes.sol";
import "../tokens/ERC721Token/IERC721Token.sol";
contract MixinERC20 {
contract MixinTransfer {
using LibBytes for bytes;
bytes4 constant internal ERC20_TRANSFER_SELECTOR = bytes4(keccak256("transfer(address,uint256)"));
bytes4 constant internal ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,uint256,bytes)"));
bytes4 constant internal ERC721_RECEIVED_OPERATOR = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
function transferToken(
function onERC721Received(address, uint256, bytes memory)
public
pure
returns(bytes4)
{
return ERC721_RECEIVED;
}
function onERC721Received(address, address, uint256, bytes memory)
public
pure
returns(bytes4)
{
return ERC721_RECEIVED_OPERATOR;
}
function transferERC20Token(
address token,
address to,
uint256 amount
@@ -64,4 +88,22 @@ contract MixinERC20 {
"TRANSFER_FAILED"
);
}
function transferERC721Token(
bytes memory assetData,
address to
)
internal
{
// Decode asset data.
address token = assetData.readAddress(16);
uint256 tokenId = assetData.readUint256(36);
bytes memory receiverData = assetData.readBytesWithLength(100);
IERC721Token(token).safeTransferFrom(
address(this),
to,
tokenId,
receiverData
);
}
}