Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Commit

Permalink
add unit tests, use DevUtils
Browse files Browse the repository at this point in the history
  • Loading branch information
xianny committed Sep 5, 2019
1 parent 31aa55f commit 7eae251
Show file tree
Hide file tree
Showing 10 changed files with 4,056 additions and 4,857 deletions.
9 changes: 9 additions & 0 deletions packages/contract-addresses/CHANGELOG.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
[
{
"version": "3.2.0",
"changes": [
{
"note": "Added `getNetworkIdByExchangeAddressOrThrow`",
"pr": 2096
}
]
},
{
"version": "3.1.0",
"changes": [
Expand Down
18 changes: 18 additions & 0 deletions packages/contract-addresses/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,21 @@ export function getContractAddressesForNetworkOrThrow(networkId: NetworkId): Con
}
return networkToAddresses[networkId];
}

/**
* Uses a given exchange address to look up the network id that the exchange contract is deployed
* on. Only works for Ethereum mainnet or a supported testnet. Throws if the exchange address
* does not correspond to a known deployed exchange contract.
* @param exchangeAddress The exchange address of concern
* @returns The network ID on which the exchange contract is deployed
*/
export function getNetworkIdByExchangeAddressOrThrow(exchangeAddress: string): NetworkId {
for (const networkId of Object.keys(networkToAddresses)) {
if (networkToAddresses[networkId as any].exchange === exchangeAddress) {
return networkId as any;
}
}
throw new Error(
`Unknown exchange address (${exchangeAddress}). No known 0x Exchange Contract deployed at this address.`,
);
}
5 changes: 4 additions & 1 deletion packages/order-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"test": "yarn run_mocha",
"rebuild_and_test": "run-s build test",
"test:circleci": "yarn test:coverage",
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit",
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --timeout 10000 --bail --exit",
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
"clean": "shx rm -rf lib generated_docs",
Expand Down Expand Up @@ -66,7 +66,10 @@
"@0x/base-contract": "^5.3.3",
"@0x/contract-addresses": "^3.1.0",
"@0x/contract-artifacts": "^2.2.1",
"@0x/dev-utils": "^2.3.2",
"@0x/json-schemas": "^4.0.1",
"@0x/migrations": "^4.3.1",
"@0x/subproviders": "^5.0.3",
"@0x/types": "^2.4.2",
"@0x/typescript-typings": "^4.2.5",
"@0x/utils": "^4.5.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,101 +1,20 @@
import { ERC20ProxyContract, ERC20TokenContract, ERC721ProxyContract, ERC721TokenContract } from '@0x/abi-gen-wrappers';
import { DevUtilsContract } from '@0x/abi-gen-wrappers';
import { BigNumber } from '@0x/utils';
import { BlockParamLiteral, SupportedProvider } from 'ethereum-types';
import * as _ from 'lodash';

import { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
import { assetDataUtils } from './asset_data_utils';

const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers

export class AssetBalanceAndProxyAllowanceFetcher implements AbstractBalanceAndProxyAllowanceFetcher {
private readonly _stateLayer: BlockParamLiteral;
private readonly _erc20Proxy: ERC20ProxyContract;
private readonly _erc721Proxy: ERC721ProxyContract;
private readonly _provider: SupportedProvider;
constructor(
erc20Proxy: ERC20ProxyContract,
erc721Proxy: ERC721ProxyContract,
provider: SupportedProvider,
stateLayer: BlockParamLiteral,
) {
this._erc20Proxy = erc20Proxy;
this._erc721Proxy = erc721Proxy;
this._stateLayer = stateLayer;
this._provider = provider;
private readonly _devUtilsContract: DevUtilsContract;
constructor(devUtilsContract: DevUtilsContract) {
this._devUtilsContract = devUtilsContract;
}
public async getBalanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
let balance: BigNumber | undefined;
const defaultBlock = this._stateLayer;
const callData = {};
if (assetDataUtils.isERC20AssetData(decodedAssetData)) {
const erc20Token = new ERC20TokenContract(decodedAssetData.tokenAddress, this._provider);
balance = await erc20Token.balanceOf.callAsync(userAddress, {}, defaultBlock);
} else if (assetDataUtils.isERC721AssetData(decodedAssetData)) {
const erc721Token = new ERC721TokenContract(decodedAssetData.tokenAddress, this._provider);
const tokenOwner = await erc721Token.ownerOf.callAsync(decodedAssetData.tokenId, callData, defaultBlock);
balance = tokenOwner === userAddress ? new BigNumber(1) : new BigNumber(0);
} else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
// The `balance` for MultiAssetData is the total units of the entire `assetData` that are held by the `userAddress`.
for (const [index, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) {
const nestedAmountElement = decodedAssetData.amounts[index];
const nestedAssetBalance = (await this.getBalanceAsync(
nestedAssetDataElement,
userAddress,
)).dividedToIntegerBy(nestedAmountElement);
if (balance === undefined || nestedAssetBalance.isLessThan(balance)) {
balance = nestedAssetBalance;
}
}
}
return balance as BigNumber;
const balance = await this._devUtilsContract.getBalance.callAsync(userAddress, assetData);
return balance;
}
public async getProxyAllowanceAsync(assetData: string, userAddress: string): Promise<BigNumber> {
const decodedAssetData = assetDataUtils.decodeAssetDataOrThrow(assetData);
let proxyAllowance: BigNumber | undefined;
const defaultBlock = this._stateLayer;
const callData = {};
if (assetDataUtils.isERC20AssetData(decodedAssetData)) {
const erc20Token = new ERC20TokenContract(decodedAssetData.tokenAddress, this._provider);
proxyAllowance = await erc20Token.allowance.callAsync(
userAddress,
this._erc20Proxy.address,
callData,
defaultBlock,
);
} else if (assetDataUtils.isERC721AssetData(decodedAssetData)) {
const erc721Token = new ERC721TokenContract(decodedAssetData.tokenAddress, this._provider);
const isApprovedForAll = await erc721Token.isApprovedForAll.callAsync(
userAddress,
this._erc721Proxy.address,
callData,
defaultBlock,
);
if (isApprovedForAll) {
return UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
} else {
const approvedAddress = await erc721Token.getApproved.callAsync(
decodedAssetData.tokenId,
callData,
defaultBlock,
);
const isApproved = approvedAddress === this._erc721Proxy.address;
proxyAllowance = isApproved ? new BigNumber(1) : new BigNumber(0);
}
} else if (assetDataUtils.isMultiAssetData(decodedAssetData)) {
// The `proxyAllowance` for MultiAssetData is the total units of the entire `assetData` that the proxies have been approved to spend by the `userAddress`.
for (const [index, nestedAssetDataElement] of decodedAssetData.nestedAssetData.entries()) {
const nestedAmountElement = decodedAssetData.amounts[index];
const nestedAssetAllowance = (await this.getProxyAllowanceAsync(
nestedAssetDataElement,
userAddress,
)).dividedToIntegerBy(nestedAmountElement);
if (proxyAllowance === undefined || nestedAssetAllowance.isLessThan(proxyAllowance)) {
proxyAllowance = nestedAssetAllowance;
}
}
}
return proxyAllowance as BigNumber;
const proxyAllowance = await this._devUtilsContract.getAssetProxyAllowance.callAsync(userAddress, assetData);
return proxyAllowance;
}
}
71 changes: 13 additions & 58 deletions packages/order-utils/src/order_validation_utils.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {
ERC20ProxyContract,
ERC721ProxyContract,
DevUtilsContract,
ExchangeContract,
getContractAddressesForNetworkOrThrow,
IAssetProxyContract,
NetworkId,
} from '@0x/abi-gen-wrappers';
import { assert } from '@0x/assert';
import { getNetworkIdByExchangeAddressOrThrow } from '@0x/contract-addresses';
import { ExchangeContractErrs, RevertReason, SignedOrder } from '@0x/types';
import { BigNumber, providerUtils } from '@0x/utils';
import { BlockParamLiteral, SupportedProvider, ZeroExProvider } from 'ethereum-types';
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
import * as _ from 'lodash';

import { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
Expand Down Expand Up @@ -187,39 +187,30 @@ export class OrderValidationUtils {
// to make migrating easier in the interim.
/**
* Validate if the supplied order is fillable, and throw if it isn't
* @param erc20ProxyAddress A valid address where an ERC20ProxyContract is deployed
* @param erc721ProxyAddress A valid address where an ERC721ProxyContract is deployed
* @param exchangeAddress A valid address where the ExchangeContract is deployed
* @param zrxTokenAddress A valid address where ZRXTokenContract is deployed
* @param networkId The network for all contracts
* @param provider The same provider used to interact with contracts
* @param signedOrder SignedOrder of interest
* @param opts ValidateOrderFillableOpts options (e.g expectedFillTakerTokenAmount.
* If it isn't supplied, we check if the order is fillable for the remaining amount.
* To check if the order is fillable for a non-zero amount, set `validateRemainingOrderAmountIsFillable` to false.)
*/
public async simpleValidateOrderFillableOrThrowAsync(
erc20ProxyAddress: string,
erc721ProxyAddress: string,
exchangeAddress: string,
zrxTokenAddress: string,
networkId: number,
provider: SupportedProvider,
signedOrder: SignedOrder,
opts: ValidateOrderFillableOpts = {},
): Promise<void> {
assert.doesConformToSchema('opts', opts, validateOrderFillableOptsSchema);

const exchange = new ExchangeContract(exchangeAddress, provider);
const exchangeAddress = signedOrder.exchangeAddress;
const networkId = getNetworkIdByExchangeAddressOrThrow(exchangeAddress);
const { zrxToken, devUtils } = getContractAddressesForNetworkOrThrow(networkId);
const exchangeContract = new ExchangeContract(exchangeAddress, provider);
const balanceAllowanceFetcher = new AssetBalanceAndProxyAllowanceFetcher(
new ERC20ProxyContract(erc20ProxyAddress, provider),
new ERC721ProxyContract(erc721ProxyAddress, provider),
provider,
BlockParamLiteral.Latest,
new DevUtilsContract(devUtils, provider),
);
const balanceAllowanceStore = new BalanceAndProxyAllowanceLazyStore(balanceAllowanceFetcher);
const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore);

// Define fillable taker asset amount
let fillableTakerAssetAmount;
const shouldValidateRemainingOrderAmountIsFillable =
opts.validateRemainingOrderAmountIsFillable === undefined
Expand All @@ -230,7 +221,9 @@ export class OrderValidationUtils {
fillableTakerAssetAmount = opts.expectedFillTakerTokenAmount;
} else if (shouldValidateRemainingOrderAmountIsFillable) {
// Default behaviour is to validate the amount left on the order.
const filledTakerTokenAmount = await exchange.filled.callAsync(orderHashUtils.getOrderHashHex(signedOrder));
const filledTakerTokenAmount = await exchangeContract.filled.callAsync(
orderHashUtils.getOrderHashHex(signedOrder),
);
fillableTakerAssetAmount = signedOrder.takerAssetAmount.minus(filledTakerTokenAmount);
} else {
const orderStateUtils = new OrderStateUtils(balanceAllowanceStore, this._orderFilledCancelledFetcher);
Expand All @@ -242,7 +235,7 @@ export class OrderValidationUtils {
await this.validateOrderFillableOrThrowAsync(
exchangeTradeSimulator,
signedOrder,
assetDataUtils.encodeERC20AssetData(zrxTokenAddress),
assetDataUtils.encodeERC20AssetData(zrxToken),
fillableTakerAssetAmount,
);
const makerTransferAmount = orderCalculationUtils.getMakerFillAmount(signedOrder, fillableTakerAssetAmount);
Expand Down Expand Up @@ -308,44 +301,6 @@ export class OrderValidationUtils {
zrxAssetData,
);
}
/**
* Validate a call to FillOrder and throw if it wouldn't succeed. This
* calls validateFillOrderThrowIfInvalidAsync under the hood
* @param erc20ProxyAddress A valid address where an ERC20ProxyContract is deployed
* @param erc721ProxyAddress A valid address where an ERC721ProxyContract is deployed
* @param zrxTokenAddress A valid address where ZRXTokenContract is deployed
* @param provider The same provider used to interact with contracts
* @param signedOrder SignedOrder of interest
* @param fillTakerAssetAmount Amount we'd like to fill the order for
* @param takerAddress The taker of the order
*/
public async simpleValidateFillOrderThrowIfInvalidAsync(
erc20ProxyAddress: string,
erc721ProxyAddress: string,
zrxTokenAddress: string,
provider: SupportedProvider,
signedOrder: SignedOrder,
fillTakerAssetAmount: BigNumber,
takerAddress: string,
): Promise<void> {
const balanceAllowanceFetcher = new AssetBalanceAndProxyAllowanceFetcher(
new ERC20ProxyContract(erc20ProxyAddress, provider),
new ERC721ProxyContract(erc721ProxyAddress, provider),
provider,
BlockParamLiteral.Latest,
);
const balanceAllowanceStore = new BalanceAndProxyAllowanceLazyStore(balanceAllowanceFetcher);
const exchangeTradeSimulator = new ExchangeTransferSimulator(balanceAllowanceStore);

await this.validateFillOrderThrowIfInvalidAsync(
exchangeTradeSimulator,
provider,
signedOrder,
fillTakerAssetAmount,
takerAddress,
assetDataUtils.encodeERC20AssetData(zrxTokenAddress),
);
}
/**
* Validate a call to FillOrder and throw if it wouldn't succeed
* @param exchangeTradeEmulator ExchangeTradeEmulator to use
Expand Down
4 changes: 4 additions & 0 deletions packages/order-utils/test/exchange_transfer_simulator_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('ExchangeTransferSimulator', async () => {
from: devConstants.TESTRPC_FIRST_ADDRESS,
};

await blockchainLifecycle.startAsync();
const erc20Proxy = await ERC20ProxyContract.deployFrom0xArtifactAsync(
artifacts.ERC20Proxy,
provider,
Expand Down Expand Up @@ -74,6 +75,9 @@ describe('ExchangeTransferSimulator', async () => {
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#transferFromAsync', function(): void {
// HACK: For some reason these tests need a slightly longer timeout
const mochaTestTimeoutMs = 3000;
Expand Down
Loading

0 comments on commit 7eae251

Please sign in to comment.