Fixed the bug and added tests that fail without the patch

This commit is contained in:
Alex Towle 2019-12-17 18:52:38 -08:00
parent 47c3ed9705
commit 2949db5f49
4 changed files with 81 additions and 61 deletions

View File

@ -31,8 +31,6 @@ import "@0x/contracts-utils/contracts/src/LibBytes.sol";
contract OrderTransferSimulationUtils is contract OrderTransferSimulationUtils is
LibExchangeRichErrorDecoder LibExchangeRichErrorDecoder
{ {
bool second;
using LibBytes for bytes; using LibBytes for bytes;
enum OrderTransferResults { enum OrderTransferResults {
@ -69,12 +67,11 @@ contract OrderTransferSimulationUtils is
public public
returns (OrderTransferResults orderTransferResults) returns (OrderTransferResults orderTransferResults)
{ {
LibFillResults.FillResults memory fillResults = LibFillResults.calculateFillResultsTrace( LibFillResults.FillResults memory fillResults = LibFillResults.calculateFillResults(
order, order,
takerAssetFillAmount, takerAssetFillAmount,
_EXCHANGE.protocolFeeMultiplier(), _EXCHANGE.protocolFeeMultiplier(),
tx.gasprice, tx.gasprice
second
); );
bytes[] memory assetData = new bytes[](2); bytes[] memory assetData = new bytes[](2);

View File

@ -16,7 +16,7 @@
*/ */
pragma solidity ^0.5.5; pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2; pragma experimental ABIEncoderV2;
import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol"; import "@0x/contracts-exchange/contracts/src/interfaces/IExchange.sol";
@ -127,6 +127,11 @@ contract OrderValidationUtils is
fillableTakerAssetAmount fillableTakerAssetAmount
) == OrderTransferResults.TransfersSuccessful ? fillableTakerAssetAmount : 0; ) == OrderTransferResults.TransfersSuccessful ? fillableTakerAssetAmount : 0;
/*
// Perform taker asset validation
fillableTakerAssetAmount = _isTakerAssetDataValid(order) ? fillableTakerAssetAmount : 0;
*/
return (orderInfo, fillableTakerAssetAmount, isValidSignature); return (orderInfo, fillableTakerAssetAmount, isValidSignature);
} }
@ -154,10 +159,6 @@ contract OrderValidationUtils is
isValidSignature = new bool[](length); isValidSignature = new bool[](length);
for (uint256 i = 0; i != length; i++) { for (uint256 i = 0; i != length; i++) {
if (i == 1) {
second = true;
}
(ordersInfo[i], fillableTakerAssetAmounts[i], isValidSignature[i]) = getOrderRelevantState( (ordersInfo[i], fillableTakerAssetAmounts[i], isValidSignature[i]) = getOrderRelevantState(
orders[i], orders[i],
signatures[i] signatures[i]
@ -184,4 +185,49 @@ contract OrderValidationUtils is
transferableAssetAmount = LibSafeMath.min256(balance, allowance); transferableAssetAmount = LibSafeMath.min256(balance, allowance);
return transferableAssetAmount; return transferableAssetAmount;
} }
/// @dev This function handles the edge cases around taker validation. This function
/// currently attempts to find duplicate ERC721 token's in the taker
/// multiAssetData.
/// @param order The order that should be validated.
/// @return Whether or not the order should be considered valid.
function _isTakerAssetDataValid(LibOrder.Order memory order)
internal
pure
returns (bool)
{
bytes memory takerAssetData = order.takerAssetData;
// Only process the taker asset data if it is multiAssetData.
bytes4 assetProxyId = takerAssetData.readBytes4(0);
if (assetProxyId != IAssetData(address(0)).MultiAsset.selector) {
return true;
}
// Get array of values and array of assetDatas
(, uint256[] memory assetAmounts, bytes[] memory nestedAssetData) = decodeMultiAssetData(takerAssetData);
uint256 length = nestedAssetData.length;
for (uint256 i = 0; i != length; i++) {
// NOTE(jalextowle): As an optimization, we will break out of this function
// as soon as it is determined that there are no possible ERC721 duplicates.
bool hasSeenERC721 = false;
bytes4 nestedAssetProxyId = nestedAssetData[i].readBytes4(0);
if (nestedAssetProxyId == IAssetData(address(0)).ERC721Token.selector) {
hasSeenERC721 = true;
for (uint256 j = i; j != length; j++) {
if (nestedAssetData[i].equals(nestedAssetData[j])) {
return false;
}
}
}
if (!hasSeenERC721) {
break;
}
}
return true;
}
} }

View File

@ -49,51 +49,6 @@ library LibFillResults {
uint256 profitInRightMakerAsset; // Profit taken from the right maker uint256 profitInRightMakerAsset; // Profit taken from the right maker
} }
event TestEvent(string file_name, string value_name, uint256 value);
/// FIXME: Remove this function after investigation
/// @dev Calculates amounts filled and fees paid by maker and taker.
/// @param order to be filled.
/// @param takerAssetFilledAmount Amount of takerAsset that will be filled.
/// @param protocolFeeMultiplier The current protocol fee of the exchange contract.
/// @param gasPrice The gasprice of the transaction. This is provided so that the function call can continue
/// to be pure rather than view.
/// @return fillResults Amounts filled and fees paid by maker and taker.
function calculateFillResultsTrace(
LibOrder.Order memory order,
uint256 takerAssetFilledAmount,
uint256 protocolFeeMultiplier,
uint256 gasPrice,
bool shouldTrace
)
internal
pure
returns (FillResults memory fillResults)
{
// Compute proportional transfer amounts
fillResults.takerAssetFilledAmount = takerAssetFilledAmount;
fillResults.makerAssetFilledAmount = LibMath.safeGetPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerAssetAmount
);
fillResults.makerFeePaid = LibMath.safeGetPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.makerFee
);
fillResults.takerFeePaid = LibMath.safeGetPartialAmountFloor(
takerAssetFilledAmount,
order.takerAssetAmount,
order.takerFee
);
// Compute the protocol fee that should be paid for a single fill.
fillResults.protocolFeePaid = gasPrice.safeMul(protocolFeeMultiplier);
return fillResults;
}
/// @dev Calculates amounts filled and fees paid by maker and taker. /// @dev Calculates amounts filled and fees paid by maker and taker.
/// @param order to be filled. /// @param order to be filled.
/// @param takerAssetFilledAmount Amount of takerAsset that will be filled. /// @param takerAssetFilledAmount Amount of takerAsset that will be filled.

View File

@ -22,6 +22,7 @@ blockchainTests.resets.only('OrderValidationUtils/OrderTransferSimulatorUtils',
let feeErc20Token: DummyERC20TokenContract; let feeErc20Token: DummyERC20TokenContract;
let erc20Proxy: ERC20ProxyContract; let erc20Proxy: ERC20ProxyContract;
let exchange: ExchangeContract; let exchange: ExchangeContract;
let deployment: DeploymentManager;
let erc20AssetData: string; let erc20AssetData: string;
let erc20AssetData2: string; let erc20AssetData2: string;
@ -33,7 +34,7 @@ blockchainTests.resets.only('OrderValidationUtils/OrderTransferSimulatorUtils',
before(async () => { before(async () => {
[takerAddress, owner] = await env.getAccountAddressesAsync(); [takerAddress, owner] = await env.getAccountAddressesAsync();
const deployment = await DeploymentManager.deployAsync(env, { deployment = await DeploymentManager.deployAsync(env, {
numErc20TokensToDeploy: 3, numErc20TokensToDeploy: 3,
numErc721TokensToDeploy: 1, numErc721TokensToDeploy: 1,
owner, owner,
@ -60,9 +61,6 @@ blockchainTests.resets.only('OrderValidationUtils/OrderTransferSimulatorUtils',
feeRecipientAddress: constants.NULL_ADDRESS, feeRecipientAddress: constants.NULL_ADDRESS,
}, },
}); });
/*
await Promise.all(deployment.tokens.erc20.map(async token => maker.configureERC20TokenAsync(token)));
*/
const [tokenID] = await maker.configureERC721TokenAsync(deployment.tokens.erc721[0]); const [tokenID] = await maker.configureERC721TokenAsync(deployment.tokens.erc721[0]);
erc721AssetData = assetDataUtils.encodeERC721AssetData(deployment.tokens.erc721[0].address, tokenID); erc721AssetData = assetDataUtils.encodeERC721AssetData(deployment.tokens.erc721[0].address, tokenID);
}); });
@ -165,11 +163,35 @@ blockchainTests.resets.only('OrderValidationUtils/OrderTransferSimulatorUtils',
.callAsync(); .callAsync();
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT); expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
}); });
it('should return a fillableTakerAssetAmount of 0 when an erc721 asset is duplicated in a multi-asset proxy order', async () => { it('should return a fillableTakerAssetAmount of 0 when an erc721 asset is duplicated in the maker side of a multi-asset proxy order', async () => {
const multiAssetData = await devUtils const multiAssetData = await devUtils
.encodeMultiAssetData([new BigNumber(1), new BigNumber(1)], [erc721AssetData, erc721AssetData]) .encodeMultiAssetData([new BigNumber(1), new BigNumber(1)], [erc721AssetData, erc721AssetData])
.callAsync(); .callAsync();
signedOrder = await maker.signOrderAsync({ makerAssetData: multiAssetData }); signedOrder = await maker.signOrderAsync({
makerAssetData: multiAssetData,
makerAssetAmount: new BigNumber(1),
takerAssetData: erc721AssetData,
takerAssetAmount: new BigNumber(1),
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
});
const [, fillableTakerAssetAmount] = await devUtils
.getOrderRelevantState(signedOrder, signedOrder.signature)
.callAsync();
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should return a fillableTakerAssetAmount of 0 when an erc721 asset is duplicated in the taker side of a multi-asset proxy order', async () => {
const multiAssetData = await devUtils
.encodeMultiAssetData([new BigNumber(1), new BigNumber(1)], [erc721AssetData, erc721AssetData])
.callAsync();
signedOrder = await maker.signOrderAsync({
makerAssetData: erc721AssetData,
makerAssetAmount: new BigNumber(1),
takerAssetData: multiAssetData,
takerAssetAmount: new BigNumber(1),
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
});
const [, fillableTakerAssetAmount] = await devUtils const [, fillableTakerAssetAmount] = await devUtils
.getOrderRelevantState(signedOrder, signedOrder.signature) .getOrderRelevantState(signedOrder, signedOrder.signature)
.callAsync(); .callAsync();