Fix regression in DevUtils (#2449)

* fix bug in OrderTransferSimulationUtils causing failures for 721 assets

* Patched the regression and added tests

* Added regression test for fillable order

* Created a test for in and out of process ganache

* Split up DevUtils into two contracts

* Updated migration

* Remove the in and out of process ganache test

* Fixed contract addresses

* Appease linter

* Addressed review comments and updated artifacts, wrappers, and snapshots

* Fixed regression after refactor

* Update DevUtils and libTransactionDecoder contracts on mainnet and testnets

* Addressed @mzhu's review feedback

* Addressed @hysz's review feedback

* Updated devUtils address on testnets and mainnet after deployment

Co-authored-by: mzhu25 <mchl.zhu.96@gmail.com>
Co-authored-by: Fabio B <kandinsky454@protonmail.ch>
This commit is contained in:
James Towle 2020-01-21 18:54:10 -08:00 committed by Jacob Evans
parent 4e46bf4697
commit ce04d3ce41
19 changed files with 625 additions and 244 deletions

View File

@ -7,7 +7,7 @@
"evmVersion": "istanbul",
"optimizer": {
"enabled": true,
"runs": 200,
"runs": 5000,
"details": { "yul": true, "deduplicate": true, "cse": true, "constantOptimizer": true }
},
"outputSelection": {

View File

@ -26,14 +26,12 @@ import "@0x/contracts-utils/contracts/src/LibEIP712.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "./OrderValidationUtils.sol";
import "./OrderTransferSimulationUtils.sol";
import "./LibTransactionDecoder.sol";
import "./EthBalanceChecker.sol";
// solhint-disable no-empty-blocks
contract DevUtils is
OrderValidationUtils,
LibTransactionDecoder,
LibEIP712ExchangeDomain,
EthBalanceChecker
{

View File

@ -41,6 +41,10 @@ contract OrderTransferSimulationUtils is
TransfersSuccessful // All transfers in the order were successful
}
// NOTE(jalextowle): This is a random address that we use to avoid issues that addresses like `address(1)`
// may cause later.
address constant internal UNUSED_ADDRESS = address(0x377f698C4c287018D09b516F415317aEC5919332);
// keccak256(abi.encodeWithSignature("Error(string)", "TRANSFERS_SUCCESSFUL"));
bytes32 constant internal _TRANSFERS_SUCCESSFUL_RESULT_HASH = 0xf43f26ea5a94b478394a975e856464913dc1a8a1ca70939d974aa7c238aa0ce0;
@ -82,13 +86,13 @@ contract OrderTransferSimulationUtils is
// Transfer `makerAsset` from maker to taker
assetData[0] = order.makerAssetData;
fromAddresses[0] = order.makerAddress;
toAddresses[0] = takerAddress;
toAddresses[0] = takerAddress == address(0) ? UNUSED_ADDRESS : takerAddress;
amounts[0] = fillResults.makerAssetFilledAmount;
// Transfer `makerFeeAsset` from maker to feeRecipient
assetData[1] = order.makerFeeAssetData;
fromAddresses[1] = order.makerAddress;
toAddresses[1] = order.feeRecipientAddress;
toAddresses[1] = order.feeRecipientAddress == address(0) ? UNUSED_ADDRESS : order.feeRecipientAddress;
amounts[1] = fillResults.makerFeePaid;
return _simulateTransferFromCalls(
@ -134,19 +138,19 @@ contract OrderTransferSimulationUtils is
// Transfer `makerAsset` from maker to taker
assetData[1] = order.makerAssetData;
fromAddresses[1] = order.makerAddress;
toAddresses[1] = takerAddress;
toAddresses[1] = takerAddress == address(0) ? UNUSED_ADDRESS : takerAddress;
amounts[1] = fillResults.makerAssetFilledAmount;
// Transfer `takerFeeAsset` from taker to feeRecipient
assetData[2] = order.takerFeeAssetData;
fromAddresses[2] = takerAddress;
toAddresses[2] = order.feeRecipientAddress;
toAddresses[2] = order.feeRecipientAddress == address(0) ? UNUSED_ADDRESS : order.feeRecipientAddress;
amounts[2] = fillResults.takerFeePaid;
// Transfer `makerFeeAsset` from maker to feeRecipient
assetData[3] = order.makerFeeAssetData;
fromAddresses[3] = order.makerAddress;
toAddresses[3] = order.feeRecipientAddress;
toAddresses[3] = order.feeRecipientAddress == address(0) ? UNUSED_ADDRESS : order.feeRecipientAddress;
amounts[3] = fillResults.makerFeePaid;
return _simulateTransferFromCalls(

View File

@ -133,6 +133,18 @@ contract OrderValidationUtils is
fillableTakerAssetAmount
) == OrderTransferResults.TransfersSuccessful ? fillableTakerAssetAmount : 0;
if (!_isAssetDataValid(order.takerAssetData)) {
fillableTakerAssetAmount = 0;
}
if (order.takerFee != 0 && !_isAssetDataValid(order.takerFeeAssetData)) {
fillableTakerAssetAmount = 0;
}
if (orderInfo.orderStatus != LibOrder.OrderStatus.FILLABLE) {
fillableTakerAssetAmount = 0;
}
return (orderInfo, fillableTakerAssetAmount, isValidSignature);
}
@ -185,4 +197,63 @@ contract OrderValidationUtils is
transferableAssetAmount = LibSafeMath.min256(balance, allowance);
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 assetData The asset data that should be validated.
/// @return Whether or not the order should be considered valid.
function _isAssetDataValid(bytes memory assetData)
internal
pure
returns (bool)
{
// Asset data must be composed of an asset proxy Id and a bytes segment with
// a length divisible by 32.
if (assetData.length % 32 != 4) {
return false;
}
// Only process the taker asset data if it is multiAssetData.
bytes4 assetProxyId = assetData.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(assetData);
uint256 length = nestedAssetData.length;
for (uint256 i = 0; i != length; i++) {
// TODO(jalextowle): Implement similar validation for non-fungible ERC1155 asset data.
bytes4 nestedAssetProxyId = nestedAssetData[i].readBytes4(0);
if (nestedAssetProxyId == IAssetData(address(0)).ERC721Token.selector) {
if (_isAssetDataDuplicated(nestedAssetData, i)) {
return false;
}
}
}
return true;
}
/// Determines whether or not asset data is duplicated later in the nested asset data.
/// @param nestedAssetData The asset data to scan for duplication.
/// @param startIdx The index where the scan should begin.
/// @return A boolean reflecting whether or not the starting asset data was duplicated.
function _isAssetDataDuplicated(
bytes[] memory nestedAssetData,
uint256 startIdx
)
internal
pure
returns (bool)
{
uint256 length = nestedAssetData.length;
for (uint256 i = startIdx + 1; i < length; i++) {
if (nestedAssetData[startIdx].equals(nestedAssetData[i])) {
return true;
}
}
}
}

View File

@ -155,6 +155,19 @@ blockchainTests.resets('OrderValidationUtils/OrderTransferSimulatorUtils', env =
.callAsync();
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should correctly validate fillable order', async () => {
signedOrder = await maker.signOrderAsync({
makerAssetData: erc721AssetData,
makerAssetAmount: new BigNumber(1),
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
});
await taker.configureERC20TokenAsync(erc20Token2);
const [, fillableTakerAssetAmount] = await devUtils
.getOrderRelevantState(signedOrder, signedOrder.signature)
.callAsync();
expect(fillableTakerAssetAmount).to.bignumber.greaterThan(constants.ZERO_AMOUNT);
});
it('should return a fillableTakerAssetAmount of 0 when balances/allowances of one asset within a multiAssetData are insufficient (ERC20)', async () => {
const multiAssetData = await devUtils
.encodeMultiAssetData([new BigNumber(1), new BigNumber(1)], [erc20AssetData, erc20AssetData2])
@ -222,6 +235,34 @@ blockchainTests.resets('OrderValidationUtils/OrderTransferSimulatorUtils', env =
.callAsync();
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should return a fillableTakerAssetAmount of 0 when an erc721 asset is duplicated in the maker fee 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({
makerFeeAssetData: multiAssetData,
makerFee: new BigNumber(1),
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 fee 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({
makerFee: constants.ZERO_AMOUNT,
takerFeeAssetData: multiAssetData,
takerFee: new BigNumber(1),
});
const [, fillableTakerAssetAmount] = await devUtils
.getOrderRelevantState(signedOrder, signedOrder.signature)
.callAsync();
expect(fillableTakerAssetAmount).to.bignumber.equal(constants.ZERO_AMOUNT);
});
it('should return the correct fillableTakerAssetAmount when fee balances/allowances are partially sufficient', async () => {
await erc20Token.setBalance(maker.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync();
await erc20Token.approve(erc20Proxy.address, signedOrder.makerAssetAmount).awaitTransactionSuccessAsync({

View File

@ -196,7 +196,6 @@ export class DeploymentManager {
exchange,
staking.stakingProxy,
]);
const devUtils = await DevUtilsContract.deployFrom0xArtifactAsync(
devUtilsArtifacts.DevUtils,
environment.provider,

View File

@ -13,13 +13,14 @@
"dutchAuction": "0x0000000000000000000000000000000000000000",
"coordinatorRegistry": "0x45797531b873fd5e519477a070a955764c1a5b07",
"coordinator": "0x38a795580d0f687e399913a00ddef6a17612c722",
"libTransactionDecoder": "0x5f20e82643ce007d87692eb1b3d3fc059588b224",
"multiAssetProxy": "0xef701d5389ae74503d633396c4d654eabedc9d78",
"staticCallProxy": "0x3517b88c19508c08650616019062b898ab65ed29",
"erc1155Proxy": "0x7eefbd48fd63d441ec7435d024ec7c5131019add",
"zrxVault": "0xba7f8b5fb1b19c1211c5d49550fcd149177a5eaf",
"staking": "0x2a17c35ff147b32f13f19f2e311446eeb02503f3",
"stakingProxy": "0xa26e80e7dea86279c6d778d702cc413e6cffa777",
"devUtils": "0x5f53f2aa72cb3a9371bf3c58e8fb3a313478b2f4",
"devUtils": "0x161793cdca4ff9e766a706c2c49c36ac1340bbcd",
"erc20BridgeProxy": "0x8ed95d1746bf1e4dab58d8ed4724f1ef95b20db0",
"uniswapBridge": "0x533344cfdf2a3e911e2cf4c6f5ed08e791f5355f",
"erc20BridgeSampler": "0x25840bf3582cb9e5acabbf45148b3092ac3f6b56",
@ -42,10 +43,11 @@
"dutchAuction": "0x0000000000000000000000000000000000000000",
"coordinatorRegistry": "0x403cc23e88c17c4652fb904784d1af640a6722d9",
"coordinator": "0x6ff734d96104965c9c1b0108f83abc46e6e501df",
"libTransactionDecoder": "0xb20f3b07afb0e38b6151b9be4f53218bdd7dc231",
"multiAssetProxy": "0xab8fbd189c569ccdee3a4d929bb7f557be4028f6",
"staticCallProxy": "0xe1b97e47aa3796276033a5341e884d2ba46b6ac1",
"erc1155Proxy": "0x19bb6caa3bc34d39e5a23cedfa3e6c7e7f3c931d",
"devUtils": "0x09a379ef7218bcfd8913faa8b281ebc5a2e0bc04",
"devUtils": "0x161793cdca4ff9e766a706c2c49c36ac1340bbcd",
"zrxVault": "0xffd161026865ad8b4ab28a76840474935eec4dfa",
"staking": "0x986b588e472b712385579d172494fe2685669504",
"stakingProxy": "0xfaabcee42ab6b9c649794ac6c133711071897ee9",
@ -67,6 +69,7 @@
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
"zeroExGovernor": "0x3f46b98061a3e1e1f41dff296ec19402c298f8a9",
"forwarder": "0xd67f2f346f6e85db70632d9f18f50e04192ab54d",
"libTransactionDecoder": "0x34b37611db8190469b735fb2a007d8236c29eb88",
"orderValidator": "0x0000000000000000000000000000000000000000",
"dutchAuction": "0x0000000000000000000000000000000000000000",
"coordinatorRegistry": "0x1084b6a398e47907bae43fec3ff4b677db6e4fee",
@ -74,7 +77,7 @@
"multiAssetProxy": "0xb34cde0ad3a83d04abebc0b66e75196f22216621",
"staticCallProxy": "0xe1b97e47aa3796276033a5341e884d2ba46b6ac1",
"erc1155Proxy": "0x19bb6caa3bc34d39e5a23cedfa3e6c7e7f3c931d",
"devUtils": "0x09a379ef7218bcfd8913faa8b281ebc5a2e0bc04",
"devUtils": "0x161793cdca4ff9e766a706c2c49c36ac1340bbcd",
"zrxVault": "0xa5bf6ac73bc40790fc6ffc9dbbbce76c9176e224",
"staking": "0x7cbe3c09cba24f26db24d9100ee915fa9fa21f5b",
"stakingProxy": "0xc6ad5277ea225ac05e271eb14a7ebb480cd9dd9f",
@ -97,13 +100,14 @@
"zeroExGovernor": "0x6ff734d96104965c9c1b0108f83abc46e6e501df",
"forwarder": "0x2759a4c639fa4882d6d64973630ef81faf901d27",
"orderValidator": "0x0000000000000000000000000000000000000000",
"libTransactionDecoder": "0x067b5997c9058eade0bb03d8fb5e6db7feda80a3",
"dutchAuction": "0x0000000000000000000000000000000000000000",
"coordinatorRegistry": "0x09fb99968c016a3ff537bf58fb3d9fe55a7975d5",
"coordinator": "0xd29e59e51e8ab5f94121efaeebd935ca4214e257",
"multiAssetProxy": "0xf6313a772c222f51c28f2304c0703b8cf5428fd8",
"staticCallProxy": "0x48e94bdb9033640d45ea7c721e25f380f8bffa43",
"erc1155Proxy": "0x64517fa2b480ba3678a2a3c0cf08ef7fd4fad36f",
"devUtils": "0xeb27220f95f364e1d9531992c48613f231839f53",
"devUtils": "0x161793cdca4ff9e766a706c2c49c36ac1340bbcd",
"zrxVault": "0xf36eabdfe986b35b62c8fd5a98a7f2aebb79b291",
"staking": "0x32b06d5611a03737a5f1834a24ccd641033fd89c",
"stakingProxy": "0xbab9145f1d57cd4bb0c9aa2d1ece0a5b6e734d34",
@ -125,6 +129,7 @@
"assetProxyOwner": "0x0000000000000000000000000000000000000000",
"erc20BridgeProxy": "0x8ea76477cfaca8f7ea06477fd3c09a740ac6012a",
"zeroExGovernor": "0x0000000000000000000000000000000000000000",
"libTransactionDecoder": "0xb48e1b16829c7f5bd62b76cb878a6bb1c4625d7a",
"forwarder": "0x5d3ad3561a1235273cbcb4e82fce63a0073d19be",
"orderValidator": "0x0000000000000000000000000000000000000000",
"dutchAuction": "0x0000000000000000000000000000000000000000",

View File

@ -14,6 +14,7 @@ export interface ContractAddresses {
forwarder: string;
coordinatorRegistry: string;
coordinator: string;
libTransactionDecoder: string;
multiAssetProxy: string;
staticCallProxy: string;
erc1155Proxy: string;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,6 +15,7 @@ import * as Forwarder from '../artifacts/Forwarder.json';
import * as IAssetProxy from '../artifacts/IAssetProxy.json';
import * as IValidator from '../artifacts/IValidator.json';
import * as IWallet from '../artifacts/IWallet.json';
import * as LibTransactionDecoder from '../artifacts/LibTransactionDecoder.json';
import * as MultiAssetProxy from '../artifacts/MultiAssetProxy.json';
import * as Staking from '../artifacts/Staking.json';
import * as StakingProxy from '../artifacts/StakingProxy.json';
@ -42,6 +43,7 @@ export {
IAssetProxy,
IValidator,
IWallet,
LibTransactionDecoder,
MultiAssetProxy,
StaticCallProxy,
WETH9,

View File

@ -19,6 +19,7 @@
"./artifacts/ERC721Token.json",
"./artifacts/Exchange.json",
"./artifacts/Forwarder.json",
"./artifacts/LibTransactionDecoder.json",
"./artifacts/IAssetProxy.json",
"./artifacts/IValidator.json",
"./artifacts/IWallet.json",

View File

@ -31,7 +31,7 @@
"wrappers:generate": "abi-gen --abis ${npm_package_config_abis} --output src/generated-wrappers --backend ethers"
},
"config": {
"abis": "../contract-artifacts/artifacts/@(DevUtils|ERC20Token|ERC721Token|Exchange|Forwarder|IAssetData|WETH9|Coordinator|Staking|StakingProxy|IERC20BridgeSampler).json"
"abis": "../contract-artifacts/artifacts/@(DevUtils|ERC20Token|ERC721Token|Exchange|Forwarder|IAssetData|LibTransactionDecoder|WETH9|Coordinator|Staking|StakingProxy|IERC20BridgeSampler).json"
},
"repository": {
"type": "git",

View File

@ -11,6 +11,7 @@ import { ERC20TokenContract } from './generated-wrappers/erc20_token';
import { ERC721TokenContract } from './generated-wrappers/erc721_token';
import { ExchangeContract } from './generated-wrappers/exchange';
import { ForwarderContract } from './generated-wrappers/forwarder';
import { LibTransactionDecoderContract } from './generated-wrappers/lib_transaction_decoder';
import { StakingContract } from './generated-wrappers/staking';
import { WETH9Contract } from './generated-wrappers/weth9';
import { ContractWrappersConfig } from './types';
@ -46,9 +47,13 @@ export class ContractWrappers {
*/
public coordinator: CoordinatorContract;
/**
* An instance of the StakingContract class containing methods for interacting with the Coordinator extension contract.
* An instance of the StakingContract class containing methods for interacting with the Staking contracts.
*/
public staking: StakingContract;
/**
* An instance of the LibTransactionDecoder class containing methods for interacting with the LibTransactionDecoder smart contract.
*/
public libTransactionDecoder: LibTransactionDecoderContract;
private readonly _web3Wrapper: Web3Wrapper;
/**
@ -73,6 +78,7 @@ export class ContractWrappers {
ForwarderContract,
StakingContract,
WETH9Contract,
LibTransactionDecoderContract,
];
contractsArray.forEach(contract => {
this._web3Wrapper.abiDecoder.addABI(contract.ABI(), contract.contractName);
@ -87,6 +93,10 @@ export class ContractWrappers {
this.staking = new StakingContract(contractAddresses.stakingProxy, this.getProvider());
this.devUtils = new DevUtilsContract(contractAddresses.devUtils, this.getProvider());
this.coordinator = new CoordinatorContract(contractAddresses.coordinator, this.getProvider());
this.libTransactionDecoder = new LibTransactionDecoderContract(
contractAddresses.libTransactionDecoder,
this.getProvider(),
);
this.contractAddresses = contractAddresses;
}
/**

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,7 @@ export { ContractAddresses } from '@0x/contract-addresses';
export { ContractWrappers } from './contract_wrappers';
export { DevUtilsContract } from './generated-wrappers/dev_utils';
export { LibTransactionDecoderContract } from './generated-wrappers/lib_transaction_decoder';
export { IAssetDataContract } from './generated-wrappers/i_asset_data'; // used for synchronously encoding and decoding asset data
export {
ERC20TokenEventArgs,

View File

@ -10,7 +10,7 @@ import {
StaticCallProxyContract,
} from '@0x/contracts-asset-proxy';
import { CoordinatorContract, CoordinatorRegistryContract } from '@0x/contracts-coordinator';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { DevUtilsContract, LibTransactionDecoderContract } from '@0x/contracts-dev-utils';
import { ERC1155MintableContract } from '@0x/contracts-erc1155';
import { DummyERC20TokenContract, WETH9Contract } from '@0x/contracts-erc20';
import { DummyERC721TokenContract } from '@0x/contracts-erc721';
@ -254,6 +254,14 @@ export async function runMigrationsAsync(
etherToken.address,
);
// LibTransactionDecoder
const libTransactionDecoder = await LibTransactionDecoderContract.deployFrom0xArtifactAsync(
artifacts.LibTransactionDecoder,
provider,
txDefaults,
artifacts,
);
const contractAddresses = {
erc20Proxy: erc20Proxy.address,
erc721Proxy: erc721Proxy.address,
@ -265,6 +273,7 @@ export async function runMigrationsAsync(
erc20BridgeProxy: erc20BridgeProxy.address,
zeroExGovernor: constants.NULL_ADDRESS,
forwarder: forwarder.address,
libTransactionDecoder: libTransactionDecoder.address,
orderValidator: constants.NULL_ADDRESS,
dutchAuction: constants.NULL_ADDRESS,
coordinatorRegistry: coordinatorRegistry.address,

View File

@ -9,7 +9,11 @@ import {
UniswapBridgeContract,
} from '@0x/contracts-asset-proxy';
import { artifacts as coordinatorArtifacts, CoordinatorContract } from '@0x/contracts-coordinator';
import { artifacts as devUtilsArtifacts, DevUtilsContract } from '@0x/contracts-dev-utils';
import {
artifacts as devUtilsArtifacts,
DevUtilsContract,
LibTransactionDecoderContract,
} from '@0x/contracts-dev-utils';
import { artifacts as exchangeArtifacts, ExchangeContract } from '@0x/contracts-exchange';
import { artifacts as forwarderArtifacts, ForwarderContract } from '@0x/contracts-exchange-forwarder';
import {
@ -272,6 +276,13 @@ export async function runMigrationsAsync(supportedProvider: SupportedProvider, t
deployedAddresses.exchangeV2,
deployedAddresses.etherToken,
);
await LibTransactionDecoderContract.deployFrom0xArtifactAsync(
devUtilsArtifacts.LibTransactionDecoder,
provider,
txDefaults,
devUtilsArtifacts,
);
}
(async () => {