diff options
Diffstat (limited to 'packages/contracts')
28 files changed, 2139 insertions, 638 deletions
diff --git a/packages/contracts/src/abstract/abstract_asset_wrapper.ts b/packages/contracts/src/abstract/abstract_asset_wrapper.ts new file mode 100644 index 000000000..0a0b42eea --- /dev/null +++ b/packages/contracts/src/abstract/abstract_asset_wrapper.ts @@ -0,0 +1,3 @@ +export abstract class AbstractAssetWrapper { + public abstract getProxyId(): number; +} diff --git a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol index 5503eb2f2..78ea96447 100644 --- a/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol +++ b/packages/contracts/src/contracts/current/test/DummyERC721Token/DummyERC721Token.sol @@ -56,4 +56,20 @@ contract DummyERC721Token is ); _mint(to, tokenId); } + + /** + * @dev Function to burn a token + * @dev Reverts if the given token ID doesn't exist + * @param tokenId uint256 ID of the token to be minted by the msg.sender + */ + function burn(address owner, uint256 tokenId) + public + onlyOwner + { + require( + exists(tokenId), + "Token with tokenId does not exist." + ); + _burn(owner, tokenId); + } } diff --git a/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol b/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol index d35815474..8b52858b1 100644 --- a/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol +++ b/packages/contracts/src/contracts/current/test/Whitelist/Whitelist.sol @@ -23,13 +23,13 @@ import "../../protocol/Exchange/interfaces/IExchange.sol"; import "../../protocol/Exchange/libs/LibOrder.sol"; import "../../utils/Ownable/Ownable.sol"; -contract Whitelist is +contract Whitelist is Ownable { // Revert reasons - string constant MAKER_NOT_WHITELISTED = "Maker address not whitelisted."; - string constant TAKER_NOT_WHITELISTED = "Taker address not whitelisted."; - string constant INVALID_SENDER = "Sender must equal transaction origin."; + string constant MAKER_NOT_WHITELISTED = "MAKER_NOT_WHITELISTED"; // Maker address not whitelisted. + string constant TAKER_NOT_WHITELISTED = "TAKER_NOT_WHITELISTED"; // Taker address not whitelisted. + string constant INVALID_SENDER = "INVALID_SENDER"; // Sender must equal transaction origin. // Mapping of address => whitelist status. mapping (address => bool) public isWhitelisted; @@ -77,7 +77,7 @@ contract Whitelist is public { address takerAddress = msg.sender; - + // This contract must be the entry point for the transaction. require( takerAddress == tx.origin, diff --git a/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol b/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol index f0bcdafef..b6961a6ec 100644 --- a/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol +++ b/packages/contracts/src/contracts/current/tokens/ERC20Token/ERC20Token.sol @@ -23,8 +23,8 @@ import "./IERC20Token.sol"; contract ERC20Token is IERC20Token { - string constant INSUFFICIENT_BALANCE = "Insufficient balance to complete transfer."; - string constant INSUFFICIENT_ALLOWANCE = "Insufficient allowance to complete transfer."; + string constant INSUFFICIENT_BALANCE = "ERC20_INSUFFICIENT_BALANCE"; + string constant INSUFFICIENT_ALLOWANCE = "ERC20_INSUFFICIENT_ALLOWANCE"; string constant OVERFLOW = "Transfer would result in an overflow."; mapping (address => uint256) balances; @@ -97,4 +97,3 @@ contract ERC20Token is IERC20Token { return allowed[_owner][_spender]; } } - diff --git a/packages/contracts/src/utils/assertions.ts b/packages/contracts/src/utils/assertions.ts index 615e648f3..29489e648 100644 --- a/packages/contracts/src/utils/assertions.ts +++ b/packages/contracts/src/utils/assertions.ts @@ -52,6 +52,18 @@ export function expectRevertOrAlwaysFailingTransactionAsync<T>(p: Promise<T>): P } /** + * Rejects if the given Promise does not reject with the given revert reason or "always + * failing transaction" error. + * @param p the Promise which is expected to reject + * @param reason a specific revert reason + * @returns a new Promise which will reject if the conditions are not met and + * otherwise resolve with no value. + */ +export function expectRevertReasonOrAlwaysFailingTransactionAsync<T>(p: Promise<T>, reason: string): PromiseLike<void> { + return _expectEitherErrorAsync(p, 'always failing transaction', reason); +} + +/** * Rejects if the given Promise does not reject with a "revert" or "Contract * call failed" error. * @param p the Promise which is expected to reject diff --git a/packages/contracts/src/utils/asset_wrapper.ts b/packages/contracts/src/utils/asset_wrapper.ts new file mode 100644 index 000000000..d5ce8cf1e --- /dev/null +++ b/packages/contracts/src/utils/asset_wrapper.ts @@ -0,0 +1,216 @@ +import { assetProxyUtils } from '@0xproject/order-utils'; +import { BigNumber, errorUtils } from '@0xproject/utils'; +import * as _ from 'lodash'; + +import { AbstractAssetWrapper } from '../abstract/abstract_asset_wrapper'; + +import { constants } from './constants'; +import { ERC20Wrapper } from './erc20_wrapper'; +import { ERC721Wrapper } from './erc721_wrapper'; + +interface ProxyIdToAssetWrappers { + [proxyId: number]: AbstractAssetWrapper; +} + +/** + * This class abstracts away the differences between ERC20 and ERC721 tokens so that + * the logic that uses it does not need to care what standard a token belongs to. + */ +export class AssetWrapper { + private _proxyIdToAssetWrappers: ProxyIdToAssetWrappers; + constructor(assetWrappers: AbstractAssetWrapper[]) { + this._proxyIdToAssetWrappers = {}; + _.each(assetWrappers, assetWrapper => { + const proxyId = assetWrapper.getProxyId(); + this._proxyIdToAssetWrappers[proxyId] = assetWrapper; + }); + } + public async getBalanceAsync(userAddress: string, assetData: string): Promise<BigNumber> { + const proxyId = assetProxyUtils.decodeAssetDataId(assetData); + switch (proxyId) { + case constants.ERC20_PROXY_ID: { + const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; + const balance = await erc20Wrapper.getBalanceAsync(userAddress, assetData); + return balance; + } + case constants.ERC721_PROXY_ID: { + const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; + const assetProxyData = assetProxyUtils.decodeERC721AssetData(assetData); + const isOwner = await assetWrapper.isOwnerAsync( + userAddress, + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + const balance = isOwner ? new BigNumber(1) : new BigNumber(0); + return balance; + } + default: + throw errorUtils.spawnSwitchErr('proxyId', proxyId); + } + } + public async setBalanceAsync(userAddress: string, assetData: string, desiredBalance: BigNumber): Promise<void> { + const proxyId = assetProxyUtils.decodeAssetDataId(assetData); + switch (proxyId) { + case constants.ERC20_PROXY_ID: { + const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; + await erc20Wrapper.setBalanceAsync(userAddress, assetData, desiredBalance); + return; + } + case constants.ERC721_PROXY_ID: { + if (!desiredBalance.eq(0) && !desiredBalance.eq(1)) { + throw new Error(`Balance for ERC721 token can only be set to 0 or 1. Got: ${desiredBalance}`); + } + const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; + const assetProxyData = assetProxyUtils.decodeERC721AssetData(assetData); + const doesTokenExist = erc721Wrapper.doesTokenExistAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + if (!doesTokenExist && desiredBalance.eq(1)) { + await erc721Wrapper.mintAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress); + return; + } else if (!doesTokenExist && desiredBalance.eq(0)) { + return; // noop + } + const tokenOwner = await erc721Wrapper.ownerOfAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + if (userAddress !== tokenOwner && desiredBalance.eq(1)) { + await erc721Wrapper.transferFromAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + tokenOwner, + userAddress, + ); + } else if (tokenOwner === userAddress && desiredBalance.eq(0)) { + // Burn token + await erc721Wrapper.burnAsync(assetProxyData.tokenAddress, assetProxyData.tokenId, userAddress); + return; + } else if ( + (userAddress !== tokenOwner && desiredBalance.eq(0)) || + (tokenOwner === userAddress && desiredBalance.eq(1)) + ) { + return; // noop + } + break; + } + default: + throw errorUtils.spawnSwitchErr('proxyId', proxyId); + } + } + public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise<BigNumber> { + const proxyId = assetProxyUtils.decodeAssetDataId(assetData); + switch (proxyId) { + case constants.ERC20_PROXY_ID: { + const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; + const allowance = await erc20Wrapper.getProxyAllowanceAsync(userAddress, assetData); + return allowance; + } + case constants.ERC721_PROXY_ID: { + const assetWrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; + const erc721ProxyData = assetProxyUtils.decodeERC721AssetData(assetData); + const isProxyApprovedForAll = await assetWrapper.isProxyApprovedForAllAsync( + userAddress, + erc721ProxyData.tokenAddress, + ); + if (isProxyApprovedForAll) { + return constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; + } + + const isProxyApproved = await assetWrapper.isProxyApprovedAsync( + erc721ProxyData.tokenAddress, + erc721ProxyData.tokenId, + ); + const allowance = isProxyApproved ? new BigNumber(1) : new BigNumber(0); + return allowance; + } + default: + throw errorUtils.spawnSwitchErr('proxyId', proxyId); + } + } + public async setProxyAllowanceAsync( + userAddress: string, + assetData: string, + desiredAllowance: BigNumber, + ): Promise<void> { + const proxyId = assetProxyUtils.decodeAssetDataId(assetData); + switch (proxyId) { + case constants.ERC20_PROXY_ID: { + const erc20Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC20Wrapper; + await erc20Wrapper.setAllowanceAsync(userAddress, assetData, desiredAllowance); + return; + } + case constants.ERC721_PROXY_ID: { + if ( + !desiredAllowance.eq(0) && + !desiredAllowance.eq(1) && + !desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS) + ) { + throw new Error( + `Allowance for ERC721 token can only be set to 0, 1 or 2^256-1. Got: ${desiredAllowance}`, + ); + } + const erc721Wrapper = this._proxyIdToAssetWrappers[proxyId] as ERC721Wrapper; + const assetProxyData = assetProxyUtils.decodeERC721AssetData(assetData); + + const doesTokenExist = await erc721Wrapper.doesTokenExistAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + if (!doesTokenExist) { + throw new Error( + `Cannot setProxyAllowance on non-existent token: ${assetProxyData.tokenAddress} ${ + assetProxyData.tokenId + }`, + ); + } + const isProxyApprovedForAll = await erc721Wrapper.isProxyApprovedForAllAsync( + userAddress, + assetProxyData.tokenAddress, + ); + if (!isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { + const isApproved = true; + await erc721Wrapper.approveProxyForAllAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + isApproved, + ); + } else if (isProxyApprovedForAll && desiredAllowance.eq(0)) { + const isApproved = false; + await erc721Wrapper.approveProxyForAllAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + isApproved, + ); + } else if (isProxyApprovedForAll && desiredAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { + return; // Noop + } + + const isProxyApproved = await erc721Wrapper.isProxyApprovedAsync( + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + if (!isProxyApproved && desiredAllowance.eq(1)) { + await erc721Wrapper.approveProxyAsync(assetProxyData.tokenAddress, assetProxyData.tokenId); + } else if (isProxyApproved && desiredAllowance.eq(0)) { + // Remove approval + await erc721Wrapper.approveAsync( + constants.NULL_ADDRESS, + assetProxyData.tokenAddress, + assetProxyData.tokenId, + ); + } else if ( + (!isProxyApproved && desiredAllowance.eq(0)) || + (isProxyApproved && desiredAllowance.eq(1)) + ) { + return; // noop + } + + break; + } + default: + throw errorUtils.spawnSwitchErr('proxyId', proxyId); + } + } +} diff --git a/packages/contracts/src/utils/constants.ts b/packages/contracts/src/utils/constants.ts index f21b8c7a0..1e7b731dc 100644 --- a/packages/contracts/src/utils/constants.ts +++ b/packages/contracts/src/utils/constants.ts @@ -19,19 +19,8 @@ const TESTRPC_PRIVATE_KEYS_STRINGS = [ export const constants = { INVALID_OPCODE: 'invalid opcode', REVERT: 'revert', - LIB_BYTES_GREATER_THAN_ZERO_LENGTH_REQUIRED: 'GREATER_THAN_ZERO_LENGTH_REQUIRED', - LIB_BYTES_GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED', - LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED', - LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED', - LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED', - LIB_BYTES_GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED: 'GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED', - ERC20_INSUFFICIENT_BALANCE: 'Insufficient balance to complete transfer.', - ERC20_INSUFFICIENT_ALLOWANCE: 'Insufficient allowance to complete transfer.', - EXCHANGE_LENGTH_GREATER_THAN_0_REQUIRED: 'LENGTH_GREATER_THAN_0_REQUIRED', - EXCHANGE_SIGNATURE_UNSUPPORTED: 'SIGNATURE_UNSUPPORTED', - EXCHANGE_SIGNATURE_ILLEGAL: 'SIGNATURE_ILLEGAL', - EXCHANGE_LENGTH_0_REQUIRED: 'LENGTH_0_REQUIRED', - EXCHANGE_LENGTH_65_REQUIRED: 'LENGTH_65_REQUIRED', + ERC20_PROXY_ID: 1, + ERC721_PROXY_ID: 2, TESTRPC_NETWORK_ID: 50, // Note(albrow): In practice V8 and most other engines limit the minimum // interval for setInterval to 10ms. We still set it to 0 here in order to diff --git a/packages/contracts/src/utils/core_combinatorial_utils.ts b/packages/contracts/src/utils/core_combinatorial_utils.ts new file mode 100644 index 000000000..a2d2a500b --- /dev/null +++ b/packages/contracts/src/utils/core_combinatorial_utils.ts @@ -0,0 +1,800 @@ +import { + assetProxyUtils, + BalanceAndProxyAllowanceLazyStore, + ExchangeTransferSimulator, + orderHashUtils, + OrderStateUtils, + OrderValidationUtils, +} from '@0xproject/order-utils'; +import { AssetProxyId, SignatureType, SignedOrder } from '@0xproject/types'; +import { BigNumber, errorUtils, logUtils } from '@0xproject/utils'; +import { Web3Wrapper } from '@0xproject/web3-wrapper'; +import * as chai from 'chai'; +import { LogWithDecodedArgs, Provider, TxData } from 'ethereum-types'; +import * as _ from 'lodash'; +import 'make-promises-safe'; + +import { ExchangeContract, FillContractEventArgs } from '../generated_contract_wrappers/exchange'; +import { artifacts } from '../utils/artifacts'; +import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../utils/assertions'; +import { AssetWrapper } from '../utils/asset_wrapper'; +import { chaiSetup } from '../utils/chai_setup'; +import { constants } from '../utils/constants'; +import { ERC20Wrapper } from '../utils/erc20_wrapper'; +import { ERC721Wrapper } from '../utils/erc721_wrapper'; +import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { OrderFactoryFromScenario } from '../utils/order_factory_from_scenario'; +import { orderUtils } from '../utils/order_utils'; +import { signingUtils } from '../utils/signing_utils'; +import { SimpleAssetBalanceAndProxyAllowanceFetcher } from '../utils/simple_asset_balance_and_proxy_allowance_fetcher'; +import { SimpleOrderFilledCancelledFetcher } from '../utils/simple_order_filled_cancelled_fetcher'; +import { + AllowanceAmountScenario, + AssetDataScenario, + BalanceAmountScenario, + ExpirationTimeSecondsScenario, + FeeRecipientAddressScenario, + FillScenario, + OrderAssetAmountScenario, + TakerAssetFillAmountScenario, + TakerScenario, + TraderStateScenario, +} from '../utils/types'; + +chaiSetup.configure(); +const expect = chai.expect; + +/** + * Instantiates a new instance of CoreCombinatorialUtils. Since this method has some + * required async setup, a factory method is required. + * @param web3Wrapper Web3Wrapper instance + * @param txDefaults Default Ethereum tx options + * @return CoreCombinatorialUtils instance + */ +export async function coreCombinatorialUtilsFactoryAsync( + web3Wrapper: Web3Wrapper, + txDefaults: Partial<TxData>, +): Promise<CoreCombinatorialUtils> { + const userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + const [ownerAddress, makerAddress, takerAddress] = userAddresses; + const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)]; + + const provider = web3Wrapper.getProvider(); + const erc20Wrapper = new ERC20Wrapper(provider, userAddresses, ownerAddress); + const erc721Wrapper = new ERC721Wrapper(provider, userAddresses, ownerAddress); + + const erc20EighteenDecimalTokenCount = 3; + const eighteenDecimals = new BigNumber(18); + const [ + erc20EighteenDecimalTokenA, + erc20EighteenDecimalTokenB, + zrxToken, + ] = await erc20Wrapper.deployDummyTokensAsync(erc20EighteenDecimalTokenCount, eighteenDecimals); + const zrxAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); + + const erc20FiveDecimalTokenCount = 2; + const fiveDecimals = new BigNumber(18); + const [erc20FiveDecimalTokenA, erc20FiveDecimalTokenB] = await erc20Wrapper.deployDummyTokensAsync( + erc20FiveDecimalTokenCount, + fiveDecimals, + ); + const erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + const [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + const erc721Proxy = await erc721Wrapper.deployProxyAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + + const assetWrapper = new AssetWrapper([erc20Wrapper, erc721Wrapper]); + + const exchangeContract = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + const exchangeWrapper = new ExchangeWrapper(exchangeContract, provider); + await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC20, erc20Proxy.address, ownerAddress); + await exchangeWrapper.registerAssetProxyAsync(AssetProxyId.ERC721, erc721Proxy.address, ownerAddress); + + await web3Wrapper.awaitTransactionSuccessAsync( + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, { + from: ownerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + await web3Wrapper.awaitTransactionSuccessAsync( + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeContract.address, { + from: ownerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + + const orderFactory = new OrderFactoryFromScenario( + userAddresses, + zrxToken.address, + [erc20EighteenDecimalTokenA.address, erc20EighteenDecimalTokenB.address], + [erc20FiveDecimalTokenA.address, erc20FiveDecimalTokenB.address], + erc721Token, + erc721Balances, + exchangeContract.address, + ); + + const coreCombinatorialUtils = new CoreCombinatorialUtils( + orderFactory, + ownerAddress, + makerAddress, + makerPrivateKey, + takerAddress, + zrxAssetData, + exchangeWrapper, + assetWrapper, + ); + return coreCombinatorialUtils; +} + +export class CoreCombinatorialUtils { + public orderFactory: OrderFactoryFromScenario; + public ownerAddress: string; + public makerAddress: string; + public makerPrivateKey: Buffer; + public takerAddress: string; + public zrxAssetData: string; + public exchangeWrapper: ExchangeWrapper; + public assetWrapper: AssetWrapper; + public static generateFillOrderCombinations(): FillScenario[] { + const takerScenarios = [TakerScenario.Unspecified]; + const feeRecipientScenarios = [FeeRecipientAddressScenario.EthUserAddress]; + const makerAssetAmountScenario = [OrderAssetAmountScenario.Large]; + const takerAssetAmountScenario = [OrderAssetAmountScenario.Large]; + const makerFeeScenario = [OrderAssetAmountScenario.Large]; + const takerFeeScenario = [OrderAssetAmountScenario.Large]; + const expirationTimeSecondsScenario = [ExpirationTimeSecondsScenario.InFuture]; + const makerAssetDataScenario = [ + AssetDataScenario.ERC20FiveDecimals, + AssetDataScenario.ERC20NonZRXEighteenDecimals, + AssetDataScenario.ERC721, + AssetDataScenario.ZRXFeeToken, + ]; + const takerAssetDataScenario = [ + AssetDataScenario.ERC20FiveDecimals, + AssetDataScenario.ERC20NonZRXEighteenDecimals, + AssetDataScenario.ERC721, + AssetDataScenario.ZRXFeeToken, + ]; + const takerAssetFillAmountScenario = [TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount]; + const fillScenarioArrays = CoreCombinatorialUtils._getAllCombinations([ + takerScenarios, + feeRecipientScenarios, + makerAssetAmountScenario, + takerAssetAmountScenario, + makerFeeScenario, + takerFeeScenario, + expirationTimeSecondsScenario, + makerAssetDataScenario, + takerAssetDataScenario, + takerAssetFillAmountScenario, + ]); + + const fillScenarios = _.map(fillScenarioArrays, fillScenarioArray => { + const fillScenario: FillScenario = { + orderScenario: { + takerScenario: fillScenarioArray[0] as TakerScenario, + feeRecipientScenario: fillScenarioArray[1] as FeeRecipientAddressScenario, + makerAssetAmountScenario: fillScenarioArray[2] as OrderAssetAmountScenario, + takerAssetAmountScenario: fillScenarioArray[3] as OrderAssetAmountScenario, + makerFeeScenario: fillScenarioArray[4] as OrderAssetAmountScenario, + takerFeeScenario: fillScenarioArray[5] as OrderAssetAmountScenario, + expirationTimeSecondsScenario: fillScenarioArray[6] as ExpirationTimeSecondsScenario, + makerAssetDataScenario: fillScenarioArray[7] as AssetDataScenario, + takerAssetDataScenario: fillScenarioArray[8] as AssetDataScenario, + }, + takerAssetFillAmountScenario: fillScenarioArray[9] as TakerAssetFillAmountScenario, + makerStateScenario: { + traderAssetBalance: BalanceAmountScenario.Higher, + traderAssetAllowance: AllowanceAmountScenario.Higher, + zrxFeeBalance: BalanceAmountScenario.Higher, + zrxFeeAllowance: AllowanceAmountScenario.Higher, + }, + takerStateScenario: { + traderAssetBalance: BalanceAmountScenario.Higher, + traderAssetAllowance: AllowanceAmountScenario.Higher, + zrxFeeBalance: BalanceAmountScenario.Higher, + zrxFeeAllowance: AllowanceAmountScenario.Higher, + }, + }; + return fillScenario; + }); + + return fillScenarios; + } + /** + * Recursive implementation of generating all combinations of the supplied + * string-containing arrays. + */ + private static _getAllCombinations(arrays: string[][]): string[][] { + // Base case + if (arrays.length === 1) { + const remainingValues = _.map(arrays[0], val => { + return [val]; + }); + return remainingValues; + } else { + const result = []; + const restOfArrays = arrays.slice(1); + const allCombinationsOfRemaining = CoreCombinatorialUtils._getAllCombinations(restOfArrays); // recur with the rest of array + // tslint:disable:prefer-for-of + for (let i = 0; i < allCombinationsOfRemaining.length; i++) { + for (let j = 0; j < arrays[0].length; j++) { + result.push([arrays[0][j], ...allCombinationsOfRemaining[i]]); + } + } + // tslint:enable:prefer-for-of + return result; + } + } + constructor( + orderFactory: OrderFactoryFromScenario, + ownerAddress: string, + makerAddress: string, + makerPrivateKey: Buffer, + takerAddress: string, + zrxAssetData: string, + exchangeWrapper: ExchangeWrapper, + assetWrapper: AssetWrapper, + ) { + this.orderFactory = orderFactory; + this.ownerAddress = ownerAddress; + this.makerAddress = makerAddress; + this.makerPrivateKey = makerPrivateKey; + this.takerAddress = takerAddress; + this.zrxAssetData = zrxAssetData; + this.exchangeWrapper = exchangeWrapper; + this.assetWrapper = assetWrapper; + } + public async testFillOrderScenarioAsync( + provider: Provider, + fillScenario: FillScenario, + isVerbose: boolean = false, + ): Promise<void> { + // 1. Generate order + const order = this.orderFactory.generateOrder(fillScenario.orderScenario); + + // 2. Sign order + const orderHashBuff = orderHashUtils.getOrderHashBuffer(order); + const signature = signingUtils.signMessage(orderHashBuff, this.makerPrivateKey, SignatureType.EthSign); + const signedOrder = { + ...order, + signature: `0x${signature.toString('hex')}`, + }; + + const balanceAndProxyAllowanceFetcher = new SimpleAssetBalanceAndProxyAllowanceFetcher(this.assetWrapper); + const orderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher( + this.exchangeWrapper, + this.zrxAssetData, + ); + + // 3. Figure out fill amount + const takerAssetFillAmount = await this._getTakerAssetFillAmountAsync( + signedOrder, + fillScenario.takerAssetFillAmountScenario, + balanceAndProxyAllowanceFetcher, + orderFilledCancelledFetcher, + ); + + // 4. Permutate the maker and taker balance/allowance scenarios + await this._modifyTraderStateAsync( + fillScenario.makerStateScenario, + fillScenario.takerStateScenario, + signedOrder, + takerAssetFillAmount, + ); + + // 5. If I fill it by X, what are the resulting balances/allowances/filled amounts expected? + const orderValidationUtils = new OrderValidationUtils(orderFilledCancelledFetcher); + const lazyStore = new BalanceAndProxyAllowanceLazyStore(balanceAndProxyAllowanceFetcher); + const exchangeTransferSimulator = new ExchangeTransferSimulator(lazyStore); + + let fillRevertReasonIfExists; + try { + await orderValidationUtils.validateFillOrderThrowIfInvalidAsync( + exchangeTransferSimulator, + provider, + signedOrder, + takerAssetFillAmount, + this.takerAddress, + this.zrxAssetData, + ); + if (isVerbose) { + logUtils.log(`Expecting fillOrder to succeed.`); + } + } catch (err) { + fillRevertReasonIfExists = err.message; + if (isVerbose) { + logUtils.log(`Expecting fillOrder to fail with:`); + logUtils.log(err); + } + } + + // 6. Fill the order + await this._fillOrderAndAssertOutcomeAsync( + signedOrder, + takerAssetFillAmount, + lazyStore, + fillRevertReasonIfExists, + ); + } + private async _fillOrderAndAssertOutcomeAsync( + signedOrder: SignedOrder, + takerAssetFillAmount: BigNumber, + lazyStore: BalanceAndProxyAllowanceLazyStore, + fillRevertReasonIfExists: string | undefined, + ): Promise<void> { + if (!_.isUndefined(fillRevertReasonIfExists)) { + return expectRevertReasonOrAlwaysFailingTransactionAsync( + this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { takerAssetFillAmount }), + fillRevertReasonIfExists, + ); + } + + const makerAddress = signedOrder.makerAddress; + const makerAssetData = signedOrder.makerAssetData; + const takerAssetData = signedOrder.takerAssetData; + const feeRecipient = signedOrder.feeRecipientAddress; + + const expMakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(makerAssetData, makerAddress); + const expMakerAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(makerAssetData, makerAddress); + const expTakerAssetBalanceOfMaker = await lazyStore.getBalanceAsync(takerAssetData, makerAddress); + const expZRXAssetBalanceOfMaker = await lazyStore.getBalanceAsync(this.zrxAssetData, makerAddress); + const expZRXAssetAllowanceOfMaker = await lazyStore.getProxyAllowanceAsync(this.zrxAssetData, makerAddress); + const expTakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(takerAssetData, this.takerAddress); + const expTakerAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync(takerAssetData, this.takerAddress); + const expMakerAssetBalanceOfTaker = await lazyStore.getBalanceAsync(makerAssetData, this.takerAddress); + const expZRXAssetBalanceOfTaker = await lazyStore.getBalanceAsync(this.zrxAssetData, this.takerAddress); + const expZRXAssetAllowanceOfTaker = await lazyStore.getProxyAllowanceAsync( + this.zrxAssetData, + this.takerAddress, + ); + const expZRXAssetBalanceOfFeeRecipient = await lazyStore.getBalanceAsync(this.zrxAssetData, feeRecipient); + + const orderHash = orderHashUtils.getOrderHashHex(signedOrder); + const alreadyFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash); + const remainingTakerAmountToFill = signedOrder.takerAssetAmount.minus(alreadyFilledTakerAmount); + const expFilledTakerAmount = takerAssetFillAmount.gt(remainingTakerAmountToFill) + ? remainingTakerAmountToFill + : alreadyFilledTakerAmount.add(takerAssetFillAmount); + + const expFilledMakerAmount = orderUtils.getPartialAmount( + expFilledTakerAmount, + signedOrder.takerAssetAmount, + signedOrder.makerAssetAmount, + ); + + // - Let's fill the order! + const txReceipt = await this.exchangeWrapper.fillOrderAsync(signedOrder, this.takerAddress, { + takerAssetFillAmount, + }); + + const actFilledTakerAmount = await this.exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash); + expect(actFilledTakerAmount).to.be.bignumber.equal(expFilledTakerAmount, 'filledTakerAmount'); + + expect(txReceipt.logs.length).to.be.equal(1, 'logs length'); + // tslint:disable-next-line:no-unnecessary-type-assertion + const log = txReceipt.logs[0] as LogWithDecodedArgs<FillContractEventArgs>; + expect(log.args.makerAddress).to.be.equal(makerAddress, 'log.args.makerAddress'); + expect(log.args.takerAddress).to.be.equal(this.takerAddress, 'log.args.this.takerAddress'); + expect(log.args.feeRecipientAddress).to.be.equal(feeRecipient, 'log.args.feeRecipientAddress'); + expect(log.args.makerAssetFilledAmount).to.be.bignumber.equal( + expFilledMakerAmount, + 'log.args.makerAssetFilledAmount', + ); + expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal( + expFilledTakerAmount, + 'log.args.takerAssetFilledAmount', + ); + const expMakerFeePaid = orderUtils.getPartialAmount( + expFilledTakerAmount, + signedOrder.takerAssetAmount, + signedOrder.makerFee, + ); + expect(log.args.makerFeePaid).to.be.bignumber.equal(expMakerFeePaid, 'log.args.makerFeePaid'); + const expTakerFeePaid = orderUtils.getPartialAmount( + expFilledTakerAmount, + signedOrder.takerAssetAmount, + signedOrder.takerFee, + ); + expect(log.args.takerFeePaid).to.be.bignumber.equal(expTakerFeePaid, 'logs.args.takerFeePaid'); + expect(log.args.orderHash).to.be.equal(orderHash, 'log.args.orderHash'); + expect(log.args.makerAssetData).to.be.equal(makerAssetData, 'log.args.makerAssetData'); + expect(log.args.takerAssetData).to.be.equal(takerAssetData, 'log.args.takerAssetData'); + + const actMakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, makerAssetData); + expect(actMakerAssetBalanceOfMaker).to.be.bignumber.equal( + expMakerAssetBalanceOfMaker, + 'makerAssetBalanceOfMaker', + ); + + const actMakerAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync( + makerAddress, + makerAssetData, + ); + expect(actMakerAssetAllowanceOfMaker).to.be.bignumber.equal( + expMakerAssetAllowanceOfMaker, + 'makerAssetAllowanceOfMaker', + ); + + const actTakerAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, takerAssetData); + expect(actTakerAssetBalanceOfMaker).to.be.bignumber.equal( + expTakerAssetBalanceOfMaker, + 'takerAssetBalanceOfMaker', + ); + + const actZRXAssetBalanceOfMaker = await this.assetWrapper.getBalanceAsync(makerAddress, this.zrxAssetData); + expect(actZRXAssetBalanceOfMaker).to.be.bignumber.equal(expZRXAssetBalanceOfMaker, 'ZRXAssetBalanceOfMaker'); + + const actZRXAssetAllowanceOfMaker = await this.assetWrapper.getProxyAllowanceAsync( + makerAddress, + this.zrxAssetData, + ); + expect(actZRXAssetAllowanceOfMaker).to.be.bignumber.equal( + expZRXAssetAllowanceOfMaker, + 'ZRXAssetAllowanceOfMaker', + ); + + const actTakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, takerAssetData); + expect(actTakerAssetBalanceOfTaker).to.be.bignumber.equal( + expTakerAssetBalanceOfTaker, + 'TakerAssetBalanceOfTaker', + ); + + const actTakerAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync( + this.takerAddress, + takerAssetData, + ); + + expect(actTakerAssetAllowanceOfTaker).to.be.bignumber.equal( + expTakerAssetAllowanceOfTaker, + 'TakerAssetAllowanceOfTaker', + ); + + const actMakerAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, makerAssetData); + expect(actMakerAssetBalanceOfTaker).to.be.bignumber.equal( + expMakerAssetBalanceOfTaker, + 'MakerAssetBalanceOfTaker', + ); + + const actZRXAssetBalanceOfTaker = await this.assetWrapper.getBalanceAsync(this.takerAddress, this.zrxAssetData); + expect(actZRXAssetBalanceOfTaker).to.be.bignumber.equal(expZRXAssetBalanceOfTaker, 'ZRXAssetBalanceOfTaker'); + + const actZRXAssetAllowanceOfTaker = await this.assetWrapper.getProxyAllowanceAsync( + this.takerAddress, + this.zrxAssetData, + ); + expect(actZRXAssetAllowanceOfTaker).to.be.bignumber.equal( + expZRXAssetAllowanceOfTaker, + 'ZRXAssetAllowanceOfTaker', + ); + + const actZRXAssetBalanceOfFeeRecipient = await this.assetWrapper.getBalanceAsync( + feeRecipient, + this.zrxAssetData, + ); + expect(actZRXAssetBalanceOfFeeRecipient).to.be.bignumber.equal( + expZRXAssetBalanceOfFeeRecipient, + 'ZRXAssetBalanceOfFeeRecipient', + ); + } + private async _getTakerAssetFillAmountAsync( + signedOrder: SignedOrder, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario, + balanceAndProxyAllowanceFetcher: SimpleAssetBalanceAndProxyAllowanceFetcher, + orderFilledCancelledFetcher: SimpleOrderFilledCancelledFetcher, + ): Promise<BigNumber> { + const orderStateUtils = new OrderStateUtils(balanceAndProxyAllowanceFetcher, orderFilledCancelledFetcher); + const fillableTakerAssetAmount = await orderStateUtils.getMaxFillableTakerAssetAmountAsync( + signedOrder, + this.takerAddress, + ); + + let takerAssetFillAmount; + switch (takerAssetFillAmountScenario) { + case TakerAssetFillAmountScenario.Zero: + takerAssetFillAmount = new BigNumber(0); + break; + + case TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount: + takerAssetFillAmount = fillableTakerAssetAmount; + break; + + case TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount: + takerAssetFillAmount = fillableTakerAssetAmount.add(1); + break; + + case TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount: + const takerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.takerAssetData); + const makerAssetProxyId = assetProxyUtils.decodeAssetDataId(signedOrder.makerAssetData); + const isEitherAssetERC721 = + takerAssetProxyId === constants.ERC721_PROXY_ID || makerAssetProxyId === constants.ERC721_PROXY_ID; + if (isEitherAssetERC721) { + throw new Error( + 'Cannot test `TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount` together with ERC721 assets since orders involving ERC721 must always be filled exactly.', + ); + } + takerAssetFillAmount = fillableTakerAssetAmount.div(2).floor(); + break; + + default: + throw errorUtils.spawnSwitchErr('TakerAssetFillAmountScenario', takerAssetFillAmountScenario); + } + + return takerAssetFillAmount; + } + private async _modifyTraderStateAsync( + makerStateScenario: TraderStateScenario, + takerStateScenario: TraderStateScenario, + signedOrder: SignedOrder, + takerAssetFillAmount: BigNumber, + ): Promise<void> { + const makerAssetFillAmount = orderUtils.getPartialAmount( + takerAssetFillAmount, + signedOrder.takerAssetAmount, + signedOrder.makerAssetAmount, + ); + switch (makerStateScenario.traderAssetBalance) { + case BalanceAmountScenario.Higher: + break; // Noop since this is already the default + + case BalanceAmountScenario.TooLow: + if (makerAssetFillAmount.eq(0)) { + throw new Error(`Cannot set makerAssetBalanceOfMaker TooLow if makerAssetFillAmount is 0`); + } + const tooLowBalance = makerAssetFillAmount.minus(1); + await this.assetWrapper.setBalanceAsync( + signedOrder.makerAddress, + signedOrder.makerAssetData, + tooLowBalance, + ); + break; + + case BalanceAmountScenario.Exact: + const exactBalance = makerAssetFillAmount; + await this.assetWrapper.setBalanceAsync( + signedOrder.makerAddress, + signedOrder.makerAssetData, + exactBalance, + ); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'makerStateScenario.traderAssetBalance', + makerStateScenario.traderAssetBalance, + ); + } + + const makerFee = orderUtils.getPartialAmount( + takerAssetFillAmount, + signedOrder.takerAssetAmount, + signedOrder.makerFee, + ); + switch (makerStateScenario.zrxFeeBalance) { + case BalanceAmountScenario.Higher: + break; // Noop since this is already the default + + case BalanceAmountScenario.TooLow: + if (makerFee.eq(0)) { + throw new Error(`Cannot set zrxAsserBalanceOfMaker TooLow if makerFee is 0`); + } + const tooLowBalance = makerFee.minus(1); + await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, tooLowBalance); + break; + + case BalanceAmountScenario.Exact: + const exactBalance = makerFee; + await this.assetWrapper.setBalanceAsync(signedOrder.makerAddress, this.zrxAssetData, exactBalance); + break; + + default: + throw errorUtils.spawnSwitchErr('makerStateScenario.zrxFeeBalance', makerStateScenario.zrxFeeBalance); + } + + switch (makerStateScenario.traderAssetAllowance) { + case AllowanceAmountScenario.Higher: + break; // Noop since this is already the default + + case AllowanceAmountScenario.TooLow: + const tooLowAllowance = makerAssetFillAmount.minus(1); + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + signedOrder.makerAssetData, + tooLowAllowance, + ); + break; + + case AllowanceAmountScenario.Exact: + const exactAllowance = makerAssetFillAmount; + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + signedOrder.makerAssetData, + exactAllowance, + ); + break; + + case AllowanceAmountScenario.Unlimited: + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + signedOrder.makerAssetData, + constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + ); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'makerStateScenario.traderAssetAllowance', + makerStateScenario.traderAssetAllowance, + ); + } + + switch (makerStateScenario.zrxFeeAllowance) { + case AllowanceAmountScenario.Higher: + break; // Noop since this is already the default + + case AllowanceAmountScenario.TooLow: + const tooLowAllowance = makerFee.minus(1); + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + this.zrxAssetData, + tooLowAllowance, + ); + break; + + case AllowanceAmountScenario.Exact: + const exactAllowance = makerFee; + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + this.zrxAssetData, + exactAllowance, + ); + break; + + case AllowanceAmountScenario.Unlimited: + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.makerAddress, + this.zrxAssetData, + constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + ); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'makerStateScenario.zrxFeeAllowance', + makerStateScenario.zrxFeeAllowance, + ); + } + + switch (takerStateScenario.traderAssetBalance) { + case BalanceAmountScenario.Higher: + break; // Noop since this is already the default + + case BalanceAmountScenario.TooLow: + if (takerAssetFillAmount.eq(0)) { + throw new Error(`Cannot set takerAssetBalanceOfTaker TooLow if takerAssetFillAmount is 0`); + } + const tooLowBalance = takerAssetFillAmount.minus(1); + await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, tooLowBalance); + break; + + case BalanceAmountScenario.Exact: + const exactBalance = takerAssetFillAmount; + await this.assetWrapper.setBalanceAsync(this.takerAddress, signedOrder.takerAssetData, exactBalance); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'takerStateScenario.traderAssetBalance', + takerStateScenario.traderAssetBalance, + ); + } + + const takerFee = orderUtils.getPartialAmount( + takerAssetFillAmount, + signedOrder.takerAssetAmount, + signedOrder.takerFee, + ); + switch (takerStateScenario.zrxFeeBalance) { + case BalanceAmountScenario.Higher: + break; // Noop since this is already the default + + case BalanceAmountScenario.TooLow: + if (takerFee.eq(0)) { + throw new Error(`Cannot set zrxAssetBalanceOfTaker TooLow if takerFee is 0`); + } + const tooLowBalance = takerFee.minus(1); + await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, tooLowBalance); + break; + + case BalanceAmountScenario.Exact: + const exactBalance = takerFee; + await this.assetWrapper.setBalanceAsync(this.takerAddress, this.zrxAssetData, exactBalance); + break; + + default: + throw errorUtils.spawnSwitchErr('takerStateScenario.zrxFeeBalance', takerStateScenario.zrxFeeBalance); + } + + switch (takerStateScenario.traderAssetAllowance) { + case AllowanceAmountScenario.Higher: + break; // Noop since this is already the default + + case AllowanceAmountScenario.TooLow: + const tooLowAllowance = takerAssetFillAmount.minus(1); + await this.assetWrapper.setProxyAllowanceAsync( + this.takerAddress, + signedOrder.takerAssetData, + tooLowAllowance, + ); + break; + + case AllowanceAmountScenario.Exact: + const exactAllowance = takerAssetFillAmount; + await this.assetWrapper.setProxyAllowanceAsync( + this.takerAddress, + signedOrder.takerAssetData, + exactAllowance, + ); + break; + + case AllowanceAmountScenario.Unlimited: + await this.assetWrapper.setProxyAllowanceAsync( + this.takerAddress, + signedOrder.takerAssetData, + constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + ); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'takerStateScenario.traderAssetAllowance', + takerStateScenario.traderAssetAllowance, + ); + } + + switch (takerStateScenario.zrxFeeAllowance) { + case AllowanceAmountScenario.Higher: + break; // Noop since this is already the default + + case AllowanceAmountScenario.TooLow: + const tooLowAllowance = takerFee.minus(1); + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.takerAddress, + this.zrxAssetData, + tooLowAllowance, + ); + break; + + case AllowanceAmountScenario.Exact: + const exactAllowance = takerFee; + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.takerAddress, + this.zrxAssetData, + exactAllowance, + ); + break; + + case AllowanceAmountScenario.Unlimited: + await this.assetWrapper.setProxyAllowanceAsync( + signedOrder.takerAddress, + this.zrxAssetData, + constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS, + ); + break; + + default: + throw errorUtils.spawnSwitchErr( + 'takerStateScenario.zrxFeeAllowance', + takerStateScenario.zrxFeeAllowance, + ); + } + } +} // tslint:disable:max-file-line-count diff --git a/packages/contracts/src/utils/erc20_wrapper.ts b/packages/contracts/src/utils/erc20_wrapper.ts index 91c9d50b7..6dd717f4a 100644 --- a/packages/contracts/src/utils/erc20_wrapper.ts +++ b/packages/contracts/src/utils/erc20_wrapper.ts @@ -1,3 +1,4 @@ +import { assetProxyUtils } from '@0xproject/order-utils'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import { Provider } from 'ethereum-types'; @@ -18,6 +19,7 @@ export class ERC20Wrapper { private _provider: Provider; private _dummyTokenContracts: DummyERC20TokenContract[]; private _proxyContract?: ERC20ProxyContract; + private _proxyIdIfExists?: number; constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) { this._dummyTokenContracts = []; this._web3Wrapper = new Web3Wrapper(provider); @@ -25,8 +27,11 @@ export class ERC20Wrapper { this._tokenOwnerAddresses = tokenOwnerAddresses; this._contractOwnerAddress = contractOwnerAddress; } - public async deployDummyTokensAsync(): Promise<DummyERC20TokenContract[]> { - for (let i = 0; i < constants.NUM_DUMMY_ERC20_TO_DEPLOY; i++) { + public async deployDummyTokensAsync( + numberToDeploy: number, + decimals: BigNumber, + ): Promise<DummyERC20TokenContract[]> { + for (let i = 0; i < numberToDeploy; i++) { this._dummyTokenContracts.push( await DummyERC20TokenContract.deployFrom0xArtifactAsync( artifacts.DummyERC20Token, @@ -34,7 +39,7 @@ export class ERC20Wrapper { txDefaults, constants.DUMMY_TOKEN_NAME, constants.DUMMY_TOKEN_SYMBOL, - constants.DUMMY_TOKEN_DECIMALS, + decimals, constants.DUMMY_TOKEN_TOTAL_SUPPLY, ), ); @@ -47,8 +52,13 @@ export class ERC20Wrapper { this._provider, txDefaults, ); + this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync(); return this._proxyContract; } + public getProxyId(): number { + this._validateProxyContractExistsOrThrow(); + return this._proxyIdIfExists as number; + } public async setBalancesAndAllowancesAsync(): Promise<void> { this._validateDummyTokenContractsExistOrThrow(); this._validateProxyContractExistsOrThrow(); @@ -73,6 +83,30 @@ export class ERC20Wrapper { } } } + public async getBalanceAsync(userAddress: string, assetData: string): Promise<BigNumber> { + const tokenContract = this._getTokenContractFromAssetData(assetData); + const balance = new BigNumber(await tokenContract.balanceOf.callAsync(userAddress)); + return balance; + } + public async setBalanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> { + const tokenContract = this._getTokenContractFromAssetData(assetData); + await tokenContract.setBalance.sendTransactionAsync(userAddress, amount, { + from: this._contractOwnerAddress, + }); + } + public async getProxyAllowanceAsync(userAddress: string, assetData: string): Promise<BigNumber> { + const tokenContract = this._getTokenContractFromAssetData(assetData); + const proxyAddress = (this._proxyContract as ERC20ProxyContract).address; + const allowance = new BigNumber(await tokenContract.allowance.callAsync(userAddress, proxyAddress)); + return allowance; + } + public async setAllowanceAsync(userAddress: string, assetData: string, amount: BigNumber): Promise<void> { + const tokenContract = this._getTokenContractFromAssetData(assetData); + const proxyAddress = (this._proxyContract as ERC20ProxyContract).address; + await tokenContract.approve.sendTransactionAsync(proxyAddress, amount, { + from: userAddress, + }); + } public async getBalancesAsync(): Promise<ERC20BalancesByOwner> { this._validateDummyTokenContractsExistOrThrow(); const balancesByOwner: ERC20BalancesByOwner = {}; @@ -105,6 +139,15 @@ export class ERC20Wrapper { const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address); return tokenAddresses; } + private _getTokenContractFromAssetData(assetData: string): DummyERC20TokenContract { + const erc20ProxyData = assetProxyUtils.decodeERC20AssetData(assetData); + const tokenAddress = erc20ProxyData.tokenAddress; + const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress); + if (_.isUndefined(tokenContractIfExists)) { + throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`); + } + return tokenContractIfExists; + } private _validateDummyTokenContractsExistOrThrow(): void { if (_.isUndefined(this._dummyTokenContracts)) { throw new Error('Dummy ERC20 tokens not yet deployed, please call "deployDummyTokensAsync"'); diff --git a/packages/contracts/src/utils/erc721_wrapper.ts b/packages/contracts/src/utils/erc721_wrapper.ts index b20d9acd2..0ad2fc7d7 100644 --- a/packages/contracts/src/utils/erc721_wrapper.ts +++ b/packages/contracts/src/utils/erc721_wrapper.ts @@ -19,6 +19,7 @@ export class ERC721Wrapper { private _provider: Provider; private _dummyTokenContracts: DummyERC721TokenContract[]; private _proxyContract?: ERC721ProxyContract; + private _proxyIdIfExists?: number; private _initialTokenIdsByOwner: ERC721TokenIdsByOwner = {}; constructor(provider: Provider, tokenOwnerAddresses: string[], contractOwnerAddress: string) { this._web3Wrapper = new Web3Wrapper(provider); @@ -47,8 +48,13 @@ export class ERC721Wrapper { this._provider, txDefaults, ); + this._proxyIdIfExists = await this._proxyContract.getProxyId.callAsync(); return this._proxyContract; } + public getProxyId(): number { + this._validateProxyContractExistsOrThrow(); + return this._proxyIdIfExists as number; + } public async setBalancesAndAllowancesAsync(): Promise<void> { this._validateDummyTokenContractsExistOrThrow(); this._validateProxyContractExistsOrThrow(); @@ -57,12 +63,7 @@ export class ERC721Wrapper { for (const tokenOwnerAddress of this._tokenOwnerAddresses) { for (let i = 0; i < constants.NUM_ERC721_TOKENS_TO_MINT; i++) { const tokenId = generatePseudoRandomSalt(); - await this._web3Wrapper.awaitTransactionSuccessAsync( - await dummyTokenContract.mint.sendTransactionAsync(tokenOwnerAddress, tokenId, { - from: this._contractOwnerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); + await this.mintAsync(dummyTokenContract.address, tokenId, tokenOwnerAddress); if (_.isUndefined(this._initialTokenIdsByOwner[tokenOwnerAddress])) { this._initialTokenIdsByOwner[tokenOwnerAddress] = { [dummyTokenContract.address]: [], @@ -72,19 +73,100 @@ export class ERC721Wrapper { this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address] = []; } this._initialTokenIdsByOwner[tokenOwnerAddress][dummyTokenContract.address].push(tokenId); + + await this.approveProxyAsync(dummyTokenContract.address, tokenId); } - const shouldApprove = true; - await this._web3Wrapper.awaitTransactionSuccessAsync( - await dummyTokenContract.setApprovalForAll.sendTransactionAsync( - (this._proxyContract as ERC721ProxyContract).address, - shouldApprove, - { from: tokenOwnerAddress }, - ), - constants.AWAIT_TRANSACTION_MINED_MS, - ); } } } + public async doesTokenExistAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const doesExist = await tokenContract.exists.callAsync(tokenId); + return doesExist; + } + public async approveProxyAsync(tokenAddress: string, tokenId: BigNumber): Promise<void> { + const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; + await this.approveAsync(proxyAddress, tokenAddress, tokenId); + } + public async approveProxyForAllAsync(tokenAddress: string, tokenId: BigNumber, isApproved: boolean): Promise<void> { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId); + const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.setApprovalForAll.sendTransactionAsync(proxyAddress, isApproved, { + from: tokenOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async approveAsync(to: string, tokenAddress: string, tokenId: BigNumber): Promise<void> { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const tokenOwner = await this.ownerOfAsync(tokenAddress, tokenId); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.approve.sendTransactionAsync(to, tokenId, { + from: tokenOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async transferFromAsync( + tokenAddress: string, + tokenId: BigNumber, + currentOwner: string, + userAddress: string, + ): Promise<void> { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.transferFrom.sendTransactionAsync(currentOwner, userAddress, tokenId, { + from: currentOwner, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async mintAsync(tokenAddress: string, tokenId: BigNumber, userAddress: string): Promise<void> { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.mint.sendTransactionAsync(userAddress, tokenId, { + from: this._contractOwnerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async burnAsync(tokenAddress: string, tokenId: BigNumber, owner: string): Promise<void> { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + await this._web3Wrapper.awaitTransactionSuccessAsync( + await tokenContract.burn.sendTransactionAsync(owner, tokenId, { + from: this._contractOwnerAddress, + }), + constants.AWAIT_TRANSACTION_MINED_MS, + ); + } + public async ownerOfAsync(tokenAddress: string, tokenId: BigNumber): Promise<string> { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const owner = await tokenContract.ownerOf.callAsync(tokenId); + return owner; + } + public async isOwnerAsync(userAddress: string, tokenAddress: string, tokenId: BigNumber): Promise<boolean> { + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const tokenOwner = await tokenContract.ownerOf.callAsync(tokenId); + const isOwner = tokenOwner === userAddress; + return isOwner; + } + public async isProxyApprovedForAllAsync(userAddress: string, tokenAddress: string): Promise<boolean> { + this._validateProxyContractExistsOrThrow(); + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const operator = (this._proxyContract as ERC721ProxyContract).address; + const didApproveAll = await tokenContract.isApprovedForAll.callAsync(userAddress, operator); + return didApproveAll; + } + public async isProxyApprovedAsync(tokenAddress: string, tokenId: BigNumber): Promise<boolean> { + this._validateProxyContractExistsOrThrow(); + const tokenContract = this._getTokenContractFromAssetData(tokenAddress); + const approvedAddress = await tokenContract.getApproved.callAsync(tokenId); + const proxyAddress = (this._proxyContract as ERC721ProxyContract).address; + const isProxyAnApprovedOperator = approvedAddress === proxyAddress; + return isProxyAnApprovedOperator; + } public async getBalancesAsync(): Promise<ERC721TokenIdsByOwner> { this._validateDummyTokenContractsExistOrThrow(); this._validateBalancesAndAllowancesSetOrThrow(); @@ -127,6 +209,13 @@ export class ERC721Wrapper { const tokenAddresses = _.map(this._dummyTokenContracts, dummyTokenContract => dummyTokenContract.address); return tokenAddresses; } + private _getTokenContractFromAssetData(tokenAddress: string): DummyERC721TokenContract { + const tokenContractIfExists = _.find(this._dummyTokenContracts, c => c.address === tokenAddress); + if (_.isUndefined(tokenContractIfExists)) { + throw new Error(`Token: ${tokenAddress} was not deployed through ERC20Wrapper`); + } + return tokenContractIfExists; + } private _validateDummyTokenContractsExistOrThrow(): void { if (_.isUndefined(this._dummyTokenContracts)) { throw new Error('Dummy ERC721 tokens not yet deployed, please call "deployDummyTokensAsync"'); diff --git a/packages/contracts/src/utils/exchange_wrapper.ts b/packages/contracts/src/utils/exchange_wrapper.ts index 06eb70644..ad68c8ff4 100644 --- a/packages/contracts/src/utils/exchange_wrapper.ts +++ b/packages/contracts/src/utils/exchange_wrapper.ts @@ -33,8 +33,8 @@ export class ExchangeWrapper { params.signature, { from }, ); - const tx = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return tx; + const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return txReceipt; } public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise<TransactionReceiptWithDecodedLogs> { const params = orderUtils.createCancel(signedOrder); @@ -227,6 +227,10 @@ export class ExchangeWrapper { const filledAmount = new BigNumber(await this._exchange.filled.callAsync(orderHashHex)); return filledAmount; } + public async isCancelledAsync(orderHashHex: string): Promise<boolean> { + const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex); + return isCancelled; + } public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> { const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; return orderInfo; diff --git a/packages/contracts/src/utils/order_factory_from_scenario.ts b/packages/contracts/src/utils/order_factory_from_scenario.ts new file mode 100644 index 000000000..b150e59f6 --- /dev/null +++ b/packages/contracts/src/utils/order_factory_from_scenario.ts @@ -0,0 +1,277 @@ +import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils'; +import { Order } from '@0xproject/types'; +import { BigNumber, errorUtils } from '@0xproject/utils'; + +import { DummyERC721TokenContract } from '../generated_contract_wrappers/dummy_e_r_c721_token'; + +import { constants } from './constants'; +import { + AssetDataScenario, + ERC721TokenIdsByOwner, + ExpirationTimeSecondsScenario, + FeeRecipientAddressScenario, + OrderAssetAmountScenario, + OrderScenario, + TakerScenario, +} from './types'; + +const TEN_UNITS_EIGHTEEN_DECIMALS = new BigNumber(10_000_000_000_000_000_000); +const FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(5_000_000_000_000_000_000); +const POINT_ONE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(100_000_000_000_000_000); +const POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS = new BigNumber(50_000_000_000_000_000); +const TEN_UNITS_FIVE_DECIMALS = new BigNumber(1_000_000); +const FIVE_UNITS_FIVE_DECIMALS = new BigNumber(500_000); +const ONE_NFT_UNIT = new BigNumber(1); + +export class OrderFactoryFromScenario { + private _userAddresses: string[]; + private _zrxAddress: string; + private _nonZrxERC20EighteenDecimalTokenAddresses: string[]; + private _erc20FiveDecimalTokenAddresses: string[]; + private _erc721Token: DummyERC721TokenContract; + private _erc721Balances: ERC721TokenIdsByOwner; + private _exchangeAddress: string; + constructor( + userAddresses: string[], + zrxAddress: string, + nonZrxERC20EighteenDecimalTokenAddresses: string[], + erc20FiveDecimalTokenAddresses: string[], + erc721Token: DummyERC721TokenContract, + erc721Balances: ERC721TokenIdsByOwner, + exchangeAddress: string, + ) { + this._userAddresses = userAddresses; + this._zrxAddress = zrxAddress; + this._nonZrxERC20EighteenDecimalTokenAddresses = nonZrxERC20EighteenDecimalTokenAddresses; + this._erc20FiveDecimalTokenAddresses = erc20FiveDecimalTokenAddresses; + this._erc721Token = erc721Token; + this._erc721Balances = erc721Balances; + this._exchangeAddress = exchangeAddress; + } + public generateOrder(orderScenario: OrderScenario): Order { + const makerAddress = this._userAddresses[1]; + let takerAddress = this._userAddresses[2]; + const erc721MakerAssetIds = this._erc721Balances[makerAddress][this._erc721Token.address]; + const erc721TakerAssetIds = this._erc721Balances[takerAddress][this._erc721Token.address]; + let feeRecipientAddress; + let makerAssetAmount; + let takerAssetAmount; + let makerFee; + let takerFee; + let expirationTimeSeconds; + let makerAssetData; + let takerAssetData; + + switch (orderScenario.feeRecipientScenario) { + case FeeRecipientAddressScenario.BurnAddress: + feeRecipientAddress = constants.NULL_ADDRESS; + break; + case FeeRecipientAddressScenario.EthUserAddress: + feeRecipientAddress = this._userAddresses[4]; + break; + default: + throw errorUtils.spawnSwitchErr('FeeRecipientAddressScenario', orderScenario.feeRecipientScenario); + } + + switch (orderScenario.makerAssetDataScenario) { + case AssetDataScenario.ZRXFeeToken: + makerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress); + break; + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + makerAssetData = assetProxyUtils.encodeERC20AssetData( + this._nonZrxERC20EighteenDecimalTokenAddresses[0], + ); + break; + case AssetDataScenario.ERC20FiveDecimals: + makerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[0]); + break; + case AssetDataScenario.ERC721: + makerAssetData = assetProxyUtils.encodeERC721AssetData( + this._erc721Token.address, + erc721MakerAssetIds[0], + ); + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); + } + + switch (orderScenario.takerAssetDataScenario) { + case AssetDataScenario.ZRXFeeToken: + takerAssetData = assetProxyUtils.encodeERC20AssetData(this._zrxAddress); + break; + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + takerAssetData = assetProxyUtils.encodeERC20AssetData( + this._nonZrxERC20EighteenDecimalTokenAddresses[1], + ); + break; + case AssetDataScenario.ERC20FiveDecimals: + takerAssetData = assetProxyUtils.encodeERC20AssetData(this._erc20FiveDecimalTokenAddresses[1]); + break; + case AssetDataScenario.ERC721: + takerAssetData = assetProxyUtils.encodeERC721AssetData( + this._erc721Token.address, + erc721TakerAssetIds[0], + ); + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); + } + + switch (orderScenario.makerAssetAmountScenario) { + case OrderAssetAmountScenario.Large: + switch (orderScenario.makerAssetDataScenario) { + case AssetDataScenario.ZRXFeeToken: + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + makerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS; + break; + case AssetDataScenario.ERC20FiveDecimals: + makerAssetAmount = TEN_UNITS_FIVE_DECIMALS; + break; + case AssetDataScenario.ERC721: + makerAssetAmount = ONE_NFT_UNIT; + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); + } + break; + case OrderAssetAmountScenario.Small: + switch (orderScenario.makerAssetDataScenario) { + case AssetDataScenario.ZRXFeeToken: + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + makerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS; + break; + case AssetDataScenario.ERC20FiveDecimals: + makerAssetAmount = FIVE_UNITS_FIVE_DECIMALS; + break; + case AssetDataScenario.ERC721: + makerAssetAmount = ONE_NFT_UNIT; + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.makerAssetDataScenario); + } + break; + case OrderAssetAmountScenario.Zero: + makerAssetAmount = new BigNumber(0); + break; + default: + throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerAssetAmountScenario); + } + + switch (orderScenario.takerAssetAmountScenario) { + case OrderAssetAmountScenario.Large: + switch (orderScenario.takerAssetDataScenario) { + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + case AssetDataScenario.ZRXFeeToken: + takerAssetAmount = TEN_UNITS_EIGHTEEN_DECIMALS; + break; + case AssetDataScenario.ERC20FiveDecimals: + takerAssetAmount = TEN_UNITS_FIVE_DECIMALS; + break; + case AssetDataScenario.ERC721: + takerAssetAmount = ONE_NFT_UNIT; + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); + } + break; + case OrderAssetAmountScenario.Small: + switch (orderScenario.takerAssetDataScenario) { + case AssetDataScenario.ERC20NonZRXEighteenDecimals: + case AssetDataScenario.ZRXFeeToken: + takerAssetAmount = FIVE_UNITS_EIGHTEEN_DECIMALS; + break; + case AssetDataScenario.ERC20FiveDecimals: + takerAssetAmount = FIVE_UNITS_FIVE_DECIMALS; + break; + case AssetDataScenario.ERC721: + takerAssetAmount = ONE_NFT_UNIT; + break; + default: + throw errorUtils.spawnSwitchErr('AssetDataScenario', orderScenario.takerAssetDataScenario); + } + break; + case OrderAssetAmountScenario.Zero: + takerAssetAmount = new BigNumber(0); + break; + default: + throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerAssetAmountScenario); + } + + switch (orderScenario.makerFeeScenario) { + case OrderAssetAmountScenario.Large: + makerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS; + break; + case OrderAssetAmountScenario.Small: + makerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS; + break; + case OrderAssetAmountScenario.Zero: + makerFee = new BigNumber(0); + break; + default: + throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.makerFeeScenario); + } + + switch (orderScenario.takerFeeScenario) { + case OrderAssetAmountScenario.Large: + takerFee = POINT_ONE_UNITS_EIGHTEEN_DECIMALS; + break; + case OrderAssetAmountScenario.Small: + takerFee = POINT_ZERO_FIVE_UNITS_EIGHTEEN_DECIMALS; + break; + case OrderAssetAmountScenario.Zero: + takerFee = new BigNumber(0); + break; + default: + throw errorUtils.spawnSwitchErr('OrderAssetAmountScenario', orderScenario.takerFeeScenario); + } + + switch (orderScenario.expirationTimeSecondsScenario) { + case ExpirationTimeSecondsScenario.InFuture: + expirationTimeSeconds = new BigNumber(2524604400); // Close to infinite + break; + case ExpirationTimeSecondsScenario.InPast: + expirationTimeSeconds = new BigNumber(0); // Jan 1, 1970 + break; + default: + throw errorUtils.spawnSwitchErr( + 'ExpirationTimeSecondsScenario', + orderScenario.expirationTimeSecondsScenario, + ); + } + + switch (orderScenario.takerScenario) { + case TakerScenario.CorrectlySpecified: + break; // noop since takerAddress is already specified + + case TakerScenario.IncorrectlySpecified: + const notTaker = this._userAddresses[3]; + takerAddress = notTaker; + break; + + case TakerScenario.Unspecified: + takerAddress = constants.NULL_ADDRESS; + break; + + default: + throw errorUtils.spawnSwitchErr('TakerScenario', orderScenario.takerScenario); + } + + const order = { + senderAddress: constants.NULL_ADDRESS, + makerAddress, + takerAddress, + makerFee, + takerFee, + makerAssetAmount, + takerAssetAmount, + makerAssetData, + takerAssetData, + salt: generatePseudoRandomSalt(), + exchangeAddress: this._exchangeAddress, + feeRecipientAddress, + expirationTimeSeconds, + }; + + return order; + } +} diff --git a/packages/contracts/src/utils/order_utils.ts b/packages/contracts/src/utils/order_utils.ts index a9f994d80..019f6e74b 100644 --- a/packages/contracts/src/utils/order_utils.ts +++ b/packages/contracts/src/utils/order_utils.ts @@ -5,6 +5,13 @@ import { constants } from './constants'; import { CancelOrder, MatchOrder } from './types'; export const orderUtils = { + getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber { + const partialAmount = numerator + .mul(target) + .div(denominator) + .floor(); + return partialAmount; + }, createFill: (signedOrder: SignedOrder, takerAssetFillAmount?: BigNumber) => { const fill = { order: orderUtils.getOrderWithoutExchangeAddress(signedOrder), diff --git a/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts b/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts new file mode 100644 index 000000000..a295a40c4 --- /dev/null +++ b/packages/contracts/src/utils/simple_asset_balance_and_proxy_allowance_fetcher.ts @@ -0,0 +1,19 @@ +import { AbstractBalanceAndProxyAllowanceFetcher } from '@0xproject/order-utils'; +import { BigNumber } from '@0xproject/utils'; + +import { AssetWrapper } from './asset_wrapper'; + +export class SimpleAssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher { + private _assetWrapper: AssetWrapper; + constructor(assetWrapper: AssetWrapper) { + this._assetWrapper = assetWrapper; + } + public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { + const balance = await this._assetWrapper.getBalanceAsync(userAddress, assetData); + return balance; + } + public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> { + const proxyAllowance = await this._assetWrapper.getProxyAllowanceAsync(userAddress, assetData); + return proxyAllowance; + } +} diff --git a/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts b/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts new file mode 100644 index 000000000..24afe36b7 --- /dev/null +++ b/packages/contracts/src/utils/simple_order_filled_cancelled_fetcher.ts @@ -0,0 +1,24 @@ +import { AbstractOrderFilledCancelledFetcher } from '@0xproject/order-utils'; +import { BigNumber } from '@0xproject/utils'; + +import { ExchangeWrapper } from './exchange_wrapper'; + +export class SimpleOrderFilledCancelledFetcher implements AbstractOrderFilledCancelledFetcher { + private _exchangeWrapper: ExchangeWrapper; + private _zrxAssetData: string; + constructor(exchange: ExchangeWrapper, zrxAssetData: string) { + this._exchangeWrapper = exchange; + this._zrxAssetData = zrxAssetData; + } + public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { + const filledTakerAmount = new BigNumber(await this._exchangeWrapper.getTakerAssetFilledAmountAsync(orderHash)); + return filledTakerAmount; + } + public async isOrderCancelledAsync(orderHash: string): Promise<boolean> { + const isCancelled = await this._exchangeWrapper.isCancelledAsync(orderHash); + return isCancelled; + } + public getZRXAssetData(): string { + return this._zrxAssetData; + } +} diff --git a/packages/contracts/src/utils/types.ts b/packages/contracts/src/utils/types.ts index 5dfac64fc..b792bb90a 100644 --- a/packages/contracts/src/utils/types.ts +++ b/packages/contracts/src/utils/types.ts @@ -150,3 +150,80 @@ export interface MatchOrder { leftSignature: string; rightSignature: string; } + +// Combinatorial testing types + +export enum FeeRecipientAddressScenario { + BurnAddress = 'BURN_ADDRESS', + EthUserAddress = 'ETH_USER_ADDRESS', +} + +export enum OrderAssetAmountScenario { + Zero = 'ZERO', + Large = 'LARGE', + Small = 'SMALL', +} + +export enum TakerScenario { + CorrectlySpecified = 'CORRECTLY_SPECIFIED', + IncorrectlySpecified = 'INCORRECTLY_SPECIFIED', + Unspecified = 'UNSPECIFIED', +} + +export enum ExpirationTimeSecondsScenario { + InPast = 'IN_PAST', + InFuture = 'IN_FUTURE', +} + +export enum AssetDataScenario { + ERC721 = 'ERC721', + ZRXFeeToken = 'ZRX_FEE_TOKEN', + ERC20FiveDecimals = 'ERC20_FIVE_DECIMALS', + ERC20NonZRXEighteenDecimals = 'ERC20_NON_ZRX_EIGHTEEN_DECIMALS', +} + +export enum TakerAssetFillAmountScenario { + Zero = 'ZERO', + GreaterThanRemainingFillableTakerAssetAmount = 'GREATER_THAN_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT', + LessThanRemainingFillableTakerAssetAmount = 'LESS_THAN_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT', + ExactlyRemainingFillableTakerAssetAmount = 'EXACTLY_REMAINING_FILLABLE_TAKER_ASSET_AMOUNT', +} + +export interface OrderScenario { + takerScenario: TakerScenario; + feeRecipientScenario: FeeRecipientAddressScenario; + makerAssetAmountScenario: OrderAssetAmountScenario; + takerAssetAmountScenario: OrderAssetAmountScenario; + makerFeeScenario: OrderAssetAmountScenario; + takerFeeScenario: OrderAssetAmountScenario; + expirationTimeSecondsScenario: ExpirationTimeSecondsScenario; + makerAssetDataScenario: AssetDataScenario; + takerAssetDataScenario: AssetDataScenario; +} + +export enum BalanceAmountScenario { + Exact = 'EXACT', + TooLow = 'TOO_LOW', + Higher = 'HIGHER', +} + +export enum AllowanceAmountScenario { + Exact = 'EXACT', + TooLow = 'TOO_LOW', + Higher = 'HIGHER', + Unlimited = 'UNLIMITED', +} + +export interface TraderStateScenario { + traderAssetBalance: BalanceAmountScenario; + traderAssetAllowance: AllowanceAmountScenario; + zrxFeeBalance: BalanceAmountScenario; + zrxFeeAllowance: AllowanceAmountScenario; +} + +export interface FillScenario { + orderScenario: OrderScenario; + takerAssetFillAmountScenario: TakerAssetFillAmountScenario; + makerStateScenario: TraderStateScenario; + takerStateScenario: TraderStateScenario; +} diff --git a/packages/contracts/test/asset_proxy/authorizable.ts b/packages/contracts/test/asset_proxy/authorizable.ts index c35dc7882..badf50bfd 100644 --- a/packages/contracts/test/asset_proxy/authorizable.ts +++ b/packages/contracts/test/asset_proxy/authorizable.ts @@ -1,10 +1,14 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { RevertReasons } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import { MixinAuthorizableContract } from '../../src/generated_contract_wrappers/mixin_authorizable'; import { artifacts } from '../../src/utils/artifacts'; -import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; +import { + expectRevertOrAlwaysFailingTransactionAsync, + expectRevertReasonOrAlwaysFailingTransactionAsync, +} from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; @@ -43,8 +47,9 @@ describe('Authorizable', () => { }); describe('addAuthorizedAddress', () => { it('should throw if not called by owner', async () => { - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( authorizable.addAuthorizedAddress.sendTransactionAsync(notOwner, { from: notOwner }), + RevertReasons.OnlyContractOwner, ); }); it('should allow owner to add an authorized address', async () => { @@ -60,8 +65,9 @@ describe('Authorizable', () => { await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), constants.AWAIT_TRANSACTION_MINED_MS, ); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), + RevertReasons.TargetAlreadyAuthorized, ); }); }); @@ -72,10 +78,11 @@ describe('Authorizable', () => { await authorizable.addAuthorizedAddress.sendTransactionAsync(address, { from: owner }), constants.AWAIT_TRANSACTION_MINED_MS, ); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { from: notOwner, }), + RevertReasons.OnlyContractOwner, ); }); @@ -95,10 +102,11 @@ describe('Authorizable', () => { }); it('should throw if owner attempts to remove an address that is not authorized', async () => { - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( authorizable.removeAuthorizedAddress.sendTransactionAsync(address, { from: owner, }), + RevertReasons.TargetNotAuthorized, ); }); }); diff --git a/packages/contracts/test/asset_proxy/proxies.ts b/packages/contracts/test/asset_proxy/proxies.ts index 2313c0b0e..7960e46d2 100644 --- a/packages/contracts/test/asset_proxy/proxies.ts +++ b/packages/contracts/test/asset_proxy/proxies.ts @@ -1,5 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils'; +import { RevertReason } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; @@ -15,7 +16,10 @@ import { DummyERC721TokenContract } from '../../src/generated_contract_wrappers/ import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy'; import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy'; import { artifacts } from '../../src/utils/artifacts'; -import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; +import { + expectRevertOrAlwaysFailingTransactionAsync, + expectRevertReasonOrAlwaysFailingTransactionAsync, +} from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -158,14 +162,15 @@ describe('Asset Transfer Proxies', () => { constants.AWAIT_TRANSACTION_MINED_MS, ); // Perform a transfer; expect this to fail. - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( erc20Proxy.transferFrom.sendTransactionAsync( encodedAssetData, makerAddress, takerAddress, transferAmount, - { from: notAuthorized }, + { from: exchangeAddress }, ), + RevertReason.TransferFailed, ); }); @@ -175,10 +180,11 @@ describe('Asset Transfer Proxies', () => { // Perform a transfer from makerAddress to takerAddress const amount = new BigNumber(10); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( erc20Proxy.transferFrom.sendTransactionAsync(encodedAssetData, makerAddress, takerAddress, amount, { from: notAuthorized, }), + RevertReason.SenderNotAuthorized, ); }); }); @@ -226,10 +232,11 @@ describe('Asset Transfer Proxies', () => { const toAddresses = _.times(numTransfers, () => takerAddress); const amounts = _.times(numTransfers, () => amount); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( erc20Proxy.batchTransferFrom.sendTransactionAsync(assetData, fromAddresses, toAddresses, amounts, { from: notAuthorized, }), + RevertReason.SenderNotAuthorized, ); }); }); @@ -363,7 +370,7 @@ describe('Asset Transfer Proxies', () => { expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); // Perform a transfer from makerAddress to takerAddress const amount = new BigNumber(0); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( erc721Proxy.transferFrom.sendTransactionAsync( encodedAssetData, makerAddress, @@ -371,6 +378,7 @@ describe('Asset Transfer Proxies', () => { amount, { from: exchangeAddress }, ), + RevertReason.InvalidAmount, ); }); @@ -382,7 +390,7 @@ describe('Asset Transfer Proxies', () => { expect(ownerMakerAsset).to.be.bignumber.equal(makerAddress); // Perform a transfer from makerAddress to takerAddress const amount = new BigNumber(500); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( erc721Proxy.transferFrom.sendTransactionAsync( encodedAssetData, makerAddress, @@ -390,6 +398,7 @@ describe('Asset Transfer Proxies', () => { amount, { from: exchangeAddress }, ), + RevertReason.InvalidAmount, ); }); @@ -405,10 +414,11 @@ describe('Asset Transfer Proxies', () => { ); // Perform a transfer; expect this to fail. const amount = new BigNumber(1); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( erc20Proxy.transferFrom.sendTransactionAsync(encodedAssetData, makerAddress, takerAddress, amount, { - from: notAuthorized, + from: exchangeAddress, }), + RevertReason.TransferFailed, ); }); @@ -417,7 +427,7 @@ describe('Asset Transfer Proxies', () => { const encodedAssetData = assetProxyUtils.encodeERC721AssetData(erc721Token.address, erc721MakerTokenId); // Perform a transfer from makerAddress to takerAddress const amount = new BigNumber(1); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( erc721Proxy.transferFrom.sendTransactionAsync( encodedAssetData, makerAddress, @@ -425,6 +435,7 @@ describe('Asset Transfer Proxies', () => { amount, { from: notAuthorized }, ), + RevertReason.SenderNotAuthorized, ); }); }); @@ -475,10 +486,11 @@ describe('Asset Transfer Proxies', () => { const toAddresses = _.times(numTransfers, () => takerAddress); const amounts = _.times(numTransfers, () => new BigNumber(1)); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( erc721Proxy.batchTransferFrom.sendTransactionAsync(assetData, fromAddresses, toAddresses, amounts, { from: notAuthorized, }), + RevertReason.SenderNotAuthorized, ); }); }); diff --git a/packages/contracts/test/exchange/combinatorial_tests.ts b/packages/contracts/test/exchange/combinatorial_tests.ts new file mode 100644 index 000000000..f5f795bbe --- /dev/null +++ b/packages/contracts/test/exchange/combinatorial_tests.ts @@ -0,0 +1,48 @@ +import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import * as _ from 'lodash'; + +import { chaiSetup } from '../../src/utils/chai_setup'; +import { CoreCombinatorialUtils, coreCombinatorialUtilsFactoryAsync } from '../../src/utils/core_combinatorial_utils'; +import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; + +import { FillScenario } from '../../src/utils/types'; + +chaiSetup.configure(); +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +describe('Combinatorial tests', () => { + let coreCombinatorialUtils: CoreCombinatorialUtils; + + before(async () => { + await blockchainLifecycle.startAsync(); + coreCombinatorialUtils = await coreCombinatorialUtilsFactoryAsync(web3Wrapper, txDefaults); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + const test = (fillScenarios: FillScenario[]) => { + _.forEach(fillScenarios, fillScenario => { + const orderScenario = fillScenario.orderScenario; + const description = `Combinatorial OrderFill: ${orderScenario.feeRecipientScenario} ${ + orderScenario.makerAssetAmountScenario + } ${orderScenario.takerAssetAmountScenario} ${orderScenario.makerFeeScenario} ${ + orderScenario.takerFeeScenario + } ${orderScenario.expirationTimeSecondsScenario} ${orderScenario.makerAssetDataScenario} ${ + orderScenario.takerAssetDataScenario + }`; + it(description, async () => { + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + }); + }; + + const allFillScenarios = CoreCombinatorialUtils.generateFillOrderCombinations(); + + describe('Fills orders', () => test(allFillScenarios)); +}); diff --git a/packages/contracts/test/exchange/core.ts b/packages/contracts/test/exchange/core.ts index 5b2d4590f..52bddf996 100644 --- a/packages/contracts/test/exchange/core.ts +++ b/packages/contracts/test/exchange/core.ts @@ -1,6 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { assetProxyUtils, orderHashUtils } from '@0xproject/order-utils'; -import { AssetProxyId, SignedOrder } from '@0xproject/types'; +import { AssetProxyId, RevertReasons, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; @@ -11,13 +11,12 @@ import { DummyERC20TokenContract } from '../../src/generated_contract_wrappers/d import { DummyERC721TokenContract } from '../../src/generated_contract_wrappers/dummy_e_r_c721_token'; import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c20_proxy'; import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy'; -import { - CancelContractEventArgs, - ExchangeContract, - FillContractEventArgs, -} from '../../src/generated_contract_wrappers/exchange'; +import { CancelContractEventArgs, ExchangeContract } from '../../src/generated_contract_wrappers/exchange'; import { artifacts } from '../../src/utils/artifacts'; -import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; +import { + expectRevertOrAlwaysFailingTransactionAsync, + expectRevertReasonOrAlwaysFailingTransactionAsync, +} from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -71,7 +70,10 @@ describe('Exchange core', () => { erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(); + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + constants.NUM_DUMMY_ERC20_TO_DEPLOY, + constants.DUMMY_TOKEN_DECIMALS, + ); erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); @@ -130,295 +132,6 @@ describe('Exchange core', () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); signedOrder = orderFactory.newSignedOrder(); }); - it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => { - signedOrder = orderFactory.newSignedOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - - const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrder), - ); - expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0); - - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - - const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrder), - ); - expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFeePaid = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFeePaid = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - - it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => { - signedOrder = orderFactory.newSignedOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - }); - - const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrder), - ); - expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0); - - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - - const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrder), - ); - expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFeePaid = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFeePaid = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - - it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => { - signedOrder = orderFactory.newSignedOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - - const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrder), - ); - expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0); - - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - - const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrder), - ); - expect(makerAmountBoughtAfter).to.be.bignumber.equal(takerAssetFillAmount); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFeePaid = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFeePaid = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - - it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => { - signedOrder = orderFactory.newSignedOrder({ - takerAddress, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - - const takerAssetFilledAmountBefore = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrder), - ); - expect(takerAssetFilledAmountBefore).to.be.bignumber.equal(0); - - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - - const makerAmountBoughtAfter = await exchangeWrapper.getTakerAssetFilledAmountAsync( - orderHashUtils.getOrderHashHex(signedOrder), - ); - const expectedMakerAmountBoughtAfter = takerAssetFillAmount.add(takerAssetFilledAmountBefore); - expect(makerAmountBoughtAfter).to.be.bignumber.equal(expectedMakerAmountBoughtAfter); - - const newBalances = await erc20Wrapper.getBalancesAsync(); - - const makerAssetFilledAmount = takerAssetFillAmount - .times(signedOrder.makerAssetAmount) - .dividedToIntegerBy(signedOrder.takerAssetAmount); - const makerFeePaid = signedOrder.makerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - const takerFeePaid = signedOrder.takerFee - .times(makerAssetFilledAmount) - .dividedToIntegerBy(signedOrder.makerAssetAmount); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(makerAssetFilledAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].add(makerAssetFilledAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - - it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => { - const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - - const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: signedOrder.takerAssetAmount, - }); - const log = res.logs[0] as LogWithDecodedArgs<FillContractEventArgs>; - expect(log.args.takerAssetFilledAmount).to.be.bignumber.equal( - signedOrder.takerAssetAmount.minus(takerAssetFillAmount), - ); - const newBalances = await erc20Wrapper.getBalancesAsync(); - - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(signedOrder.makerAssetAmount), - ); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].add(signedOrder.takerAssetAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(signedOrder.takerAssetAmount), - ); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].add(signedOrder.makerAssetAmount), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add( - signedOrder.makerFee.add(signedOrder.takerFee), - ), - ); - }); - - it('should log 1 event with the correct arguments when order has a feeRecipient', async () => { - const divisor = 2; - const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: signedOrder.takerAssetAmount.div(divisor), - }); - expect(res.logs).to.have.length(1); - - const log = res.logs[0] as LogWithDecodedArgs<FillContractEventArgs>; - const logArgs = log.args; - const expectedFilledMakerAssetAmount = signedOrder.makerAssetAmount.div(divisor); - const expectedFilledTakerAssetAmount = signedOrder.takerAssetAmount.div(divisor); - const expectedFeeMPaid = signedOrder.makerFee.div(divisor); - const expectedFeeTPaid = signedOrder.takerFee.div(divisor); - - expect(signedOrder.makerAddress).to.be.equal(logArgs.makerAddress); - expect(takerAddress).to.be.equal(logArgs.takerAddress); - expect(takerAddress).to.be.equal(logArgs.senderAddress); - expect(signedOrder.feeRecipientAddress).to.be.equal(logArgs.feeRecipientAddress); - expect(signedOrder.makerAssetData).to.be.equal(logArgs.makerAssetData); - expect(signedOrder.takerAssetData).to.be.equal(logArgs.takerAssetData); - expect(expectedFilledMakerAssetAmount).to.be.bignumber.equal(logArgs.makerAssetFilledAmount); - expect(expectedFilledTakerAssetAmount).to.be.bignumber.equal(logArgs.takerAssetFilledAmount); - expect(expectedFeeMPaid).to.be.bignumber.equal(logArgs.makerFeePaid); - expect(expectedFeeTPaid).to.be.bignumber.equal(logArgs.takerFeePaid); - expect(orderHashUtils.getOrderHashHex(signedOrder)).to.be.equal(logArgs.orderHash); - }); - - it('should throw when taker is specified and order is claimed by other', async () => { - signedOrder = orderFactory.newSignedOrder({ - takerAddress: feeRecipientAddress, - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), 18), - }); - return expectRevertOrAlwaysFailingTransactionAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - ); - }); it('should throw if signature is invalid', async () => { signedOrder = orderFactory.newSignedOrder({ @@ -432,98 +145,18 @@ describe('Exchange core', () => { const invalidSigBuff = Buffer.concat([v, invalidR, invalidS, signatureType]); const invalidSigHex = `0x${invalidSigBuff.toString('hex')}`; signedOrder.signature = invalidSigHex; - return expectRevertOrAlwaysFailingTransactionAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - ); - }); - - it('should throw if makerAssetAmount is 0', async () => { - signedOrder = orderFactory.newSignedOrder({ - makerAssetAmount: new BigNumber(0), - }); - - return expectRevertOrAlwaysFailingTransactionAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - ); - }); - - it('should throw if takerAssetAmount is 0', async () => { - signedOrder = orderFactory.newSignedOrder({ - takerAssetAmount: new BigNumber(0), - }); - - return expectRevertOrAlwaysFailingTransactionAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - ); - }); - - it('should throw if takerAssetFillAmount is 0', async () => { - signedOrder = orderFactory.newSignedOrder(); - - return expectRevertOrAlwaysFailingTransactionAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { - takerAssetFillAmount: new BigNumber(0), - }), - ); - }); - - it('should throw if maker erc20Balances are too low to fill order', async () => { - signedOrder = orderFactory.newSignedOrder({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18), - }); - - return expectRevertOrAlwaysFailingTransactionAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - ); - }); - - it('should throw if taker erc20Balances are too low to fill order', async () => { - signedOrder = orderFactory.newSignedOrder({ - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100000), 18), - }); - return expectRevertOrAlwaysFailingTransactionAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - ); - }); - - it('should throw if maker allowances are too low to fill order', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenA.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), { - from: makerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - return expectRevertOrAlwaysFailingTransactionAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - ); - }); - - it('should throw if taker allowances are too low to fill order', async () => { - await web3Wrapper.awaitTransactionSuccessAsync( - await erc20TokenB.approve.sendTransactionAsync(erc20Proxy.address, new BigNumber(0), { - from: takerAddress, - }), - constants.AWAIT_TRANSACTION_MINED_MS, - ); - return expectRevertOrAlwaysFailingTransactionAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), - ); - }); - - it('should throw if an order is expired', async () => { - signedOrder = orderFactory.newSignedOrder({ - expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), - }); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), + RevertReasons.InvalidOrderSignature, ); }); it('should throw if no value is filled', async () => { signedOrder = orderFactory.newSignedOrder(); await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress), + RevertReasons.OrderUnfillable, ); }); }); @@ -535,8 +168,9 @@ describe('Exchange core', () => { }); it('should throw if not sent by maker', async () => { - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.cancelOrderAsync(signedOrder, takerAddress), + RevertReasons.InvalidMaker, ); }); @@ -545,8 +179,9 @@ describe('Exchange core', () => { makerAssetAmount: new BigNumber(0), }); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), + RevertReasons.OrderUnfillable, ); }); @@ -555,17 +190,19 @@ describe('Exchange core', () => { takerAssetAmount: new BigNumber(0), }); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), + RevertReasons.OrderUnfillable, ); }); it('should be able to cancel a full order', async () => { await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount: signedOrder.takerAssetAmount.div(2), }), + RevertReasons.OrderUnfillable, ); }); @@ -586,8 +223,9 @@ describe('Exchange core', () => { it('should throw if already cancelled', async () => { await exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), + RevertReasons.OrderUnfillable, ); }); @@ -595,8 +233,9 @@ describe('Exchange core', () => { signedOrder = orderFactory.newSignedOrder({ expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), }); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.cancelOrderAsync(signedOrder, makerAddress), + RevertReasons.OrderUnfillable, ); }); @@ -612,10 +251,11 @@ describe('Exchange core', () => { }); const fillTakerAssetAmount2 = new BigNumber(1); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount: fillTakerAssetAmount2, }), + RevertReasons.RoundingError, ); }); }); @@ -625,16 +265,18 @@ describe('Exchange core', () => { const orderEpoch = new BigNumber(1); await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); const lesserOrderEpoch = new BigNumber(0); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.cancelOrdersUpToAsync(lesserOrderEpoch, makerAddress), + RevertReasons.InvalidNewOrderEpoch, ); }); it('should fail to set orderEpoch equal to existing orderEpoch', async () => { const orderEpoch = new BigNumber(1); await exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.cancelOrdersUpToAsync(orderEpoch, makerAddress), + RevertReasons.InvalidNewOrderEpoch, ); }); @@ -705,32 +347,6 @@ describe('Exchange core', () => { }); describe('Testing Exchange of ERC721 Tokens', () => { - it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetIds[0]; - const takerAssetId = erc721TakerAssetIds[1]; - signedOrder = orderFactory.newSignedOrder({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: new BigNumber(1), - makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - // tslint:disable-next-line:no-unused-variable - const res = await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - // Verify post-conditions - const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); - const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress); - }); - it('should throw when maker does not own the token with id makerAssetId', async () => { // Construct Exchange parameters const makerAssetId = erc721TakerAssetIds[0]; @@ -792,8 +408,9 @@ describe('Exchange core', () => { expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); // Call Exchange const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), + RevertReasons.InvalidAmount, ); }); @@ -814,110 +431,26 @@ describe('Exchange core', () => { expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); // Call Exchange const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), + RevertReasons.InvalidAmount, ); }); it('should throw on partial fill', async () => { // Construct Exchange parameters const makerAssetId = erc721MakerAssetIds[0]; - const takerAssetId = erc721TakerAssetIds[0]; - signedOrder = orderFactory.newSignedOrder({ - makerAssetAmount: new BigNumber(1), - takerAssetAmount: new BigNumber(0), - makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), - takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); - // Call Exchange - const takerAssetFillAmount = signedOrder.takerAssetAmount; - return expectRevertOrAlwaysFailingTransactionAsync( - exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), - ); - }); - - it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => { - // Construct Exchange parameters - const makerAssetId = erc721MakerAssetIds[0]; signedOrder = orderFactory.newSignedOrder({ makerAssetAmount: new BigNumber(1), takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), makerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, makerAssetId), takerAssetData: assetProxyUtils.encodeERC20AssetData(defaultTakerAssetAddress), }); - // Verify pre-conditions - const initialOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(initialOwnerMakerAsset).to.be.bignumber.equal(makerAddress); - // Call Exchange - erc20Balances = await erc20Wrapper.getBalancesAsync(); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - // Verify ERC721 token was transferred from Maker to Taker - const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); - expect(newOwnerMakerAsset).to.be.bignumber.equal(takerAddress); - // Verify ERC20 tokens were transferred from Taker to Maker & fees were paid correctly - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[makerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[takerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add( - signedOrder.makerFee.add(signedOrder.takerFee), - ), - ); - }); - - it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => { - // Construct Exchange parameters - const takerAssetId = erc721TakerAssetIds[0]; - signedOrder = orderFactory.newSignedOrder({ - takerAssetAmount: new BigNumber(1), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 18), - takerAssetData: assetProxyUtils.encodeERC721AssetData(erc721Token.address, takerAssetId), - makerAssetData: assetProxyUtils.encodeERC20AssetData(defaultMakerAssetAddress), - }); - // Verify pre-conditions - const initialOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(initialOwnerTakerAsset).to.be.bignumber.equal(takerAddress); // Call Exchange - erc20Balances = await erc20Wrapper.getBalancesAsync(); - const takerAssetFillAmount = signedOrder.takerAssetAmount; - await exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }); - // Verify ERC721 token was transferred from Taker to Maker - const newOwnerTakerAsset = await erc721Token.ownerOf.callAsync(takerAssetId); - expect(newOwnerTakerAsset).to.be.bignumber.equal(makerAddress); - // Verify ERC20 tokens were transferred from Maker to Taker & fees were paid correctly - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect(newBalances[takerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[takerAddress][defaultMakerAssetAddress].add(signedOrder.makerAssetAmount), - ); - expect(newBalances[makerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[makerAddress][defaultMakerAssetAddress].minus(signedOrder.makerAssetAmount), - ); - expect(newBalances[makerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[makerAddress][zrxToken.address].minus(signedOrder.makerFee), - ); - expect(newBalances[takerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[takerAddress][zrxToken.address].minus(signedOrder.takerFee), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add( - signedOrder.makerFee.add(signedOrder.takerFee), - ), + const takerAssetFillAmount = signedOrder.takerAssetAmount.div(2); + return expectRevertReasonOrAlwaysFailingTransactionAsync( + exchangeWrapper.fillOrderAsync(signedOrder, takerAddress, { takerAssetFillAmount }), + RevertReasons.RoundingError, ); }); }); diff --git a/packages/contracts/test/exchange/dispatcher.ts b/packages/contracts/test/exchange/dispatcher.ts index f96f15f16..41ea88d4e 100644 --- a/packages/contracts/test/exchange/dispatcher.ts +++ b/packages/contracts/test/exchange/dispatcher.ts @@ -1,6 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { assetProxyUtils } from '@0xproject/order-utils'; -import { AssetProxyId } from '@0xproject/types'; +import { AssetProxyId, RevertReasons } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; @@ -9,7 +9,7 @@ import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c2 import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy'; import { TestAssetProxyDispatcherContract } from '../../src/generated_contract_wrappers/test_asset_proxy_dispatcher'; import { artifacts } from '../../src/utils/artifacts'; -import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; +import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -48,7 +48,10 @@ describe('AssetProxyDispatcher', () => { erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - [zrxToken] = await erc20Wrapper.deployDummyTokensAsync(); + [zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + constants.NUM_DUMMY_ERC20_TO_DEPLOY, + constants.DUMMY_TOKEN_DECIMALS, + ); erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); @@ -175,13 +178,14 @@ describe('AssetProxyDispatcher', () => { const proxyAddress = await assetProxyDispatcher.getAssetProxy.callAsync(AssetProxyId.ERC20); expect(proxyAddress).to.be.equal(erc20Proxy.address); // The following transaction will throw because the currentAddress is no longer constants.NULL_ADDRESS - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( assetProxyDispatcher.registerAssetProxy.sendTransactionAsync( AssetProxyId.ERC20, erc20Proxy.address, constants.NULL_ADDRESS, { from: owner }, ), + RevertReasons.AssetProxyMismatch, ); }); @@ -216,25 +220,27 @@ describe('AssetProxyDispatcher', () => { it('should throw if requesting address is not owner', async () => { const prevProxyAddress = constants.NULL_ADDRESS; - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( assetProxyDispatcher.registerAssetProxy.sendTransactionAsync( AssetProxyId.ERC20, erc20Proxy.address, prevProxyAddress, { from: notOwner }, ), + RevertReasons.OnlyContractOwner, ); }); it('should throw if attempting to register a proxy to the incorrect id', async () => { const prevProxyAddress = constants.NULL_ADDRESS; - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( assetProxyDispatcher.registerAssetProxy.sendTransactionAsync( AssetProxyId.ERC721, erc20Proxy.address, prevProxyAddress, { from: owner }, ), + RevertReasons.AssetProxyIdMismatch, ); }); }); @@ -305,7 +311,7 @@ describe('AssetProxyDispatcher', () => { const encodedAssetData = assetProxyUtils.encodeERC20AssetData(zrxToken.address); // Perform a transfer from makerAddress to takerAddress const amount = new BigNumber(10); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( assetProxyDispatcher.publicDispatchTransferFrom.sendTransactionAsync( encodedAssetData, makerAddress, @@ -313,6 +319,7 @@ describe('AssetProxyDispatcher', () => { amount, { from: owner }, ), + RevertReasons.AssetProxyDoesNotExist, ); }); }); diff --git a/packages/contracts/test/exchange/fill_order.ts b/packages/contracts/test/exchange/fill_order.ts new file mode 100644 index 000000000..60fe777c3 --- /dev/null +++ b/packages/contracts/test/exchange/fill_order.ts @@ -0,0 +1,285 @@ +import { BlockchainLifecycle } from '@0xproject/dev-utils'; + +import { chaiSetup } from '../../src/utils/chai_setup'; +import { CoreCombinatorialUtils, coreCombinatorialUtilsFactoryAsync } from '../../src/utils/core_combinatorial_utils'; +import { provider, txDefaults, web3Wrapper } from '../../src/utils/web3_wrapper'; + +import { + AllowanceAmountScenario, + AssetDataScenario, + BalanceAmountScenario, + ExpirationTimeSecondsScenario, + FeeRecipientAddressScenario, + OrderAssetAmountScenario, + TakerAssetFillAmountScenario, + TakerScenario, +} from '../../src/utils/types'; + +chaiSetup.configure(); +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +const defaultFillScenario = { + orderScenario: { + takerScenario: TakerScenario.Unspecified, + feeRecipientScenario: FeeRecipientAddressScenario.EthUserAddress, + makerAssetAmountScenario: OrderAssetAmountScenario.Large, + takerAssetAmountScenario: OrderAssetAmountScenario.Large, + makerFeeScenario: OrderAssetAmountScenario.Large, + takerFeeScenario: OrderAssetAmountScenario.Large, + expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InFuture, + makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, + takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.LessThanRemainingFillableTakerAssetAmount, + makerStateScenario: { + traderAssetBalance: BalanceAmountScenario.Higher, + traderAssetAllowance: AllowanceAmountScenario.Higher, + zrxFeeBalance: BalanceAmountScenario.Higher, + zrxFeeAllowance: AllowanceAmountScenario.Higher, + }, + takerStateScenario: { + traderAssetBalance: BalanceAmountScenario.Higher, + traderAssetAllowance: AllowanceAmountScenario.Higher, + zrxFeeBalance: BalanceAmountScenario.Higher, + zrxFeeAllowance: AllowanceAmountScenario.Higher, + }, +}; + +describe('FillOrder Tests', () => { + let coreCombinatorialUtils: CoreCombinatorialUtils; + + before(async () => { + await blockchainLifecycle.startAsync(); + coreCombinatorialUtils = await coreCombinatorialUtilsFactoryAsync(web3Wrapper, txDefaults); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe('fillOrder', () => { + it('should transfer the correct amounts when makerAssetAmount === takerAssetAmount', async () => { + const fillScenario = { + ...defaultFillScenario, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should transfer the correct amounts when makerAssetAmount > takerAssetAmount', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerAssetAmountScenario: OrderAssetAmountScenario.Small, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should transfer the correct amounts when makerAssetAmount < takerAssetAmount', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetAmountScenario: OrderAssetAmountScenario.Small, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should transfer the correct amounts when taker is specified and order is claimed by taker', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerScenario: TakerScenario.CorrectlySpecified, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should fill remaining value if takerAssetFillAmount > remaining takerAssetAmount', async () => { + const fillScenario = { + ...defaultFillScenario, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + it('should throw when taker is specified and order is claimed by other', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerScenario: TakerScenario.IncorrectlySpecified, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if makerAssetAmount is 0', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetAmountScenario: OrderAssetAmountScenario.Zero, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if takerAssetAmount is 0', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + takerAssetAmountScenario: OrderAssetAmountScenario.Zero, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.GreaterThanRemainingFillableTakerAssetAmount, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if takerAssetFillAmount is 0', async () => { + const fillScenario = { + ...defaultFillScenario, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.Zero, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if an order is expired', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + expirationTimeSecondsScenario: ExpirationTimeSecondsScenario.InPast, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if maker erc20Balances are too low to fill order', async () => { + const fillScenario = { + ...defaultFillScenario, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetBalance: BalanceAmountScenario.TooLow, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if taker erc20Balances are too low to fill order', async () => { + const fillScenario = { + ...defaultFillScenario, + takerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetBalance: BalanceAmountScenario.TooLow, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if maker allowances are too low to fill order', async () => { + const fillScenario = { + ...defaultFillScenario, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetAllowance: AllowanceAmountScenario.TooLow, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should throw if taker allowances are too low to fill order', async () => { + const fillScenario = { + ...defaultFillScenario, + takerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetAllowance: AllowanceAmountScenario.TooLow, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + }); + + describe('Testing Exchange of ERC721 Tokens', () => { + it('should successfully exchange a single token between the maker and taker (via fillOrder)', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC721, + takerAssetDataScenario: AssetDataScenario.ERC721, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should successfully fill order when makerAsset is ERC721 and takerAsset is ERC20', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC721, + takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario, true); + }); + + it('should successfully fill order when makerAsset is ERC20 and takerAsset is ERC721', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, + takerAssetDataScenario: AssetDataScenario.ERC721, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should successfully fill order when makerAsset is ERC721 and approveAll is set for it', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC721, + takerAssetDataScenario: AssetDataScenario.ERC20NonZRXEighteenDecimals, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetAllowance: AllowanceAmountScenario.Unlimited, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + + it('should successfully fill order when makerAsset and takerAsset are ERC721 and approveAll is set for them', async () => { + const fillScenario = { + ...defaultFillScenario, + orderScenario: { + ...defaultFillScenario.orderScenario, + makerAssetDataScenario: AssetDataScenario.ERC721, + takerAssetDataScenario: AssetDataScenario.ERC721, + }, + takerAssetFillAmountScenario: TakerAssetFillAmountScenario.ExactlyRemainingFillableTakerAssetAmount, + makerStateScenario: { + ...defaultFillScenario.makerStateScenario, + traderAssetAllowance: AllowanceAmountScenario.Unlimited, + }, + takerStateScenario: { + ...defaultFillScenario.takerStateScenario, + traderAssetAllowance: AllowanceAmountScenario.Unlimited, + }, + }; + await coreCombinatorialUtils.testFillOrderScenarioAsync(provider, fillScenario); + }); + }); +}); diff --git a/packages/contracts/test/exchange/match_orders.ts b/packages/contracts/test/exchange/match_orders.ts index 18a46187f..7c4ad165e 100644 --- a/packages/contracts/test/exchange/match_orders.ts +++ b/packages/contracts/test/exchange/match_orders.ts @@ -1,6 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { assetProxyUtils } from '@0xproject/order-utils'; -import { AssetProxyId } from '@0xproject/types'; +import { AssetProxyId, RevertReasons } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; @@ -12,7 +12,7 @@ import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c2 import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy'; import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange'; import { artifacts } from '../../src/utils/artifacts'; -import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; +import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -81,7 +81,10 @@ describe('matchOrders', () => { erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); // Deploy ERC20 token & ERC20 proxy - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(); + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + constants.NUM_DUMMY_ERC20_TO_DEPLOY, + constants.DUMMY_TOKEN_DECIMALS, + ); erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); // Deploy ERC721 token and proxy @@ -598,8 +601,9 @@ describe('matchOrders', () => { // Cancel left order await exchangeWrapper.cancelOrderAsync(signedOrderLeft, signedOrderLeft.makerAddress); // Match orders - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), + RevertReasons.OrderUnfillable, ); }); @@ -622,8 +626,9 @@ describe('matchOrders', () => { // Cancel right order await exchangeWrapper.cancelOrderAsync(signedOrderRight, signedOrderRight.makerAddress); // Match orders - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, takerAddress), + RevertReasons.OrderUnfillable, ); }); @@ -644,7 +649,7 @@ describe('matchOrders', () => { feeRecipientAddress: feeRecipientAddressRight, }); // Match orders - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, @@ -652,6 +657,7 @@ describe('matchOrders', () => { erc20BalancesByOwner, erc721TokenIdsByOwner, ), + RevertReasons.NegativeSpreadRequired, ); }); @@ -672,7 +678,7 @@ describe('matchOrders', () => { feeRecipientAddress: feeRecipientAddressRight, }); // Match orders - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, @@ -680,6 +686,11 @@ describe('matchOrders', () => { erc20BalancesByOwner, erc721TokenIdsByOwner, ), + // We are assuming assetData fields of the right order are the + // reverse of the left order, rather than checking equality. This + // saves a bunch of gas, but as a result if the assetData fields are + // off then the failure ends up happening at signature validation + RevertReasons.InvalidOrderSignature, ); }); @@ -702,7 +713,7 @@ describe('matchOrders', () => { feeRecipientAddress: feeRecipientAddressRight, }); // Match orders - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( matchOrderTester.matchOrdersAndVerifyBalancesAsync( signedOrderLeft, signedOrderRight, @@ -710,6 +721,7 @@ describe('matchOrders', () => { erc20BalancesByOwner, erc721TokenIdsByOwner, ), + RevertReasons.InvalidOrderSignature, ); }); diff --git a/packages/contracts/test/exchange/signature_validator.ts b/packages/contracts/test/exchange/signature_validator.ts index 8e221e3f1..c610ead61 100644 --- a/packages/contracts/test/exchange/signature_validator.ts +++ b/packages/contracts/test/exchange/signature_validator.ts @@ -1,6 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { addSignedMessagePrefix, assetProxyUtils, MessagePrefixType, orderHashUtils } from '@0xproject/order-utils'; -import { SignatureType, SignedOrder } from '@0xproject/types'; +import { RevertReasons, SignatureType, SignedOrder } from '@0xproject/types'; import * as chai from 'chai'; import { LogWithDecodedArgs } from 'ethereum-types'; import ethUtil = require('ethereumjs-util'); @@ -107,7 +107,7 @@ describe('MixinSignatureValidator', () => { signedOrder.makerAddress, emptySignature, ), - constants.EXCHANGE_LENGTH_GREATER_THAN_0_REQUIRED, + RevertReasons.LengthGreaterThan0Required, ); }); @@ -121,7 +121,7 @@ describe('MixinSignatureValidator', () => { signedOrder.makerAddress, unsupportedSignatureHex, ), - constants.EXCHANGE_SIGNATURE_UNSUPPORTED, + RevertReasons.SignatureUnsupported, ); }); @@ -134,7 +134,7 @@ describe('MixinSignatureValidator', () => { signedOrder.makerAddress, unsupportedSignatureHex, ), - constants.EXCHANGE_SIGNATURE_ILLEGAL, + RevertReasons.SignatureIllegal, ); }); @@ -161,7 +161,7 @@ describe('MixinSignatureValidator', () => { signedOrder.makerAddress, signatureHex, ), - constants.EXCHANGE_LENGTH_0_REQUIRED, + RevertReasons.Length0Required, ); }); diff --git a/packages/contracts/test/exchange/transactions.ts b/packages/contracts/test/exchange/transactions.ts index 43b9f46b2..cfabe0435 100644 --- a/packages/contracts/test/exchange/transactions.ts +++ b/packages/contracts/test/exchange/transactions.ts @@ -1,6 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils'; -import { AssetProxyId, OrderWithoutExchangeAddress, SignedOrder } from '@0xproject/types'; +import { AssetProxyId, OrderWithoutExchangeAddress, RevertReasons, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; @@ -10,7 +10,7 @@ import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange import { ExchangeWrapperContract } from '../../src/generated_contract_wrappers/exchange_wrapper'; import { WhitelistContract } from '../../src/generated_contract_wrappers/whitelist'; import { artifacts } from '../../src/utils/artifacts'; -import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; +import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -59,13 +59,22 @@ describe('Exchange transactions', () => { after(async () => { await blockchainLifecycle.revertAsync(); }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); before(async () => { const accounts = await web3Wrapper.getAvailableAddressesAsync(); const usedAddresses = ([owner, senderAddress, makerAddress, takerAddress, feeRecipientAddress] = accounts); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(); + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + constants.NUM_DUMMY_ERC20_TO_DEPLOY, + constants.DUMMY_TOKEN_DECIMALS, + ); erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); @@ -101,13 +110,6 @@ describe('Exchange transactions', () => { makerTransactionFactory = new TransactionFactory(makerPrivateKey, exchange.address); takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchange.address); }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe('executeTransaction', () => { describe('fillOrder', () => { let takerAssetFillAmount: BigNumber; @@ -126,8 +128,9 @@ describe('Exchange transactions', () => { }); it('should throw if not called by specified sender', async () => { - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.executeTransactionAsync(signedTx, takerAddress), + RevertReasons.FailedExecution, ); }); @@ -168,8 +171,9 @@ describe('Exchange transactions', () => { it('should throw if the a 0x transaction with the same transactionHash has already been executed', async () => { await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.executeTransactionAsync(signedTx, senderAddress), + RevertReasons.InvalidTxHash, ); }); @@ -187,15 +191,17 @@ describe('Exchange transactions', () => { }); it('should throw if not called by specified sender', async () => { - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.executeTransactionAsync(signedTx, makerAddress), + RevertReasons.FailedExecution, ); }); it('should cancel the order when signed by maker and called by sender', async () => { await exchangeWrapper.executeTransactionAsync(signedTx, senderAddress); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrderAsync(signedOrder, senderAddress), + RevertReasons.OrderUnfillable, ); }); }); @@ -238,7 +244,7 @@ describe('Exchange transactions', () => { signedOrder.signature, ); const signedFillTx = takerTransactionFactory.newSignedTransaction(fillData); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapperContract.fillOrder.sendTransactionAsync( orderWithoutExchangeAddress, takerAssetFillAmount, @@ -247,6 +253,7 @@ describe('Exchange transactions', () => { signedFillTx.signature, { from: takerAddress }, ), + RevertReasons.FailedExecution, ); }); @@ -357,7 +364,7 @@ describe('Exchange transactions', () => { orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); const takerAssetFillAmount = signedOrder.takerAssetAmount; const salt = generatePseudoRandomSalt(); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( whitelist.fillOrderIfWhitelisted.sendTransactionAsync( orderWithoutExchangeAddress, takerAssetFillAmount, @@ -365,6 +372,7 @@ describe('Exchange transactions', () => { signedOrder.signature, { from: takerAddress }, ), + RevertReasons.MakerNotWhitelisted, ); }); @@ -378,7 +386,7 @@ describe('Exchange transactions', () => { orderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(signedOrder); const takerAssetFillAmount = signedOrder.takerAssetAmount; const salt = generatePseudoRandomSalt(); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( whitelist.fillOrderIfWhitelisted.sendTransactionAsync( orderWithoutExchangeAddress, takerAssetFillAmount, @@ -386,6 +394,7 @@ describe('Exchange transactions', () => { signedOrder.signature, { from: takerAddress }, ), + RevertReasons.TakerNotWhitelisted, ); }); diff --git a/packages/contracts/test/exchange/wrapper.ts b/packages/contracts/test/exchange/wrapper.ts index a39a81057..7e3203291 100644 --- a/packages/contracts/test/exchange/wrapper.ts +++ b/packages/contracts/test/exchange/wrapper.ts @@ -1,6 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { assetProxyUtils } from '@0xproject/order-utils'; -import { AssetProxyId, SignedOrder } from '@0xproject/types'; +import { AssetProxyId, RevertReasons, SignedOrder } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import { Web3Wrapper } from '@0xproject/web3-wrapper'; import * as chai from 'chai'; @@ -12,7 +12,7 @@ import { ERC20ProxyContract } from '../../src/generated_contract_wrappers/e_r_c2 import { ERC721ProxyContract } from '../../src/generated_contract_wrappers/e_r_c721_proxy'; import { ExchangeContract } from '../../src/generated_contract_wrappers/exchange'; import { artifacts } from '../../src/utils/artifacts'; -import { expectRevertOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; +import { expectRevertReasonOrAlwaysFailingTransactionAsync } from '../../src/utils/assertions'; import { chaiSetup } from '../../src/utils/chai_setup'; import { constants } from '../../src/utils/constants'; import { ERC20Wrapper } from '../../src/utils/erc20_wrapper'; @@ -65,7 +65,10 @@ describe('Exchange wrappers', () => { erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync(); + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + constants.NUM_DUMMY_ERC20_TO_DEPLOY, + constants.DUMMY_TOKEN_DECIMALS, + ); erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); @@ -165,13 +168,14 @@ describe('Exchange wrappers', () => { ); }); - it('should throw if an signedOrder is expired', async () => { + it('should throw if a signedOrder is expired', async () => { const signedOrder = orderFactory.newSignedOrder({ expirationTimeSeconds: new BigNumber(Math.floor((Date.now() - 10000) / 1000)), }); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), + RevertReasons.OrderUnfillable, ); }); @@ -182,8 +186,9 @@ describe('Exchange wrappers', () => { takerAssetFillAmount: signedOrder.takerAssetAmount.div(2), }); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.fillOrKillOrderAsync(signedOrder, takerAddress), + RevertReasons.CompleteFillFailed, ); }); }); @@ -368,7 +373,7 @@ describe('Exchange wrappers', () => { // HACK(albrow): We need to hardcode the gas estimate here because // the Geth gas estimator doesn't work with the way we use // delegatecall and swallow errors. - gas: 270000, + gas: 280000, }); // Verify post-conditions const newOwnerMakerAsset = await erc721Token.ownerOf.callAsync(makerAssetId); @@ -494,10 +499,11 @@ describe('Exchange wrappers', () => { await exchangeWrapper.fillOrKillOrderAsync(signedOrders[0], takerAddress); - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.batchFillOrKillOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmounts, }), + RevertReasons.OrderUnfillable, ); }); }); @@ -687,7 +693,7 @@ describe('Exchange wrappers', () => { expect(newBalances).to.be.deep.equal(erc20Balances); }); - it('should throw when an signedOrder does not use the same takerAssetAddress', async () => { + it('should throw when a signedOrder does not use the same takerAssetAddress', async () => { signedOrders = [ orderFactory.newSignedOrder(), orderFactory.newSignedOrder({ @@ -696,10 +702,13 @@ describe('Exchange wrappers', () => { orderFactory.newSignedOrder(), ]; - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.marketSellOrdersAsync(signedOrders, takerAddress, { takerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), }), + // We simply use the takerAssetData from the first order for all orders. + // If they are not the same, the contract throws when validating the order signature + RevertReasons.InvalidOrderSignature, ); }); }); @@ -902,7 +911,7 @@ describe('Exchange wrappers', () => { expect(newBalances).to.be.deep.equal(erc20Balances); }); - it('should throw when an signedOrder does not use the same makerAssetAddress', async () => { + it('should throw when a signedOrder does not use the same makerAssetAddress', async () => { signedOrders = [ orderFactory.newSignedOrder(), orderFactory.newSignedOrder({ @@ -911,10 +920,11 @@ describe('Exchange wrappers', () => { orderFactory.newSignedOrder(), ]; - return expectRevertOrAlwaysFailingTransactionAsync( + return expectRevertReasonOrAlwaysFailingTransactionAsync( exchangeWrapper.marketBuyOrdersAsync(signedOrders, takerAddress, { makerAssetFillAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), 18), }), + RevertReasons.InvalidOrderSignature, ); }); }); diff --git a/packages/contracts/test/libraries/lib_bytes.ts b/packages/contracts/test/libraries/lib_bytes.ts index 56f1dc2bc..5ab299b73 100644 --- a/packages/contracts/test/libraries/lib_bytes.ts +++ b/packages/contracts/test/libraries/lib_bytes.ts @@ -1,5 +1,6 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; import { assetProxyUtils, generatePseudoRandomSalt } from '@0xproject/order-utils'; +import { RevertReasons } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import BN = require('bn.js'); import * as chai from 'chai'; @@ -101,7 +102,7 @@ describe('LibBytes', () => { it('should revert if length is 0', async () => { return expectRevertOrOtherErrorAsync( libBytes.publicPopLastByte.callAsync(constants.NULL_BYTES), - constants.LIB_BYTES_GREATER_THAN_ZERO_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterThanZeroLengthRequired, ); }); it('should pop the last byte from the input and return it', async () => { @@ -117,7 +118,7 @@ describe('LibBytes', () => { it('should revert if length is less than 20', async () => { return expectRevertOrOtherErrorAsync( libBytes.publicPopLast20Bytes.callAsync(byteArrayShorterThan20Bytes), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo20LengthRequired, ); }); it('should pop the last 20 bytes from the input and return it', async () => { @@ -185,7 +186,7 @@ describe('LibBytes', () => { it('should revert if dest is shorter than source', async () => { return expectRevertOrOtherErrorAsync( libBytes.publicDeepCopyBytes.callAsync(byteArrayShorterThan32Bytes, byteArrayLongerThan32Bytes), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_SOURCE_BYTES_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualToSourceBytesLengthRequired, ); }); it('should overwrite dest with source if source and dest have equal length', async () => { @@ -238,7 +239,7 @@ describe('LibBytes', () => { const offset = new BigNumber(0); return expectRevertOrOtherErrorAsync( libBytes.publicReadAddress.callAsync(shortByteArray, offset), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo20LengthRequired, ); }); it('should fail if the length between the offset and end of the byte array is too short to hold an address', async () => { @@ -246,7 +247,7 @@ describe('LibBytes', () => { const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength); return expectRevertOrOtherErrorAsync( libBytes.publicReadAddress.callAsync(byteArray, badOffset), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo20LengthRequired, ); }); }); @@ -282,7 +283,7 @@ describe('LibBytes', () => { const offset = new BigNumber(0); return expectRevertOrOtherErrorAsync( libBytes.publicWriteAddress.callAsync(byteArrayShorterThan20Bytes, offset, testAddress), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo20LengthRequired, ); }); it('should fail if the length between the offset and end of the byte array is too short to hold an address', async () => { @@ -290,7 +291,7 @@ describe('LibBytes', () => { const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength); return expectRevertOrOtherErrorAsync( libBytes.publicWriteAddress.callAsync(byteArray, badOffset, testAddress), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_20_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo20LengthRequired, ); }); }); @@ -314,14 +315,14 @@ describe('LibBytes', () => { const offset = new BigNumber(0); return expectRevertOrOtherErrorAsync( libBytes.publicReadBytes32.callAsync(byteArrayShorterThan32Bytes, offset), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32', async () => { const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength); return expectRevertOrOtherErrorAsync( libBytes.publicReadBytes32.callAsync(testBytes32, badOffset), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); }); @@ -357,7 +358,7 @@ describe('LibBytes', () => { const offset = new BigNumber(0); return expectRevertOrOtherErrorAsync( libBytes.publicWriteBytes32.callAsync(byteArrayShorterThan32Bytes, offset, testBytes32), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); it('should fail if the length between the offset and end of the byte array is too short to hold a bytes32', async () => { @@ -365,7 +366,7 @@ describe('LibBytes', () => { const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength); return expectRevertOrOtherErrorAsync( libBytes.publicWriteBytes32.callAsync(byteArray, badOffset, testBytes32), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); }); @@ -393,7 +394,7 @@ describe('LibBytes', () => { const offset = new BigNumber(0); return expectRevertOrOtherErrorAsync( libBytes.publicReadUint256.callAsync(byteArrayShorterThan32Bytes, offset), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); it('should fail if the length between the offset and end of the byte array is too short to hold a uint256', async () => { @@ -403,7 +404,7 @@ describe('LibBytes', () => { const badOffset = new BigNumber(testUint256AsBuffer.byteLength); return expectRevertOrOtherErrorAsync( libBytes.publicReadUint256.callAsync(byteArray, badOffset), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); }); @@ -443,7 +444,7 @@ describe('LibBytes', () => { const offset = new BigNumber(0); return expectRevertOrOtherErrorAsync( libBytes.publicWriteUint256.callAsync(byteArrayShorterThan32Bytes, offset, testUint256), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); it('should fail if the length between the offset and end of the byte array is too short to hold a uint256', async () => { @@ -451,7 +452,7 @@ describe('LibBytes', () => { const badOffset = new BigNumber(ethUtil.toBuffer(byteArray).byteLength); return expectRevertOrOtherErrorAsync( libBytes.publicWriteUint256.callAsync(byteArray, badOffset, testUint256), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); }); @@ -462,7 +463,7 @@ describe('LibBytes', () => { const byteArrayLessThan4Bytes = '0x010101'; return expectRevertOrOtherErrorAsync( libBytes.publicReadBytes4.callAsync(byteArrayLessThan4Bytes, new BigNumber(0)), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_4_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo4LengthRequired, ); }); it('should return the first 4 bytes of a byte array of arbitrary length', async () => { @@ -517,28 +518,28 @@ describe('LibBytes', () => { const offset = new BigNumber(0); return expectRevertOrOtherErrorAsync( libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, offset), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); it('should fail if we store a nested byte array length, without a nested byte array', async () => { const offset = new BigNumber(0); return expectRevertOrOtherErrorAsync( libBytes.publicReadBytesWithLength.callAsync(testBytes32, offset), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualToNestedBytesLengthRequired, ); }); it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array', async () => { const badOffset = new BigNumber(ethUtil.toBuffer(byteArrayShorterThan32Bytes).byteLength); return expectRevertOrOtherErrorAsync( libBytes.publicReadBytesWithLength.callAsync(byteArrayShorterThan32Bytes, badOffset), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); it('should fail if the length between the offset and end of the byte array is too short to hold the nested byte array', async () => { const badOffset = new BigNumber(ethUtil.toBuffer(testBytes32).byteLength); return expectRevertOrOtherErrorAsync( libBytes.publicReadBytesWithLength.callAsync(testBytes32, badOffset), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_32_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualTo32LengthRequired, ); }); }); @@ -650,7 +651,7 @@ describe('LibBytes', () => { const emptyByteArray = ethUtil.bufferToHex(new Buffer(1)); return expectRevertOrOtherErrorAsync( libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, offset, longData), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualToNestedBytesLengthRequired, ); }); it('should fail if the length between the offset and end of the byte array is too short to hold the length of a nested byte array)', async () => { @@ -658,7 +659,7 @@ describe('LibBytes', () => { const badOffset = new BigNumber(ethUtil.toBuffer(shortTestBytesAsBuffer).byteLength); return expectRevertOrOtherErrorAsync( libBytes.publicWriteBytesWithLength.callAsync(emptyByteArray, badOffset, shortData), - constants.LIB_BYTES_GREATER_OR_EQUAL_TO_NESTED_BYTES_LENGTH_REQUIRED, + RevertReasons.LibBytesGreaterOrEqualToNestedBytesLengthRequired, ); }); }); diff --git a/packages/contracts/test/unlimited_allowance_token.ts b/packages/contracts/test/unlimited_allowance_token.ts index 7132c57bf..6ad59d4f1 100644 --- a/packages/contracts/test/unlimited_allowance_token.ts +++ b/packages/contracts/test/unlimited_allowance_token.ts @@ -1,4 +1,5 @@ import { BlockchainLifecycle } from '@0xproject/dev-utils'; +import { RevertReasons } from '@0xproject/types'; import { BigNumber } from '@0xproject/utils'; import * as chai from 'chai'; @@ -55,7 +56,7 @@ describe('UnlimitedAllowanceToken', () => { const amountToTransfer = ownerBalance.plus(1); return expectRevertOrOtherErrorAsync( token.transfer.callAsync(spender, amountToTransfer, { from: owner }), - constants.ERC20_INSUFFICIENT_BALANCE, + RevertReasons.Erc20InsufficientBalance, ); }); @@ -96,7 +97,7 @@ describe('UnlimitedAllowanceToken', () => { token.transferFrom.callAsync(owner, spender, amountToTransfer, { from: spender, }), - constants.ERC20_INSUFFICIENT_BALANCE, + RevertReasons.Erc20InsufficientBalance, ); }); @@ -112,7 +113,7 @@ describe('UnlimitedAllowanceToken', () => { token.transferFrom.callAsync(owner, spender, amountToTransfer, { from: spender, }), - constants.ERC20_INSUFFICIENT_ALLOWANCE, + RevertReasons.Erc20InsufficientAllowance, ); }); |