Merge branch 'development' into sol-cov-fixes

This commit is contained in:
Leonid Logvinov
2018-08-09 17:03:41 +02:00
committed by GitHub
84 changed files with 1663 additions and 201 deletions

View File

@@ -80,6 +80,7 @@ jobs:
- run: yarn wsrun test:circleci @0xproject/sra-report
- run: yarn wsrun test:circleci @0xproject/subproviders
- run: yarn wsrun test:circleci @0xproject/web3-wrapper
- run: yarn wsrun test:circleci @0xproject/utils
- save_cache:
key: coverage-0xjs-{{ .Environment.CIRCLE_SHA1 }}
paths:

View File

@@ -1,4 +1,12 @@
[
{
"version": "1.0.1-rc.3",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "1.0.1-rc.2",
"changes": [

View File

@@ -94,7 +94,7 @@
"source-map-support": "^0.5.0",
"tslint": "5.11.0",
"typedoc": "0xProject/typedoc",
"typescript": "2.7.1",
"typescript": "2.9.2",
"webpack": "^3.1.0"
},
"dependencies": {

View File

@@ -48,10 +48,11 @@ describe('ZeroEx library', () => {
const ethSignSignature =
'0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403';
const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
const bytes32Zeros = '0x0000000000000000000000000000000000000000000000000000000000000000';
it("should return false if the data doesn't pertain to the signature & address", async () => {
return expect((zeroEx.exchange as any).isValidSignatureAsync('0x0', address, ethSignSignature)).to.become(
false,
);
return expect(
(zeroEx.exchange as any).isValidSignatureAsync(bytes32Zeros, address, ethSignSignature),
).to.become(false);
});
it("should return false if the address doesn't pertain to the signature & data", async () => {
const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42';

View File

@@ -63,7 +63,7 @@
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"publishConfig": {
"access": "public"

View File

@@ -44,7 +44,7 @@
"nyc": "^11.0.1",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@0xproject/json-schemas": "^1.0.1-rc.3",

View File

@@ -1,4 +1,13 @@
[
{
"version": "2.0.0-rc.1",
"changes": [
{
"pr": 915,
"note": "Added strict encoding/decoding checks for sendTransaction and call"
}
]
},
{
"timestamp": 1532619515,
"version": "1.0.4",

View File

@@ -40,7 +40,7 @@
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@0xproject/typescript-typings": "^1.0.3",

View File

@@ -82,6 +82,27 @@ export class BaseContract {
}
return txDataWithDefaults;
}
// Throws if the given arguments cannot be safely/correctly encoded based on
// the given inputAbi. An argument may not be considered safely encodeable
// if it overflows the corresponding Solidity type, there is a bug in the
// encoder, or the encoder performs unsafe type coercion.
public static strictArgumentEncodingCheck(inputAbi: DataItem[], args: any[]): void {
const coder = ethers.utils.AbiCoder.defaultCoder;
const params = abiUtils.parseEthersParams(inputAbi);
const rawEncoded = coder.encode(params.names, params.types, args);
const rawDecoded = coder.decode(params.names, params.types, rawEncoded);
for (let i = 0; i < rawDecoded.length; i++) {
const original = args[i];
const decoded = rawDecoded[i];
if (!abiUtils.isAbiDataEqual(params.names[i], params.types[i], original, decoded)) {
throw new Error(
`Cannot safely encode argument: ${params.names[i]} (${original}) of type ${
params.types[i]
}. (Possible type overflow or other encoding error)`,
);
}
}
}
protected _lookupEthersInterface(functionSignature: string): ethers.Interface {
const ethersInterface = this._ethersInterfacesByFunctionSignature[functionSignature];
if (_.isUndefined(ethersInterface)) {

View File

@@ -0,0 +1,114 @@
import * as chai from 'chai';
import 'mocha';
import { BaseContract } from '../src';
const { expect } = chai;
describe('BaseContract', () => {
describe('strictArgumentEncodingCheck', () => {
it('works for simple types', () => {
BaseContract.strictArgumentEncodingCheck(
[{ name: 'to', type: 'address' }],
['0xe834ec434daba538cd1b9fe1582052b880bd7e63'],
);
});
it('works for array types', () => {
const inputAbi = [
{
name: 'takerAssetFillAmounts',
type: 'uint256[]',
},
];
const args = [
['9000000000000000000', '79000000000000000000', '979000000000000000000', '7979000000000000000000'],
];
BaseContract.strictArgumentEncodingCheck(inputAbi, args);
});
it('works for tuple/struct types', () => {
const inputAbi = [
{
components: [
{
name: 'makerAddress',
type: 'address',
},
{
name: 'takerAddress',
type: 'address',
},
{
name: 'feeRecipientAddress',
type: 'address',
},
{
name: 'senderAddress',
type: 'address',
},
{
name: 'makerAssetAmount',
type: 'uint256',
},
{
name: 'takerAssetAmount',
type: 'uint256',
},
{
name: 'makerFee',
type: 'uint256',
},
{
name: 'takerFee',
type: 'uint256',
},
{
name: 'expirationTimeSeconds',
type: 'uint256',
},
{
name: 'salt',
type: 'uint256',
},
{
name: 'makerAssetData',
type: 'bytes',
},
{
name: 'takerAssetData',
type: 'bytes',
},
],
name: 'order',
type: 'tuple',
},
];
const args = [
{
makerAddress: '0x6ecbe1db9ef729cbe972c83fb886247691fb6beb',
takerAddress: '0x0000000000000000000000000000000000000000',
feeRecipientAddress: '0xe834ec434daba538cd1b9fe1582052b880bd7e63',
senderAddress: '0x0000000000000000000000000000000000000000',
makerAssetAmount: '0',
takerAssetAmount: '200000000000000000000',
makerFee: '1000000000000000000',
takerFee: '1000000000000000000',
expirationTimeSeconds: '1532563026',
salt: '59342956082154660870994022243365949771115859664887449740907298019908621891376',
makerAssetData: '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48',
takerAssetData: '0xf47261b00000000000000000000000001d7022f5b17d2f8b695918fb48fa1089c9f85401',
},
];
BaseContract.strictArgumentEncodingCheck(inputAbi, args);
});
it('throws for integer overflows', () => {
expect(() =>
BaseContract.strictArgumentEncodingCheck([{ name: 'amount', type: 'uint8' }], ['256']),
).to.throw();
});
it('throws for fixed byte array overflows', () => {
expect(() =>
BaseContract.strictArgumentEncodingCheck([{ name: 'hash', type: 'bytes8' }], ['0x001122334455667788']),
).to.throw();
});
});
});

View File

@@ -83,7 +83,7 @@
"shx": "^0.2.2",
"tslint": "5.11.0",
"typedoc": "~0.8.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"publishConfig": {
"access": "public"

View File

@@ -1,4 +1,21 @@
[
{
"version": "1.0.1-rc.3",
"changes": [
{
"pr": 915,
"note": "Added strict encoding/decoding checks for sendTransaction and call"
},
{
"note": "Add ForwarderWrapper",
"pr": 934
},
{
"note": "Optimize orders in ForwarderWrapper",
"pr": 936
}
]
},
{
"version": "1.0.1-rc.2",
"changes": [

View File

@@ -14,7 +14,7 @@
"watch_without_deps": "yarn pre_build && tsc -w",
"build": "yarn pre_build && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
"pre_build": "run-s update_artifacts_v2_beta update_artifacts_v2 generate_contract_wrappers copy_artifacts",
"generate_contract_wrappers": "abi-gen --abis 'src/artifacts/@(Exchange|DummyERC20Token|DummyERC721Token|ZRXToken|ERC20Token|ERC721Token|WETH9|ERC20Proxy|ERC721Proxy).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers",
"generate_contract_wrappers": "abi-gen --abis 'src/artifacts/@(Exchange|DummyERC20Token|DummyERC721Token|ZRXToken|ERC20Token|ERC721Token|WETH9|ERC20Proxy|ERC721Proxy|Forwarder).json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers",
"lint": "tslint --project . --exclude **/src/contract_wrappers/**/* --exclude **/lib/**/*",
"test:circleci": "run-s test:coverage",
"test": "yarn run_mocha",
@@ -29,7 +29,7 @@
"manual:postpublish": "yarn build; node ./scripts/postpublish.js"
},
"config": {
"contracts_v2_beta": "Exchange ERC20Proxy ERC20Token ERC721Proxy ERC721Token WETH9 ZRXToken",
"contracts_v2_beta": "Exchange ERC20Proxy ERC20Token ERC721Proxy ERC721Token WETH9 ZRXToken Forwarder",
"contracts_v2": "DummyERC20Token DummyERC721Token"
},
"repository": {
@@ -68,7 +68,7 @@
"sinon": "^4.0.0",
"source-map-support": "^0.5.0",
"tslint": "5.11.0",
"typescript": "2.7.1",
"typescript": "2.9.2",
"web3-provider-engine": "14.0.6"
},
"dependencies": {

View File

@@ -7,6 +7,7 @@ import * as ERC20Token from './artifacts/ERC20Token.json';
import * as ERC721Proxy from './artifacts/ERC721Proxy.json';
import * as ERC721Token from './artifacts/ERC721Token.json';
import * as Exchange from './artifacts/Exchange.json';
import * as Forwarder from './artifacts/Forwarder.json';
import * as EtherToken from './artifacts/WETH9.json';
import * as ZRXToken from './artifacts/ZRXToken.json';
@@ -20,4 +21,5 @@ export const artifacts = {
EtherToken: (EtherToken as any) as ContractArtifact,
ERC20Proxy: (ERC20Proxy as any) as ContractArtifact,
ERC721Proxy: (ERC721Proxy as any) as ContractArtifact,
Forwarder: (Forwarder as any) as ContractArtifact,
};

View File

@@ -11,6 +11,7 @@ import { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper';
import { ERC721TokenWrapper } from './contract_wrappers/erc721_token_wrapper';
import { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper';
import { ExchangeWrapper } from './contract_wrappers/exchange_wrapper';
import { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper';
import { ContractWrappersConfigSchema } from './schemas/contract_wrappers_config_schema';
import { contractWrappersPrivateNetworkConfigSchema } from './schemas/contract_wrappers_private_network_config_schema';
import { contractWrappersPublicNetworkConfigSchema } from './schemas/contract_wrappers_public_network_config_schema';
@@ -47,6 +48,11 @@ export class ContractWrappers {
* erc721Proxy smart contract.
*/
public erc721Proxy: ERC721ProxyWrapper;
/**
* An instance of the ForwarderWrapper class containing methods for interacting with any Forwarder smart contract.
*/
public forwarder: ForwarderWrapper;
private _web3Wrapper: Web3Wrapper;
/**
* Instantiates a new ContractWrappers instance.
@@ -104,6 +110,12 @@ export class ContractWrappers {
config.zrxContractAddress,
blockPollingIntervalMs,
);
this.forwarder = new ForwarderWrapper(
this._web3Wrapper,
config.networkId,
config.forwarderContractAddress,
config.zrxContractAddress,
);
}
/**
* Sets a new web3 provider for 0x.js. Updating the provider will stop all

View File

@@ -869,15 +869,35 @@ export class ExchangeWrapper extends ContractWrapper {
*/
@decorators.asyncZeroExErrorHandler
public async getOrderInfoAsync(order: Order | SignedOrder, methodOpts: MethodOpts = {}): Promise<OrderInfo> {
assert.doesConformToSchema('order', order, schemas.orderSchema);
if (!_.isUndefined(methodOpts)) {
assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
}
const exchangeInstance = await this._getExchangeContractAsync();
const txData = {};
const orderInfo = await exchangeInstance.getOrderInfo.callAsync(order, txData, methodOpts.defaultBlock);
return orderInfo;
}
/**
* Get order info for multiple orders
* @param orders Orders
* @param methodOpts Optional arguments this method accepts.
* @returns Array of Order infos
*/
@decorators.asyncZeroExErrorHandler
public async getOrdersInfoAsync(
orders: Array<Order | SignedOrder>,
methodOpts: MethodOpts = {},
): Promise<OrderInfo[]> {
assert.doesConformToSchema('orders', orders, schemas.ordersSchema);
if (!_.isUndefined(methodOpts)) {
assert.doesConformToSchema('methodOpts', methodOpts, methodOptsSchema);
}
const exchangeInstance = await this._getExchangeContractAsync();
const txData = {};
const ordersInfo = await exchangeInstance.getOrdersInfo.callAsync(orders, txData, methodOpts.defaultBlock);
return ordersInfo;
}
/**
* Cancel a given order.
* @param order An object that conforms to the Order or SignedOrder interface. The order you would like to cancel.

View File

@@ -0,0 +1,220 @@
import { schemas } from '@0xproject/json-schemas';
import { AssetProxyId, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { ContractAbi } from 'ethereum-types';
import * as _ from 'lodash';
import { artifacts } from '../artifacts';
import { orderTxOptsSchema } from '../schemas/order_tx_opts_schema';
import { txOptsSchema } from '../schemas/tx_opts_schema';
import { TransactionOpts } from '../types';
import { assert } from '../utils/assert';
import { calldataOptimizationUtils } from '../utils/calldata_optimization_utils';
import { constants } from '../utils/constants';
import { ContractWrapper } from './contract_wrapper';
import { ForwarderContract } from './generated/forwarder';
/**
* This class includes the functionality related to interacting with the Forwarder contract.
*/
export class ForwarderWrapper extends ContractWrapper {
public abi: ContractAbi = artifacts.Forwarder.compilerOutput.abi;
private _forwarderContractIfExists?: ForwarderContract;
private _contractAddressIfExists?: string;
private _zrxContractAddressIfExists?: string;
constructor(
web3Wrapper: Web3Wrapper,
networkId: number,
contractAddressIfExists?: string,
zrxContractAddressIfExists?: string,
) {
super(web3Wrapper, networkId);
this._contractAddressIfExists = contractAddressIfExists;
this._zrxContractAddressIfExists = zrxContractAddressIfExists;
}
/**
* Purchases as much of orders' makerAssets as possible by selling up to 95% of transaction's ETH value.
* Any ZRX required to pay fees for primary orders will automatically be purchased by this contract.
* 5% of ETH value is reserved for paying fees to order feeRecipients (in ZRX) and forwarding contract feeRecipient (in ETH).
* Any ETH not spent will be refunded to sender.
* @param signedOrders An array of objects that conform to the SignedOrder interface. All orders must specify the same makerAsset.
* All orders must specify WETH as the takerAsset
* @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied
* Provider provided at instantiation.
* @param ethAmount The amount of eth to send with the transaction (in wei).
* @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders must specify ZRX as makerAsset and WETH as takerAsset.
* Used to purchase ZRX for primary order fees.
* @param feePercentage The percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
* Defaults to 0.
* @param feeRecipientAddress The address that will receive ETH when signedFeeOrders are filled.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async marketSellOrdersWithEthAsync(
signedOrders: SignedOrder[],
takerAddress: string,
ethAmount: BigNumber,
signedFeeOrders: SignedOrder[] = [],
feePercentage: BigNumber = constants.ZERO_AMOUNT,
feeRecipientAddress: string = constants.NULL_ADDRESS,
txOpts: TransactionOpts = {},
): Promise<string> {
// type assertions
assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
assert.isBigNumber('ethAmount', ethAmount);
assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema);
assert.isBigNumber('feePercentage', feePercentage);
assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress);
assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
// other assertions
assert.ordersCanBeUsedForForwarderContract(signedOrders, this.getEtherTokenAddress());
assert.feeOrdersCanBeUsedForForwarderContract(
signedFeeOrders,
this.getZRXTokenAddress(),
this.getEtherTokenAddress(),
);
// lowercase input addresses
const normalizedTakerAddress = takerAddress.toLowerCase();
const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase();
// optimize orders
const optimizedMarketOrders = calldataOptimizationUtils.optimizeForwarderOrders(signedOrders);
const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(signedFeeOrders);
// send transaction
const forwarderContractInstance = await this._getForwarderContractAsync();
const txHash = await forwarderContractInstance.marketSellOrdersWithEth.sendTransactionAsync(
optimizedMarketOrders,
_.map(optimizedMarketOrders, order => order.signature),
optimizedFeeOrders,
_.map(optimizedFeeOrders, order => order.signature),
feePercentage,
feeRecipientAddress,
{
value: ethAmount,
from: normalizedTakerAddress,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
},
);
return txHash;
}
/**
* Attempt to purchase makerAssetFillAmount of makerAsset by selling ethAmount provided with transaction.
* Any ZRX required to pay fees for primary orders will automatically be purchased by the contract.
* Any ETH not spent will be refunded to sender.
* @param signedOrders An array of objects that conform to the SignedOrder interface. All orders must specify the same makerAsset.
* All orders must specify WETH as the takerAsset
* @param makerAssetFillAmount The amount of the order (in taker asset baseUnits) that you wish to fill.
* @param takerAddress The user Ethereum address who would like to fill this order. Must be available via the supplied
* Provider provided at instantiation.
* @param ethAmount The amount of eth to send with the transaction (in wei).
* @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders must specify ZRX as makerAsset and WETH as takerAsset.
* Used to purchase ZRX for primary order fees.
* @param feePercentage The percentage of WETH sold that will payed as fee to forwarding contract feeRecipient.
* Defaults to 0.
* @param feeRecipientAddress The address that will receive ETH when signedFeeOrders are filled.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async marketBuyOrdersWithEthAsync(
signedOrders: SignedOrder[],
makerAssetFillAmount: BigNumber,
takerAddress: string,
ethAmount: BigNumber,
signedFeeOrders: SignedOrder[] = [],
feePercentage: BigNumber = constants.ZERO_AMOUNT,
feeRecipientAddress: string = constants.NULL_ADDRESS,
txOpts: TransactionOpts = {},
): Promise<string> {
// type assertions
assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
assert.isBigNumber('makerAssetFillAmount', makerAssetFillAmount);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
assert.isBigNumber('ethAmount', ethAmount);
assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema);
assert.isBigNumber('feePercentage', feePercentage);
assert.isETHAddressHex('feeRecipientAddress', feeRecipientAddress);
assert.doesConformToSchema('txOpts', txOpts, txOptsSchema);
// other assertions
assert.ordersCanBeUsedForForwarderContract(signedOrders, this.getEtherTokenAddress());
assert.feeOrdersCanBeUsedForForwarderContract(
signedFeeOrders,
this.getZRXTokenAddress(),
this.getEtherTokenAddress(),
);
// lowercase input addresses
const normalizedTakerAddress = takerAddress.toLowerCase();
const normalizedFeeRecipientAddress = feeRecipientAddress.toLowerCase();
// optimize orders
const optimizedMarketOrders = calldataOptimizationUtils.optimizeForwarderOrders(signedOrders);
const optimizedFeeOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(signedFeeOrders);
// send transaction
const forwarderContractInstance = await this._getForwarderContractAsync();
const txHash = await forwarderContractInstance.marketBuyOrdersWithEth.sendTransactionAsync(
optimizedMarketOrders,
makerAssetFillAmount,
_.map(optimizedMarketOrders, order => order.signature),
optimizedFeeOrders,
_.map(optimizedFeeOrders, order => order.signature),
feePercentage,
feeRecipientAddress,
{
value: ethAmount,
from: normalizedTakerAddress,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
},
);
return txHash;
}
/**
* Retrieves the Ethereum address of the Forwarder contract deployed on the network
* that the user-passed web3 provider is connected to.
* @returns The Ethereum address of the Forwarder contract being used.
*/
public getContractAddress(): string {
const contractAddress = this._getContractAddress(artifacts.Forwarder, this._contractAddressIfExists);
return contractAddress;
}
/**
* Returns the ZRX token address used by the forwarder contract.
* @return Address of ZRX token
*/
public getZRXTokenAddress(): string {
const contractAddress = this._getContractAddress(artifacts.ZRXToken, this._zrxContractAddressIfExists);
return contractAddress;
}
/**
* Returns the Ether token address used by the forwarder contract.
* @return Address of Ether token
*/
public getEtherTokenAddress(): string {
const contractAddress = this._getContractAddress(artifacts.EtherToken);
return contractAddress;
}
// HACK: We don't want this method to be visible to the other units within that package but not to the end user.
// TS doesn't give that possibility and therefore we make it private and access it over an any cast. Because of that tslint sees it as unused.
// tslint:disable-next-line:no-unused-variable
private _invalidateContractInstance(): void {
delete this._forwarderContractIfExists;
}
private async _getForwarderContractAsync(): Promise<ForwarderContract> {
if (!_.isUndefined(this._forwarderContractIfExists)) {
return this._forwarderContractIfExists;
}
const [abi, address] = await this._getContractAbiAndAddressFromArtifactsAsync(
artifacts.Forwarder,
this._contractAddressIfExists,
);
const contractInstance = new ForwarderContract(
abi,
address,
this._web3Wrapper.getProvider(),
this._web3Wrapper.getContractDefaults(),
);
this._forwarderContractIfExists = contractInstance;
return this._forwarderContractIfExists;
}
}

View File

@@ -5,6 +5,7 @@ export { EtherTokenWrapper } from './contract_wrappers/ether_token_wrapper';
export { ExchangeWrapper } from './contract_wrappers/exchange_wrapper';
export { ERC20ProxyWrapper } from './contract_wrappers/erc20_proxy_wrapper';
export { ERC721ProxyWrapper } from './contract_wrappers/erc721_proxy_wrapper';
export { ForwarderWrapper } from './contract_wrappers/forwarder_wrapper';
export {
ContractWrappersError,

View File

@@ -109,6 +109,7 @@ export type SyncMethod = (...args: any[]) => any;
* zrxContractAddress: The address of the ZRX contract to use
* erc20ProxyContractAddress: The address of the erc20 token transfer proxy contract to use
* erc721ProxyContractAddress: The address of the erc721 token transfer proxy contract to use
* forwarderContractAddress: The address of the forwarder contract to use
* orderWatcherConfig: All the configs related to the orderWatcher
* blockPollingIntervalMs: The interval to use for block polling in event watching methods (defaults to 1000)
*/
@@ -119,6 +120,7 @@ export interface ContractWrappersConfig {
zrxContractAddress?: string;
erc20ProxyContractAddress?: string;
erc721ProxyContractAddress?: string;
forwarderContractAddress?: string;
blockPollingIntervalMs?: number;
}
@@ -172,13 +174,13 @@ export enum TransferType {
export type OnOrderStateChangeCallback = (err: Error | null, orderState?: OrderState) => void;
export interface OrderInfo {
orderStatus: number;
orderStatus: OrderStatus;
orderHash: string;
orderTakerAssetFilledAmount: BigNumber;
}
export enum OrderStatus {
INVALID,
INVALID = 0,
INVALID_MAKER_ASSET_AMOUNT,
INVALID_TAKER_ASSET_AMOUNT,
FILLABLE,

View File

@@ -1,11 +1,14 @@
import { assert as sharedAssert } from '@0xproject/assert';
// HACK: We need those two unused imports because they're actually used by sharedAssert which gets injected here
import { Schema } from '@0xproject/json-schemas'; // tslint:disable-line:no-unused-variable
import { isValidSignatureAsync } from '@0xproject/order-utils';
import { ECSignature } from '@0xproject/types'; // tslint:disable-line:no-unused-variable
import { assetDataUtils, isValidSignatureAsync } from '@0xproject/order-utils';
import { ECSignature, Order } from '@0xproject/types'; // tslint:disable-line:no-unused-variable
import { BigNumber } from '@0xproject/utils'; // tslint:disable-line:no-unused-variable
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
import { constants } from './constants';
export const assert = {
...sharedAssert,
@@ -16,12 +19,12 @@ export const assert = {
signerAddress: string,
): Promise<void> {
const isValid = await isValidSignatureAsync(provider, orderHash, signature, signerAddress);
this.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`);
sharedAssert.assert(isValid, `Expected order with hash '${orderHash}' to have a valid signature`);
},
isValidSubscriptionToken(variableName: string, subscriptionToken: string): void {
const uuidRegex = new RegExp('^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$');
const isValid = uuidRegex.test(subscriptionToken);
this.assert(isValid, `Expected ${variableName} to be a valid subscription token`);
sharedAssert.assert(isValid, `Expected ${variableName} to be a valid subscription token`);
},
async isSenderAddressAsync(
variableName: string,
@@ -35,4 +38,53 @@ export const assert = {
`Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`,
);
},
ordersCanBeUsedForForwarderContract(orders: Order[], etherTokenAddress: string): void {
sharedAssert.assert(!_.isEmpty(orders), 'Expected at least 1 signed order. Found no orders');
assert.ordersHaveAtMostOneUniqueValueForProperty(orders, 'makerAssetData');
assert.allTakerAssetDatasAreErc20Token(orders, etherTokenAddress);
assert.allTakerAddressesAreNull(orders);
},
feeOrdersCanBeUsedForForwarderContract(orders: Order[], zrxTokenAddress: string, etherTokenAddress: string): void {
if (!_.isEmpty(orders)) {
assert.allMakerAssetDatasAreErc20Token(orders, zrxTokenAddress);
assert.allTakerAssetDatasAreErc20Token(orders, etherTokenAddress);
}
},
allTakerAddressesAreNull(orders: Order[]): void {
assert.ordersHaveAtMostOneUniqueValueForProperty(orders, 'takerAddress', constants.NULL_ADDRESS);
},
allMakerAssetDatasAreErc20Token(orders: Order[], tokenAddress: string): void {
assert.ordersHaveAtMostOneUniqueValueForProperty(
orders,
'makerAssetData',
assetDataUtils.encodeERC20AssetData(tokenAddress),
);
},
allTakerAssetDatasAreErc20Token(orders: Order[], tokenAddress: string): void {
assert.ordersHaveAtMostOneUniqueValueForProperty(
orders,
'takerAssetData',
assetDataUtils.encodeERC20AssetData(tokenAddress),
);
},
/*
* Asserts that all the orders have the same value for the provided propertyName
* If the value parameter is provided, this asserts that all orders have the prope
*/
ordersHaveAtMostOneUniqueValueForProperty(orders: Order[], propertyName: string, value?: any): void {
const allValues = _.map(orders, order => _.get(order, propertyName));
sharedAssert.hasAtMostOneUniqueValue(
allValues,
`Expected all orders to have the same ${propertyName} field. Found the following ${propertyName} values: ${JSON.stringify(
allValues,
)}`,
);
if (!_.isUndefined(value)) {
const firstValue = _.head(allValues);
sharedAssert.assert(
firstValue === value,
`Expected all orders to have a ${propertyName} field with value: ${value}. Found: ${firstValue}`,
);
}
},
};

View File

@@ -0,0 +1,44 @@
import { SignedOrder } from '@0xproject/types';
import * as _ from 'lodash';
import { constants } from './constants';
export const calldataOptimizationUtils = {
/**
* Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and
* all makerAssetData are '0x' except for that of the first order, which retains its original value
* @param orders An array of SignedOrder objects
* @returns optimized orders
*/
optimizeForwarderOrders(orders: SignedOrder[]): SignedOrder[] {
const optimizedOrders = _.map(orders, (order, index) =>
transformOrder(order, {
makerAssetData: index === 0 ? order.makerAssetData : constants.NULL_BYTES,
takerAssetData: constants.NULL_BYTES,
}),
);
return optimizedOrders;
},
/**
* Takes an array of orders and outputs an array of equivalent orders where all takerAssetData are '0x' and
* all makerAssetData are '0x'
* @param orders An array of SignedOrder objects
* @returns optimized orders
*/
optimizeForwarderFeeOrders(orders: SignedOrder[]): SignedOrder[] {
const optimizedOrders = _.map(orders, (order, index) =>
transformOrder(order, {
makerAssetData: constants.NULL_BYTES,
takerAssetData: constants.NULL_BYTES,
}),
);
return optimizedOrders;
},
};
const transformOrder = (order: SignedOrder, partialOrder: Partial<SignedOrder>) => {
return {
...order,
...partialOrder,
};
};

View File

@@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils';
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
NULL_BYTES: '0x',
TESTRPC_NETWORK_ID: 50,
INVALID_JUMP_PATTERN: 'invalid JUMP at',
REVERT: 'revert',
@@ -10,4 +11,5 @@ export const constants = {
// tslint:disable-next-line:custom-no-magic-numbers
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
DEFAULT_BLOCK_POLLING_INTERVAL: 1000,
ZERO_AMOUNT: new BigNumber(0),
};

View File

@@ -0,0 +1,60 @@
import { orderFactory } from '@0xproject/order-utils';
import * as chai from 'chai';
import * as _ from 'lodash';
import 'mocha';
import { assert } from '../src/utils/assert';
import { calldataOptimizationUtils } from '../src/utils/calldata_optimization_utils';
import { constants } from '../src/utils/constants';
import { chaiSetup } from './utils/chai_setup';
chaiSetup.configure();
const expect = chai.expect;
// utility for generating a set of order objects with mostly NULL values
// except for a specified makerAssetData and takerAssetData
const FAKE_ORDERS_COUNT = 5;
const generateFakeOrders = (makerAssetData: string, takerAssetData: string) =>
_.map(_.range(FAKE_ORDERS_COUNT), index => {
const order = orderFactory.createOrder(
constants.NULL_ADDRESS,
constants.ZERO_AMOUNT,
makerAssetData,
constants.ZERO_AMOUNT,
takerAssetData,
constants.NULL_ADDRESS,
);
return {
...order,
signature: 'dummy signature',
};
});
describe('calldataOptimizationUtils', () => {
const fakeMakerAssetData = 'fakeMakerAssetData';
const fakeTakerAssetData = 'fakeTakerAssetData';
const orders = generateFakeOrders(fakeMakerAssetData, fakeTakerAssetData);
describe('#optimizeForwarderOrders', () => {
it('should make makerAssetData `0x` unless first order', () => {
const optimizedOrders = calldataOptimizationUtils.optimizeForwarderOrders(orders);
expect(optimizedOrders[0].makerAssetData).to.equal(fakeMakerAssetData);
const ordersWithoutHead = _.slice(optimizedOrders, 1);
_.forEach(ordersWithoutHead, order => expect(order.makerAssetData).to.equal(constants.NULL_BYTES));
});
it('should make all takerAssetData `0x`', () => {
const optimizedOrders = calldataOptimizationUtils.optimizeForwarderOrders(orders);
_.forEach(optimizedOrders, order => expect(order.takerAssetData).to.equal(constants.NULL_BYTES));
});
});
describe('#optimizeForwarderFeeOrders', () => {
it('should make all makerAssetData `0x`', () => {
const optimizedOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(orders);
_.forEach(optimizedOrders, order => expect(order.makerAssetData).to.equal(constants.NULL_BYTES));
});
it('should make all takerAssetData `0x`', () => {
const optimizedOrders = calldataOptimizationUtils.optimizeForwarderFeeOrders(orders);
_.forEach(optimizedOrders, order => expect(order.takerAssetData).to.equal(constants.NULL_BYTES));
});
});
});

View File

@@ -277,6 +277,15 @@ describe('ExchangeWrapper', () => {
expect(orderInfo.orderHash).to.be.equal(orderHash);
});
});
describe('#getOrdersInfoAsync', () => {
it('should get the orders info', async () => {
const ordersInfo = await contractWrappers.exchange.getOrdersInfoAsync([signedOrder, anotherSignedOrder]);
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
expect(ordersInfo[0].orderHash).to.be.equal(orderHash);
const anotherOrderHash = orderHashUtils.getOrderHashHex(anotherSignedOrder);
expect(ordersInfo[1].orderHash).to.be.equal(anotherOrderHash);
});
});
describe('#isValidSignature', () => {
it('should check if the signature is valid', async () => {
const orderHash = orderHashUtils.getOrderHashHex(signedOrder);
@@ -295,7 +304,7 @@ describe('ExchangeWrapper', () => {
});
});
describe('#isAllowedValidatorAsync', () => {
it('should check if the validator is alllowed', async () => {
it('should check if the validator is allowed', async () => {
const signerAddress = makerAddress;
const validatorAddress = constants.NULL_ADDRESS;
const isAllowed = await contractWrappers.exchange.isAllowedValidatorAsync(signerAddress, validatorAddress);

View File

@@ -0,0 +1,130 @@
import { BlockchainLifecycle, callbackErrorReporter } from '@0xproject/dev-utils';
import { FillScenarios } from '@0xproject/fill-scenarios';
import { assetDataUtils, orderHashUtils } from '@0xproject/order-utils';
import { DoneCallback, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import { BlockParamLiteral } from 'ethereum-types';
import 'mocha';
import {
ContractWrappers,
DecodedLogEvent,
ExchangeCancelEventArgs,
ExchangeEvents,
ExchangeFillEventArgs,
OrderStatus,
} from '../src';
import { chaiSetup } from './utils/chai_setup';
import { constants } from './utils/constants';
import { tokenUtils } from './utils/token_utils';
import { provider, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
describe('ForwarderWrapper', () => {
const contractWrappersConfig = {
networkId: constants.TESTRPC_NETWORK_ID,
blockPollingIntervalMs: 0,
};
const fillableAmount = new BigNumber(5);
const takerTokenFillAmount = new BigNumber(5);
let contractWrappers: ContractWrappers;
let fillScenarios: FillScenarios;
let forwarderContractAddress: string;
let exchangeContractAddress: string;
let zrxTokenAddress: string;
let userAddresses: string[];
let coinbase: string;
let makerAddress: string;
let takerAddress: string;
let feeRecipient: string;
let anotherMakerAddress: string;
let makerTokenAddress: string;
let takerTokenAddress: string;
let makerAssetData: string;
let takerAssetData: string;
let signedOrder: SignedOrder;
let anotherSignedOrder: SignedOrder;
before(async () => {
await blockchainLifecycle.startAsync();
contractWrappers = new ContractWrappers(provider, contractWrappersConfig);
forwarderContractAddress = contractWrappers.forwarder.getContractAddress();
exchangeContractAddress = contractWrappers.exchange.getContractAddress();
userAddresses = await web3Wrapper.getAvailableAddressesAsync();
zrxTokenAddress = tokenUtils.getProtocolTokenAddress();
fillScenarios = new FillScenarios(
provider,
userAddresses,
zrxTokenAddress,
exchangeContractAddress,
contractWrappers.erc20Proxy.getContractAddress(),
contractWrappers.erc721Proxy.getContractAddress(),
);
[coinbase, makerAddress, takerAddress, feeRecipient, anotherMakerAddress] = userAddresses;
[makerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses();
takerTokenAddress = tokenUtils.getWethTokenAddress();
[makerAssetData, takerAssetData] = [
assetDataUtils.encodeERC20AssetData(makerTokenAddress),
assetDataUtils.encodeERC20AssetData(takerTokenAddress),
];
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerAssetData,
takerAssetData,
makerAddress,
constants.NULL_ADDRESS,
fillableAmount,
);
anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerAssetData,
takerAssetData,
makerAddress,
constants.NULL_ADDRESS,
fillableAmount,
);
});
after(async () => {
await blockchainLifecycle.revertAsync();
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#marketBuyOrdersWithEthAsync', () => {
it('should market buy orders with eth', async () => {
const signedOrders = [signedOrder, anotherSignedOrder];
const makerAssetFillAmount = signedOrder.makerAssetAmount.plus(anotherSignedOrder.makerAssetAmount);
const txHash = await contractWrappers.forwarder.marketBuyOrdersWithEthAsync(
signedOrders,
makerAssetFillAmount,
takerAddress,
makerAssetFillAmount,
);
await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
const ordersInfo = await contractWrappers.exchange.getOrdersInfoAsync([signedOrder, anotherSignedOrder]);
expect(ordersInfo[0].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
expect(ordersInfo[1].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
});
});
describe('#marketSellOrdersWithEthAsync', () => {
it('should market sell orders with eth', async () => {
const signedOrders = [signedOrder, anotherSignedOrder];
const makerAssetFillAmount = signedOrder.makerAssetAmount.plus(anotherSignedOrder.makerAssetAmount);
const txHash = await contractWrappers.forwarder.marketSellOrdersWithEthAsync(
signedOrders,
takerAddress,
makerAssetFillAmount,
);
await web3Wrapper.awaitTransactionSuccessAsync(txHash, constants.AWAIT_TRANSACTION_MINED_MS);
const ordersInfo = await contractWrappers.exchange.getOrdersInfoAsync([signedOrder, anotherSignedOrder]);
expect(ordersInfo[0].orderStatus).to.be.equal(OrderStatus.FULLY_FILLED);
expect(ordersInfo[1].orderStatus).to.be.equal(OrderStatus.FILLABLE);
expect(ordersInfo[1].orderTakerAssetFilledAmount).to.be.bignumber.equal(new BigNumber(4)); // only 95% of ETH is sold
});
});
});

View File

@@ -7,6 +7,7 @@ async callAsync(
const functionSignature = '{{this.functionSignature}}';
const inputAbi = self._lookupAbi(functionSignature).inputs;
[{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self));
BaseContract.strictArgumentEncodingCheck(inputAbi, [{{> params inputs=inputs}}]);
const ethersFunction = self._lookupEthersInterface(functionSignature).functions.{{this.name}}(
{{> params inputs=inputs}}
) as ethers.CallDescription;

View File

@@ -11,6 +11,7 @@ public {{this.tsName}} = {
const self = this as any as {{contractName}}Contract;
const inputAbi = self._lookupAbi('{{this.functionSignature}}').inputs;
[{{> params inputs=inputs}}] = BaseContract._formatABIDataItemList(inputAbi, [{{> params inputs=inputs}}], BaseContract._bigNumberToString.bind(self));
BaseContract.strictArgumentEncodingCheck(inputAbi, [{{> params inputs=inputs}}]);
const encodedData = self._lookupEthersInterface('{{this.functionSignature}}').functions.{{this.name}}(
{{> params inputs=inputs}}
).data;

View File

@@ -68,7 +68,7 @@
"solc": "^0.4.24",
"solhint": "^1.2.1",
"tslint": "5.11.0",
"typescript": "2.7.1",
"typescript": "2.9.2",
"yargs": "^10.0.3"
},
"dependencies": {

View File

@@ -96,14 +96,15 @@ contract MixinSignatureValidator is
"LENGTH_GREATER_THAN_0_REQUIRED"
);
// Ensure signature is supported
// Pop last byte off of signature byte array.
uint8 signatureTypeRaw = uint8(signature.popLastByte());
// Ensure signature is supported
require(
signatureTypeRaw < uint8(SignatureType.NSignatureTypes),
"SIGNATURE_UNSUPPORTED"
);
// Pop last byte off of signature byte array.
SignatureType signatureType = SignatureType(signatureTypeRaw);
// Variables are not scoped in Solidity.
@@ -141,7 +142,12 @@ contract MixinSignatureValidator is
v = uint8(signature[0]);
r = signature.readBytes32(1);
s = signature.readBytes32(33);
recovered = ecrecover(hash, v, r, s);
recovered = ecrecover(
hash,
v,
r,
s
);
isValid = signerAddress == recovered;
return isValid;
@@ -197,7 +203,6 @@ contract MixinSignatureValidator is
// | 0x14 + x | 1 | Signature type is always "\x06" |
} else if (signatureType == SignatureType.Validator) {
// Pop last 20 bytes off of signature byte array.
address validatorAddress = signature.popLast20Bytes();
// Ensure signer has approved validator.

View File

@@ -123,19 +123,23 @@ contract MixinTransactions is
bytes32 dataHash = keccak256(data);
// Assembly for more efficiently computing:
// keccak256(abi.encode(
// keccak256(abi.encodePacked(
// EIP712_ZEROEX_TRANSACTION_SCHEMA_HASH,
// salt,
// signerAddress,
// bytes32(signerAddress),
// keccak256(data)
// ));
assembly {
// Load free memory pointer
let memPtr := mload(64)
mstore(memPtr, schemaHash)
mstore(add(memPtr, 32), salt)
mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(memPtr, 96), dataHash)
mstore(memPtr, schemaHash) // hash of schema
mstore(add(memPtr, 32), salt) // salt
mstore(add(memPtr, 64), and(signerAddress, 0xffffffffffffffffffffffffffffffffffffffff)) // signerAddress
mstore(add(memPtr, 96), dataHash) // hash of data
// Compute hash
result := keccak256(memPtr, 128)
}

View File

@@ -30,7 +30,7 @@ contract LibEIP712 {
string constant internal EIP712_DOMAIN_VERSION = "2";
// Hash of the EIP712 Domain Separator Schema
bytes32 public constant EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
bytes32 constant internal EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH = keccak256(abi.encodePacked(
"EIP712Domain(",
"string name,",
"string version,",
@@ -45,11 +45,11 @@ contract LibEIP712 {
constructor ()
public
{
EIP712_DOMAIN_HASH = keccak256(abi.encode(
EIP712_DOMAIN_HASH = keccak256(abi.encodePacked(
EIP712_DOMAIN_SEPARATOR_SCHEMA_HASH,
keccak256(bytes(EIP712_DOMAIN_NAME)),
keccak256(bytes(EIP712_DOMAIN_VERSION)),
address(this)
bytes32(address(this))
));
}
@@ -59,8 +59,28 @@ contract LibEIP712 {
function hashEIP712Message(bytes32 hashStruct)
internal
view
returns (bytes32)
returns (bytes32 result)
{
return keccak256(abi.encodePacked(EIP191_HEADER, EIP712_DOMAIN_HASH, hashStruct));
bytes32 eip712DomainHash = EIP712_DOMAIN_HASH;
// Assembly for more efficient computing:
// keccak256(abi.encodePacked(
// EIP191_HEADER,
// EIP712_DOMAIN_HASH,
// hashStruct
// ));
assembly {
// Load free memory pointer
let memPtr := mload(64)
mstore(memPtr, 0x1901000000000000000000000000000000000000000000000000000000000000) // EIP191 header
mstore(add(memPtr, 2), eip712DomainHash) // EIP712 domain hash
mstore(add(memPtr, 34), hashStruct) // Hash of struct
// Compute hash
result := keccak256(memPtr, 66)
}
return result;
}
}

View File

@@ -103,11 +103,12 @@ contract LibOrder is
bytes32 takerAssetDataHash = keccak256(order.takerAssetData);
// Assembly for more efficiently computing:
// keccak256(abi.encode(
// order.makerAddress,
// order.takerAddress,
// order.feeRecipientAddress,
// order.senderAddress,
// keccak256(abi.encodePacked(
// EIP712_ORDER_SCHEMA_HASH,
// bytes32(order.makerAddress),
// bytes32(order.takerAddress),
// bytes32(order.feeRecipientAddress),
// bytes32(order.senderAddress),
// order.makerAssetAmount,
// order.takerAssetAmount,
// order.makerFee,
@@ -119,24 +120,26 @@ contract LibOrder is
// ));
assembly {
// Calculate memory addresses that will be swapped out before hashing
let pos1 := sub(order, 32)
let pos2 := add(order, 320)
let pos3 := add(order, 352)
// Backup
// solhint-disable-next-line space-after-comma
let temp1 := mload(sub(order, 32))
let temp2 := mload(add(order, 320))
let temp3 := mload(add(order, 352))
let temp1 := mload(pos1)
let temp2 := mload(pos2)
let temp3 := mload(pos3)
// Hash in place
// solhint-disable-next-line space-after-comma
mstore(sub(order, 32), schemaHash)
mstore(add(order, 320), makerAssetDataHash)
mstore(add(order, 352), takerAssetDataHash)
result := keccak256(sub(order, 32), 416)
mstore(pos1, schemaHash)
mstore(pos2, makerAssetDataHash)
mstore(pos3, takerAssetDataHash)
result := keccak256(pos1, 416)
// Restore
// solhint-disable-next-line space-after-comma
mstore(sub(order, 32), temp1)
mstore(add(order, 320), temp2)
mstore(add(order, 352), temp3)
mstore(pos1, temp1)
mstore(pos2, temp2)
mstore(pos3, temp3)
}
return result;
}

View File

@@ -69,13 +69,22 @@ describe('matchOrders', () => {
before(async () => {
// Create accounts
const accounts = await web3Wrapper.getAvailableAddressesAsync();
// Hack(albrow): Both Prettier and TSLint insert a trailing comma below
// but that is invalid syntax as of TypeScript version >= 2.8. We don't
// have the right fine-grained configuration options in TSLint,
// Prettier, or TypeScript, to reconcile this, so we will just have to
// wait for them to sort it out. We disable TSLint and Prettier for
// this part of the code for now. This occurs several times in this
// file. See https://github.com/prettier/prettier/issues/4624.
// prettier-ignore
const usedAddresses = ([
owner,
makerAddressLeft,
makerAddressRight,
takerAddress,
feeRecipientAddressLeft,
feeRecipientAddressRight,
// tslint:disable-next-line:trailing-comma
feeRecipientAddressRight
] = _.slice(accounts, 0, 6));
// Create wrappers
erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner);
@@ -201,9 +210,11 @@ describe('matchOrders', () => {
// Match signedOrderLeft with signedOrderRight
let newERC20BalancesByOwner: ERC20BalancesByOwner;
let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
// prettier-ignore
[
newERC20BalancesByOwner,
newERC721TokenIdsByOwner,
// tslint:disable-next-line:trailing-comma
newERC721TokenIdsByOwner
] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
signedOrderLeft,
signedOrderRight,
@@ -306,9 +317,11 @@ describe('matchOrders', () => {
// Match orders
let newERC20BalancesByOwner: ERC20BalancesByOwner;
let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
// prettier-ignore
[
newERC20BalancesByOwner,
newERC721TokenIdsByOwner,
// tslint:disable-next-line:trailing-comma
newERC721TokenIdsByOwner
] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
signedOrderLeft,
signedOrderRight,
@@ -374,9 +387,11 @@ describe('matchOrders', () => {
// Match orders
let newERC20BalancesByOwner: ERC20BalancesByOwner;
let newERC721TokenIdsByOwner: ERC721TokenIdsByOwner;
// prettier-ignore
[
newERC20BalancesByOwner,
newERC721TokenIdsByOwner,
// tslint:disable-next-line:trailing-comma
newERC721TokenIdsByOwner
] = await matchOrderTester.matchOrdersAndVerifyBalancesAsync(
signedOrderLeft,
signedOrderRight,

View File

@@ -113,7 +113,7 @@ describe('MixinSignatureValidator', () => {
it('should revert when signature type is unsupported', async () => {
const unsupportedSignatureType = SignatureType.NSignatureTypes;
const unsupportedSignatureHex = `0x${unsupportedSignatureType}`;
const unsupportedSignatureHex = '0x' + Buffer.from([unsupportedSignatureType]).toString('hex');
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
return expectContractCallFailed(
signatureValidator.publicIsValidSignature.callAsync(
@@ -126,7 +126,7 @@ describe('MixinSignatureValidator', () => {
});
it('should revert when SignatureType=Illegal', async () => {
const unsupportedSignatureHex = `0x${SignatureType.Illegal}`;
const unsupportedSignatureHex = '0x' + Buffer.from([SignatureType.Illegal]).toString('hex');
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
return expectContractCallFailed(
signatureValidator.publicIsValidSignature.callAsync(
@@ -139,7 +139,7 @@ describe('MixinSignatureValidator', () => {
});
it('should return false when SignatureType=Invalid and signature has a length of zero', async () => {
const signatureHex = `0x${SignatureType.Invalid}`;
const signatureHex = '0x' + Buffer.from([SignatureType.Invalid]).toString('hex');
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
orderHashHex,

View File

@@ -42,7 +42,7 @@
"nyc": "^11.0.1",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@0xproject/subproviders": "^1.0.4",

View File

@@ -41,7 +41,7 @@
"make-promises-safe": "^1.1.0",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@types/node": "^8.0.53",

View File

@@ -1,4 +1,17 @@
[
{
"version": "1.0.1-rc.3",
"changes": [
{
"note":
"Updated to use latest orderFactory interface, fixed `feeRecipient` spelling error in public interface",
"pr": 936
},
{
"note": "Dependencies updated"
}
]
},
{
"version": "1.0.1-rc.2",
"changes": [

View File

@@ -38,7 +38,7 @@
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@0xproject/base-contract": "^1.0.4",

View File

@@ -61,7 +61,7 @@ export class FillScenarios {
makerAddress: string,
takerAddress: string,
fillableAmount: BigNumber,
feeRecepientAddress: string,
feeRecipientAddress: string,
expirationTimeSeconds?: BigNumber,
): Promise<SignedOrder> {
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
@@ -73,7 +73,7 @@ export class FillScenarios {
takerAddress,
fillableAmount,
fillableAmount,
feeRecepientAddress,
feeRecipientAddress,
expirationTimeSeconds,
);
}
@@ -88,7 +88,7 @@ export class FillScenarios {
): Promise<SignedOrder> {
const makerFee = new BigNumber(0);
const takerFee = new BigNumber(0);
const feeRecepientAddress = constants.NULL_ADDRESS;
const feeRecipientAddress = constants.NULL_ADDRESS;
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
makerAssetData,
takerAssetData,
@@ -98,7 +98,7 @@ export class FillScenarios {
takerAddress,
makerFillableAmount,
takerFillableAmount,
feeRecepientAddress,
feeRecipientAddress,
expirationTimeSeconds,
);
}
@@ -148,7 +148,7 @@ export class FillScenarios {
takerAddress: string,
makerFillableAmount: BigNumber,
takerFillableAmount: BigNumber,
feeRecepientAddress: string,
feeRecipientAddress: string,
expirationTimeSeconds?: BigNumber,
): Promise<SignedOrder> {
const decodedMakerAssetData = assetDataUtils.decodeAssetDataOrThrow(makerAssetData);
@@ -194,17 +194,19 @@ export class FillScenarios {
const signedOrder = await orderFactory.createSignedOrderAsync(
this._web3Wrapper.getProvider(),
makerAddress,
takerAddress,
senderAddress,
makerFee,
takerFee,
makerFillableAmount,
makerAssetData,
takerFillableAmount,
takerAssetData,
this._exchangeAddress,
feeRecepientAddress,
expirationTimeSeconds,
{
takerAddress,
senderAddress,
makerFee,
takerFee,
feeRecipientAddress,
expirationTimeSeconds,
},
);
return signedOrder;
}

View File

@@ -5,6 +5,10 @@
{
"note": "Allow for additional properties in txData schema",
"pr": 938
},
{
"note": "Change hexSchema to match `0x`",
"pr": 937
}
]
},

View File

@@ -70,7 +70,7 @@
"shx": "^0.2.2",
"tslint": "5.11.0",
"typedoc": "0xProject/typedoc",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"publishConfig": {
"access": "public"

View File

@@ -7,7 +7,7 @@ export const addressSchema = {
export const hexSchema = {
id: '/Hex',
type: 'string',
pattern: '^0x([0-9a-f][0-9a-f])+$',
pattern: '^0x(([0-9a-f][0-9a-f])+)?$',
};
export const numberSchema = {

View File

@@ -89,7 +89,7 @@ describe('Schema', () => {
validateAgainstSchema(testCases, hexSchema);
});
it('should fail for invalid hex string', () => {
const testCases = ['0x', '0', '0xzzzzzzB11a196601eD2ce54B665CaFEca0347D42'];
const testCases = ['0', '0xzzzzzzB11a196601eD2ce54B665CaFEca0347D42'];
const shouldFail = true;
validateAgainstSchema(testCases, hexSchema, shouldFail);
});

View File

@@ -56,6 +56,6 @@
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
}
}

View File

@@ -49,7 +49,7 @@
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1",
"typescript": "2.9.2",
"yargs": "^10.0.3"
},
"dependencies": {

View File

@@ -39,7 +39,7 @@
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@lerna/batch-packages": "^3.0.0-beta.18",

View File

@@ -31,12 +31,25 @@ const packageNameToWebsitePath: { [name: string]: string } = {
'ethereum-types': 'ethereum-types',
};
async function confirmAsync(message: string): Promise<void> {
prompt.start();
const result = await promisify(prompt.get)([message]);
const didConfirm = result[message] === 'y';
if (!didConfirm) {
utils.log('Publish process aborted.');
process.exit(0);
}
}
(async () => {
// Fetch public, updated Lerna packages
const shouldIncludePrivate = true;
const allUpdatedPackages = await utils.getUpdatedPackagesAsync(shouldIncludePrivate);
if (!configs.IS_LOCAL_PUBLISH) {
await confirmAsync(
'THIS IS NOT A TEST PUBLISH! You are about to publish one or more packages to npm. Are you sure you want to continue? (y/n)',
);
await confirmDocPagesRenderAsync(allUpdatedPackages);
}
@@ -107,14 +120,7 @@ package.ts. Please add an entry for it and try again.`,
opn(link);
});
prompt.start();
const message = 'Do all the doc pages render properly? (yn)';
const result = await promisify(prompt.get)([message]);
const didConfirm = result[message] === 'y';
if (!didConfirm) {
utils.log('Publish process aborted.');
process.exit(0);
}
await confirmAsync('Do all the doc pages render properly? (y/n)');
}
async function pushChangelogsToGithubAsync(): Promise<void> {

View File

@@ -117,7 +117,7 @@ export const utils = {
return tags;
},
async getLocalGitTagsAsync(): Promise<string[]> {
const result = await execAsync(`git tags`, {
const result = await execAsync(`git tag`, {
cwd: constants.monorepoRootPath,
});
const tagsString = result.stdout;

View File

@@ -1,4 +1,21 @@
[
{
"version": "1.0.1-rc.3",
"changes": [
{
"note":
"Added a synchronous `createOrder` method in `orderFactory`, updated public interfaces to support some optional parameters",
"pr": 936
},
{
"note": "Added marketUtils",
"pr": 937
},
{
"note": "Dependencies updated"
}
]
},
{
"version": "1.0.1-rc.2",
"changes": [

View File

@@ -70,7 +70,7 @@
"sinon": "^4.0.0",
"tslint": "5.11.0",
"typedoc": "0xProject/typedoc",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@0xproject/assert": "^1.0.4",

View File

@@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils';
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
NULL_BYTES: '0x',
// tslint:disable-next-line:custom-no-magic-numbers
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
TESTRPC_NETWORK_ID: 50,
@@ -10,4 +11,6 @@ export const constants = {
ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH: 53,
SELECTOR_LENGTH: 4,
BASE_16: 16,
INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite
ZERO_AMOUNT: new BigNumber(0),
};

View File

@@ -13,7 +13,15 @@ export { orderFactory } from './order_factory';
export { constants } from './constants';
export { crypto } from './crypto';
export { generatePseudoRandomSalt } from './salt';
export { OrderError, MessagePrefixType, MessagePrefixOpts, EIP712Parameter, EIP712Schema, EIP712Types } from './types';
export {
CreateOrderOpts,
OrderError,
MessagePrefixType,
MessagePrefixOpts,
EIP712Parameter,
EIP712Schema,
EIP712Types,
} from './types';
export { AbstractBalanceAndProxyAllowanceFetcher } from './abstract/abstract_balance_and_proxy_allowance_fetcher';
export { AbstractOrderFilledCancelledFetcher } from './abstract/abstract_order_filled_cancelled_fetcher';
export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
@@ -24,3 +32,4 @@ export { assetDataUtils } from './asset_data_utils';
export { EIP712Utils } from './eip712_utils';
export { OrderValidationUtils } from './order_validation_utils';
export { ExchangeTransferSimulator } from './exchange_transfer_simulator';
export { marketUtils } from './market_utils';

View File

@@ -0,0 +1,133 @@
import { schemas } from '@0xproject/json-schemas';
import { SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import { assert } from './assert';
import { constants } from './constants';
export const marketUtils = {
/**
* Takes an array of orders and returns a subset of those orders that has enough makerAssetAmount (taking into account on-chain balances,
* allowances, and partial fills) in order to fill the input makerAssetFillAmount plus slippageBufferAmount. Iterates from first order to last.
* Sort the input by ascending rate in order to get the subset of orders that will cost the least ETH.
* @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify the same makerAsset.
* All orders should specify WETH as the takerAsset.
* @param remainingFillableMakerAssetAmounts An array of BigNumbers corresponding to the signedOrders parameter.
* You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups
* for these values.
* @param makerAssetFillAmount The amount of makerAsset desired to be filled.
* @param slippageBufferAmount An additional amount of makerAsset to be covered by the result in case of trade collisions or partial fills.
* @return Resulting orders and remaining fill amount that could not be covered by the input.
*/
findOrdersThatCoverMakerAssetFillAmount(
signedOrders: SignedOrder[],
remainingFillableMakerAssetAmounts: BigNumber[],
makerAssetFillAmount: BigNumber,
slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT,
): { resultOrders: SignedOrder[]; remainingFillAmount: BigNumber } {
assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
_.forEach(remainingFillableMakerAssetAmounts, (amount, index) =>
assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount),
);
assert.isValidBaseUnitAmount('makerAssetFillAmount', makerAssetFillAmount);
assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount);
assert.assert(
signedOrders.length === remainingFillableMakerAssetAmounts.length,
'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length',
);
// calculate total amount of makerAsset needed to be filled
const totalFillAmount = makerAssetFillAmount.plus(slippageBufferAmount);
// iterate through the signedOrders input from left to right until we have enough makerAsset to fill totalFillAmount
const result = _.reduce(
signedOrders,
({ resultOrders, remainingFillAmount }, order, index) => {
if (remainingFillAmount.lessThanOrEqualTo(constants.ZERO_AMOUNT)) {
return { resultOrders, remainingFillAmount: constants.ZERO_AMOUNT };
} else {
const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index];
// if there is no makerAssetAmountAvailable do not append order to resultOrders
// if we have exceeded the total amount we want to fill set remainingFillAmount to 0
return {
resultOrders: makerAssetAmountAvailable.gt(constants.ZERO_AMOUNT)
? _.concat(resultOrders, order)
: resultOrders,
remainingFillAmount: BigNumber.max(
constants.ZERO_AMOUNT,
remainingFillAmount.minus(makerAssetAmountAvailable),
),
};
}
},
{ resultOrders: [] as SignedOrder[], remainingFillAmount: totalFillAmount },
);
return result;
},
/**
* Takes an array of orders and an array of feeOrders. Returns a subset of the feeOrders that has enough ZRX (taking into account
* on-chain balances, allowances, and partial fills) in order to fill the takerFees required by signedOrders plus a
* slippageBufferAmount. Iterates from first feeOrder to last. Sort the feeOrders by ascending rate in order to get the subset of
* feeOrders that will cost the least ETH.
* @param signedOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as
* the makerAsset and WETH as the takerAsset.
* @param remainingFillableMakerAssetAmounts An array of BigNumbers corresponding to the signedOrders parameter.
* You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups
* for these values.
* @param signedFeeOrders An array of objects that conform to the SignedOrder interface. All orders should specify ZRX as
* the makerAsset and WETH as the takerAsset.
* @param remainingFillableFeeAmounts An array of BigNumbers corresponding to the signedFeeOrders parameter.
* You can use OrderStateUtils @0xproject/order-utils to perform blockchain lookups
* for these values.
* @param slippageBufferAmount An additional amount of fee to be covered by the result in case of trade collisions or partial fills.
* @return Resulting orders and remaining fee amount that could not be covered by the input.
*/
findFeeOrdersThatCoverFeesForTargetOrders(
signedOrders: SignedOrder[],
remainingFillableMakerAssetAmounts: BigNumber[],
signedFeeOrders: SignedOrder[],
remainingFillableFeeAmounts: BigNumber[],
slippageBufferAmount: BigNumber = constants.ZERO_AMOUNT,
): { resultOrders: SignedOrder[]; remainingFeeAmount: BigNumber } {
assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
_.forEach(remainingFillableMakerAssetAmounts, (amount, index) =>
assert.isValidBaseUnitAmount(`remainingFillableMakerAssetAmount[${index}]`, amount),
);
assert.doesConformToSchema('signedFeeOrders', signedFeeOrders, schemas.signedOrdersSchema);
_.forEach(remainingFillableFeeAmounts, (amount, index) =>
assert.isValidBaseUnitAmount(`remainingFillableFeeAmounts[${index}]`, amount),
);
assert.isValidBaseUnitAmount('slippageBufferAmount', slippageBufferAmount);
assert.assert(
signedOrders.length === remainingFillableMakerAssetAmounts.length,
'Expected signedOrders.length to equal remainingFillableMakerAssetAmounts.length',
);
assert.assert(
signedOrders.length === remainingFillableMakerAssetAmounts.length,
'Expected signedFeeOrders.length to equal remainingFillableFeeAmounts.length',
);
// calculate total amount of ZRX needed to fill signedOrders
const totalFeeAmount = _.reduce(
signedOrders,
(accFees, order, index) => {
const makerAssetAmountAvailable = remainingFillableMakerAssetAmounts[index];
const feeToFillMakerAssetAmountAvailable = makerAssetAmountAvailable
.mul(order.takerFee)
.div(order.makerAssetAmount);
return accFees.plus(feeToFillMakerAssetAmountAvailable);
},
constants.ZERO_AMOUNT,
);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
signedFeeOrders,
remainingFillableFeeAmounts,
totalFeeAmount,
slippageBufferAmount,
);
return {
resultOrders,
remainingFeeAmount: remainingFillAmount,
};
// TODO: add more orders here to cover rounding
// https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarding-contract-specification.md#over-buying-zrx
},
};

View File

@@ -1,49 +1,63 @@
import { ECSignature, SignedOrder } from '@0xproject/types';
import { ECSignature, Order, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Provider } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import { constants } from './constants';
import { orderHashUtils } from './order_hash';
import { generatePseudoRandomSalt } from './salt';
import { ecSignOrderHashAsync } from './signature_utils';
import { MessagePrefixType } from './types';
import { CreateOrderOpts, MessagePrefixType } from './types';
export const orderFactory = {
async createSignedOrderAsync(
provider: Provider,
createOrder(
makerAddress: string,
takerAddress: string,
senderAddress: string,
makerFee: BigNumber,
takerFee: BigNumber,
makerAssetAmount: BigNumber,
makerAssetData: string,
takerAssetAmount: BigNumber,
takerAssetData: string,
exchangeAddress: string,
feeRecipientAddress: string,
expirationTimeSecondsIfExists?: BigNumber,
): Promise<SignedOrder> {
const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite
const expirationTimeSeconds = _.isUndefined(expirationTimeSecondsIfExists)
? defaultExpirationUnixTimestampSec
: expirationTimeSecondsIfExists;
createOrderOpts: CreateOrderOpts = generateDefaultCreateOrderOpts(),
): Order {
const defaultCreateOrderOpts = generateDefaultCreateOrderOpts();
const order = {
makerAddress,
takerAddress,
senderAddress,
makerFee,
takerFee,
makerAssetAmount,
takerAssetAmount,
makerAssetData,
takerAssetData,
salt: generatePseudoRandomSalt(),
exchangeAddress,
feeRecipientAddress,
expirationTimeSeconds,
takerAddress: createOrderOpts.takerAddress || defaultCreateOrderOpts.takerAddress,
senderAddress: createOrderOpts.senderAddress || defaultCreateOrderOpts.senderAddress,
makerFee: createOrderOpts.makerFee || defaultCreateOrderOpts.makerFee,
takerFee: createOrderOpts.takerFee || defaultCreateOrderOpts.takerFee,
feeRecipientAddress: createOrderOpts.feeRecipientAddress || defaultCreateOrderOpts.feeRecipientAddress,
salt: createOrderOpts.salt || defaultCreateOrderOpts.salt,
expirationTimeSeconds:
createOrderOpts.expirationTimeSeconds || defaultCreateOrderOpts.expirationTimeSeconds,
};
return order;
},
async createSignedOrderAsync(
provider: Provider,
makerAddress: string,
makerAssetAmount: BigNumber,
makerAssetData: string,
takerAssetAmount: BigNumber,
takerAssetData: string,
exchangeAddress: string,
createOrderOpts?: CreateOrderOpts,
): Promise<SignedOrder> {
const order = orderFactory.createOrder(
makerAddress,
makerAssetAmount,
makerAssetData,
takerAssetAmount,
takerAssetData,
exchangeAddress,
createOrderOpts,
);
const orderHash = orderHashUtils.getOrderHashHex(order);
const messagePrefixOpts = {
prefixType: MessagePrefixType.EthSign,
@@ -56,6 +70,26 @@ export const orderFactory = {
},
};
function generateDefaultCreateOrderOpts(): {
takerAddress: string;
senderAddress: string;
makerFee: BigNumber;
takerFee: BigNumber;
feeRecipientAddress: string;
salt: BigNumber;
expirationTimeSeconds: BigNumber;
} {
return {
takerAddress: constants.NULL_ADDRESS,
senderAddress: constants.NULL_ADDRESS,
makerFee: constants.ZERO_AMOUNT,
takerFee: constants.ZERO_AMOUNT,
feeRecipientAddress: constants.NULL_ADDRESS,
salt: generatePseudoRandomSalt(),
expirationTimeSeconds: constants.INFINITE_TIMESTAMP_SEC,
};
}
function getVRSHexString(ecSignature: ECSignature): string {
const ETH_SIGN_SIGNATURE_TYPE = '03';
const vrs = `${intToHex(ecSignature.v)}${ethUtil.stripHexPrefix(ecSignature.r)}${ethUtil.stripHexPrefix(

View File

@@ -1,3 +1,5 @@
import { BigNumber } from '@0xproject/utils';
export enum OrderError {
InvalidSignature = 'INVALID_SIGNATURE',
}
@@ -51,3 +53,13 @@ export enum EIP712Types {
String = 'string',
Uint256 = 'uint256',
}
export interface CreateOrderOpts {
takerAddress?: string;
senderAddress?: string;
makerFee?: BigNumber;
takerFee?: BigNumber;
feeRecipientAddress?: string;
salt?: BigNumber;
expirationTimeSeconds?: BigNumber;
}

View File

@@ -0,0 +1,280 @@
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import 'mocha';
import { constants, marketUtils } from '../src';
import { chaiSetup } from './utils/chai_setup';
import { testOrderFactory } from './utils/test_order_factory';
chaiSetup.configure();
const expect = chai.expect;
// tslint:disable: no-unused-expression
describe('marketUtils', () => {
describe('#findOrdersThatCoverMakerAssetFillAmount', () => {
describe('no orders', () => {
it('returns empty and unchanged remainingFillAmount', async () => {
const fillAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
[],
[],
fillAmount,
);
expect(resultOrders).to.be.empty;
expect(remainingFillAmount).to.be.bignumber.equal(fillAmount);
});
});
describe('orders are completely fillable', () => {
// generate three signed orders each with 10 units of makerAsset, 30 total
const makerAssetAmount = new BigNumber(10);
const inputOrders = testOrderFactory.generateTestSignedOrders(
{
makerAssetAmount,
},
3,
);
// generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
it('returns input orders and zero remainingFillAmount when input exactly matches requested fill amount', async () => {
// try to fill 20 units of makerAsset
// include 10 units of slippageBufferAmount
const fillAmount = new BigNumber(20);
const slippageBufferAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
remainingFillableMakerAssetAmounts,
fillAmount,
slippageBufferAmount,
);
expect(resultOrders).to.be.deep.equal(inputOrders);
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('returns input orders and zero remainingFillAmount when input has more than requested fill amount', async () => {
// try to fill 15 units of makerAsset
// include 10 units of slippageBufferAmount
const fillAmount = new BigNumber(15);
const slippageBufferAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
remainingFillableMakerAssetAmounts,
fillAmount,
slippageBufferAmount,
);
expect(resultOrders).to.be.deep.equal(inputOrders);
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('returns input orders and non-zero remainingFillAmount when input has less than requested fill amount', async () => {
// try to fill 30 units of makerAsset
// include 5 units of slippageBufferAmount
const fillAmount = new BigNumber(30);
const slippageBufferAmount = new BigNumber(5);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
remainingFillableMakerAssetAmounts,
fillAmount,
slippageBufferAmount,
);
expect(resultOrders).to.be.deep.equal(inputOrders);
expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(5));
});
it('returns first order and zero remainingFillAmount when requested fill amount is exactly covered by the first order', async () => {
// try to fill 10 units of makerAsset
const fillAmount = new BigNumber(10);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
remainingFillableMakerAssetAmounts,
fillAmount,
);
expect(resultOrders).to.be.deep.equal([inputOrders[0]]);
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
it('returns first two orders and zero remainingFillAmount when requested fill amount is over covered by the first two order', async () => {
// try to fill 15 units of makerAsset
const fillAmount = new BigNumber(15);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
remainingFillableMakerAssetAmounts,
fillAmount,
);
expect(resultOrders).to.be.deep.equal([inputOrders[0], inputOrders[1]]);
expect(remainingFillAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
});
describe('orders are partially fillable', () => {
// generate three signed orders each with 10 units of makerAsset, 30 total
const makerAssetAmount = new BigNumber(10);
const inputOrders = testOrderFactory.generateTestSignedOrders(
{
makerAssetAmount,
},
3,
);
// generate remainingFillableMakerAssetAmounts that cover different partial fill scenarios
// 1. order is completely filled already
// 2. order is partially fillable
// 3. order is completely fillable
const remainingFillableMakerAssetAmounts = [constants.ZERO_AMOUNT, new BigNumber(5), makerAssetAmount];
it('returns last two orders and non-zero remainingFillAmount when trying to fill original makerAssetAmounts', async () => {
// try to fill 30 units of makerAsset
const fillAmount = new BigNumber(30);
const { resultOrders, remainingFillAmount } = marketUtils.findOrdersThatCoverMakerAssetFillAmount(
inputOrders,
remainingFillableMakerAssetAmounts,
fillAmount,
);
expect(resultOrders).to.be.deep.equal([inputOrders[1], inputOrders[2]]);
expect(remainingFillAmount).to.be.bignumber.equal(new BigNumber(15));
});
});
});
describe('#findFeeOrdersThatCoverFeesForTargetOrders', () => {
// generate three signed fee orders each with 10 units of ZRX, 30 total
const zrxAmount = new BigNumber(10);
const inputFeeOrders = testOrderFactory.generateTestSignedOrders(
{
makerAssetAmount: zrxAmount,
},
3,
);
// generate remainingFillableFeeAmounts that equal the zrxAmount
const remainingFillableFeeAmounts = [zrxAmount, zrxAmount, zrxAmount];
describe('no target orders', () => {
it('returns empty and zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
[],
[],
inputFeeOrders,
remainingFillableFeeAmounts,
);
expect(resultOrders).to.be.empty;
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
});
describe('no fee orders', () => {
// generate three signed orders each with 10 units of makerAsset, 30 total
// each signed order requires 10 units of takerFee
const makerAssetAmount = new BigNumber(10);
const takerFee = new BigNumber(10);
const inputOrders = testOrderFactory.generateTestSignedOrders(
{
makerAssetAmount,
takerFee,
},
3,
);
// generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
it('returns empty and non-zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
remainingFillableMakerAssetAmounts,
[],
[],
);
expect(resultOrders).to.be.empty;
expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30));
});
});
describe('target orders have no fees', () => {
// generate three signed orders each with 10 units of makerAsset, 30 total
const makerAssetAmount = new BigNumber(10);
const inputOrders = testOrderFactory.generateTestSignedOrders(
{
makerAssetAmount,
},
3,
);
// generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
it('returns empty and zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
remainingFillableMakerAssetAmounts,
inputFeeOrders,
remainingFillableFeeAmounts,
);
expect(resultOrders).to.be.empty;
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
});
describe('target orders require fees and are completely fillable', () => {
// generate three signed orders each with 10 units of makerAsset, 30 total
// each signed order requires 10 units of takerFee
const makerAssetAmount = new BigNumber(10);
const takerFee = new BigNumber(10);
const inputOrders = testOrderFactory.generateTestSignedOrders(
{
makerAssetAmount,
takerFee,
},
3,
);
// generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
it('returns input fee orders and zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
remainingFillableMakerAssetAmounts,
inputFeeOrders,
remainingFillableFeeAmounts,
);
expect(resultOrders).to.be.deep.equal(inputFeeOrders);
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
});
describe('target orders require fees and are partially fillable', () => {
// generate three signed orders each with 10 units of makerAsset, 30 total
// each signed order requires 10 units of takerFee
const makerAssetAmount = new BigNumber(10);
const takerFee = new BigNumber(10);
const inputOrders = testOrderFactory.generateTestSignedOrders(
{
makerAssetAmount,
takerFee,
},
3,
);
// generate remainingFillableMakerAssetAmounts that cover different partial fill scenarios
// 1. order is completely filled already
// 2. order is partially fillable
// 3. order is completely fillable
const remainingFillableMakerAssetAmounts = [constants.ZERO_AMOUNT, new BigNumber(5), makerAssetAmount];
it('returns first two input fee orders and zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
remainingFillableMakerAssetAmounts,
inputFeeOrders,
remainingFillableFeeAmounts,
);
expect(resultOrders).to.be.deep.equal([inputFeeOrders[0], inputFeeOrders[1]]);
expect(remainingFeeAmount).to.be.bignumber.equal(constants.ZERO_AMOUNT);
});
});
describe('target orders require more fees than available', () => {
// generate three signed orders each with 10 units of makerAsset, 30 total
// each signed order requires 20 units of takerFee
const makerAssetAmount = new BigNumber(10);
const takerFee = new BigNumber(20);
const inputOrders = testOrderFactory.generateTestSignedOrders(
{
makerAssetAmount,
takerFee,
},
3,
);
// generate remainingFillableMakerAssetAmounts that equal the makerAssetAmount
const remainingFillableMakerAssetAmounts = [makerAssetAmount, makerAssetAmount, makerAssetAmount];
it('returns input fee orders and non-zero remainingFeeAmount', async () => {
const { resultOrders, remainingFeeAmount } = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
inputOrders,
remainingFillableMakerAssetAmounts,
inputFeeOrders,
remainingFillableFeeAmounts,
);
expect(resultOrders).to.be.deep.equal(inputFeeOrders);
expect(remainingFeeAmount).to.be.bignumber.equal(new BigNumber(30));
});
});
});
});

View File

@@ -22,7 +22,8 @@ describe('Signature utils', () => {
let address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
it("should return false if the data doesn't pertain to the signature & address", async () => {
expect(await isValidSignatureAsync(provider, '0x0', ethSignSignature, address)).to.be.false();
const bytes32Zeros = '0x0000000000000000000000000000000000000000000000000000000000000000';
expect(await isValidSignatureAsync(provider, bytes32Zeros, ethSignSignature, address)).to.be.false();
});
it("should return false if the address doesn't pertain to the signature & data", async () => {
const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42';

View File

@@ -0,0 +1,32 @@
import { Order, SignedOrder } from '@0xproject/types';
import * as _ from 'lodash';
import { constants, orderFactory } from '../../src';
const BASE_TEST_ORDER: Order = orderFactory.createOrder(
constants.NULL_ADDRESS,
constants.ZERO_AMOUNT,
constants.NULL_ADDRESS,
constants.ZERO_AMOUNT,
constants.NULL_ADDRESS,
constants.NULL_ADDRESS,
);
const BASE_TEST_SIGNED_ORDER: SignedOrder = {
...BASE_TEST_ORDER,
signature: constants.NULL_BYTES,
};
export const testOrderFactory = {
generateTestSignedOrder(partialOrder: Partial<SignedOrder>): SignedOrder {
return transformObject(BASE_TEST_SIGNED_ORDER, partialOrder);
},
generateTestSignedOrders(partialOrder: Partial<SignedOrder>, numOrders: number): SignedOrder[] {
const baseTestOrders = _.map(_.range(numOrders), () => BASE_TEST_SIGNED_ORDER);
return _.map(baseTestOrders, order => transformObject(order, partialOrder));
},
};
function transformObject<T>(input: T, transformation: Partial<T>): T {
const copy = _.cloneDeep(input);
return _.assign(copy, transformation);
}

View File

@@ -1,4 +1,12 @@
[
{
"version": "1.0.1-rc.3",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "1.0.1-rc.2",
"changes": [

View File

@@ -67,7 +67,7 @@
"sinon": "^4.0.0",
"source-map-support": "^0.5.0",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@0xproject/assert": "^1.0.4",

View File

@@ -45,7 +45,7 @@
"source-map-loader": "^0.2.3",
"style-loader": "^0.20.2",
"tslint": "^5.9.1",
"typescript": "2.7.1",
"typescript": "2.9.2",
"webpack": "^3.11.0",
"webpack-dev-server": "^2.11.1"
},

View File

@@ -33,7 +33,7 @@
"make-promises-safe": "^1.1.0",
"shx": "^0.2.2",
"tslint": "^5.9.1",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@0xproject/react-shared": "^1.0.5",

View File

@@ -32,7 +32,7 @@
"make-promises-safe": "^1.1.0",
"shx": "^0.2.2",
"tslint": "^5.9.1",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@types/is-mobile": "0.3.0",

View File

@@ -71,7 +71,7 @@
"tslint": "5.11.0",
"typedoc": "0xProject/typedoc",
"types-bn": "^0.0.1",
"typescript": "2.7.1",
"typescript": "2.9.2",
"web3-typescript-typings": "^0.10.2",
"zeppelin-solidity": "1.8.0"
},

View File

@@ -89,7 +89,7 @@
"sinon": "^4.0.0",
"tslint": "5.11.0",
"typedoc": "0xProject/typedoc",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"publishConfig": {
"access": "public"

View File

@@ -5,7 +5,6 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
## v1.0.4 - _July 26, 2018_
* Dependencies updated

View File

@@ -30,7 +30,7 @@
"make-promises-safe": "^1.1.0",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@0xproject/types": "^1.0.1-rc.3",

View File

@@ -66,7 +66,7 @@
"nyc": "^11.0.1",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"publishConfig": {
"access": "public"

View File

@@ -84,7 +84,7 @@
"sinon": "^4.0.0",
"tslint": "5.11.0",
"typedoc": "0xProject/typedoc",
"typescript": "2.7.1",
"typescript": "2.9.2",
"webpack": "^3.1.0"
},
"optionalDependencies": {

View File

@@ -43,7 +43,7 @@
"shx": "^0.2.2",
"source-map-loader": "^0.1.6",
"tslint": "5.11.0",
"typescript": "2.7.1",
"typescript": "2.9.2",
"webpack": "^3.1.0",
"webpack-node-externals": "^1.6.0"
}

View File

@@ -39,7 +39,7 @@
"copyfiles": "^1.2.0",
"make-promises-safe": "^1.1.0",
"shx": "^0.2.2",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"lodash": "^4.17.4",

View File

@@ -30,7 +30,7 @@
"make-promises-safe": "^1.1.0",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@types/node": "^8.0.53",

View File

@@ -34,4 +34,22 @@ declare module 'ethers' {
const enum errors {
INVALID_ARGUMENT = 'INVALID_ARGUMENT',
}
export type ParamName = null | string | NestedParamName;
export interface NestedParamName {
name: string | null;
names: ParamName[];
}
export const utils: {
AbiCoder: {
defaultCoder: AbiCoder;
};
};
export interface AbiCoder {
encode: (names: ParamName[] | string[], types: string[] | any[], args: any[] | undefined) => string;
decode: (names: ParamName[] | string[], types: string[] | string, data: string | undefined) => any;
}
}

View File

View File

@@ -5,12 +5,17 @@
"node": ">=6.12"
},
"description": "0x TS utils",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
"watch_without_deps": "tsc -w",
"build": "tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts",
"clean": "shx rm -rf lib scripts",
"test": "yarn run_mocha",
"test:circleci": "yarn test:coverage",
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --bail --exit",
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
"lint": "tslint --project .",
"manual:postpublish": "yarn build; node ./scripts/postpublish.js"
},
@@ -27,12 +32,15 @@
"@0xproject/monorepo-scripts": "^1.0.4",
"@0xproject/tslint-config": "^1.0.4",
"@types/lodash": "4.14.104",
"@types/mocha": "^2.2.42",
"copyfiles": "^1.2.0",
"make-promises-safe": "^1.1.0",
"npm-run-all": "^4.1.2",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typescript": "2.7.1"
"typescript": "2.9.2",
"chai": "^4.0.1",
"mocha": "^4.0.1"
},
"dependencies": {
"@0xproject/types": "^1.0.1-rc.3",

View File

@@ -1,7 +1,160 @@
import { AbiDefinition, AbiType, ContractAbi, DataItem, MethodAbi } from 'ethereum-types';
import * as ethers from 'ethers';
import * as _ from 'lodash';
import { BigNumber } from './configured_bignumber';
// Note(albrow): This function is unexported in ethers.js. Copying it here for
// now.
// Source: https://github.com/ethers-io/ethers.js/blob/884593ab76004a808bf8097e9753fb5f8dcc3067/contracts/interface.js#L30
function parseEthersParams(params: DataItem[]): { names: ethers.ParamName[]; types: string[] } {
const names: ethers.ParamName[] = [];
const types: string[] = [];
params.forEach((param: DataItem) => {
if (param.components != null) {
let suffix = '';
const arrayBracket = param.type.indexOf('[');
if (arrayBracket >= 0) {
suffix = param.type.substring(arrayBracket);
}
const result = parseEthersParams(param.components);
names.push({ name: param.name || null, names: result.names });
types.push('tuple(' + result.types.join(',') + ')' + suffix);
} else {
names.push(param.name || null);
types.push(param.type);
}
});
return {
names,
types,
};
}
// returns true if x is equal to y and false otherwise. Performs some minimal
// type conversion and data massaging for x and y, depending on type. name and
// type should typically be derived from parseEthersParams.
function isAbiDataEqual(name: ethers.ParamName, type: string, x: any, y: any): boolean {
if (_.isUndefined(x) && _.isUndefined(y)) {
return true;
} else if (_.isUndefined(x) && !_.isUndefined(y)) {
return false;
} else if (!_.isUndefined(x) && _.isUndefined(y)) {
return false;
}
if (_.endsWith(type, '[]')) {
// For array types, we iterate through the elements and check each one
// individually. Strangely, name does not need to be changed in this
// case.
if (x.length !== y.length) {
return false;
}
const newType = _.trimEnd(type, '[]');
for (let i = 0; i < x.length; i++) {
if (!isAbiDataEqual(name, newType, x[i], y[i])) {
return false;
}
}
return true;
}
if (_.startsWith(type, 'tuple(')) {
if (_.isString(name)) {
throw new Error('Internal error: type was tuple but names was a string');
} else if (_.isNull(name)) {
throw new Error('Internal error: type was tuple but names was null');
}
// For tuples, we iterate through the underlying values and check each
// one individually.
const types = splitTupleTypes(type);
if (types.length !== name.names.length) {
throw new Error(
`Internal error: parameter types/names length mismatch (${types.length} != ${name.names.length})`,
);
}
for (let i = 0; i < types.length; i++) {
// For tuples, name is an object with a names property that is an
// array. As an example, for orders, name looks like:
//
// {
// name: 'orders',
// names: [
// 'makerAddress',
// // ...
// 'takerAssetData'
// ]
// }
//
const nestedName = _.isString(name.names[i])
? (name.names[i] as string)
: ((name.names[i] as ethers.NestedParamName).name as string);
if (!isAbiDataEqual(name.names[i], types[i], x[nestedName], y[nestedName])) {
return false;
}
}
return true;
} else if (type === 'address' || type === 'bytes') {
// HACK(albrow): ethers.js returns the checksummed address even when
// initially passed in a non-checksummed address. To account for that,
// we convert to lowercase before comparing.
return _.isEqual(_.toLower(x), _.toLower(y));
} else if (_.startsWith(type, 'uint') || _.startsWith(type, 'int')) {
return new BigNumber(x).eq(new BigNumber(y));
}
return _.isEqual(x, y);
}
// splitTupleTypes splits a tuple type string (of the form `tuple(X)` where X is
// any other type or list of types) into its component types. It works with
// nested tuples, so, e.g., `tuple(tuple(uint256,address),bytes32)` will yield:
// `['tuple(uint256,address)', 'bytes32']`. It expects exactly one tuple type as
// an argument (not an array).
function splitTupleTypes(type: string): string[] {
if (_.endsWith(type, '[]')) {
throw new Error('Internal error: array types are not supported');
} else if (!_.startsWith(type, 'tuple(')) {
throw new Error('Internal error: expected tuple type but got non-tuple type: ' + type);
}
// Trim the outtermost tuple().
const trimmedType = type.substring('tuple('.length, type.length - 1);
const types: string[] = [];
let currToken = '';
let parenCount = 0;
// Tokenize the type string while keeping track of parentheses.
for (const char of trimmedType) {
switch (char) {
case '(':
parenCount += 1;
currToken += char;
break;
case ')':
parenCount -= 1;
currToken += char;
break;
case ',':
if (parenCount === 0) {
types.push(currToken);
currToken = '';
break;
} else {
currToken += char;
break;
}
default:
currToken += char;
break;
}
}
types.push(currToken);
return types;
}
export const abiUtils = {
parseEthersParams,
isAbiDataEqual,
splitTupleTypes,
parseFunctionParam(param: DataItem): string {
if (param.type === 'tuple') {
// Parse out tuple types into {type_1, type_2, ..., type_N}

View File

@@ -0,0 +1,19 @@
import * as chai from 'chai';
import 'mocha';
import { abiUtils } from '../src';
const expect = chai.expect;
describe('abiUtils', () => {
describe('splitTupleTypes', () => {
it('handles basic types', () => {
const got = abiUtils.splitTupleTypes('tuple(bytes,uint256,address)');
expect(got).to.deep.equal(['bytes', 'uint256', 'address']);
});
it('handles nested tuple types', () => {
const got = abiUtils.splitTupleTypes('tuple(tuple(bytes,uint256),address)');
expect(got).to.deep.equal(['tuple(bytes,uint256)', 'address']);
});
});
});

View File

@@ -3,5 +3,5 @@
"compilerOptions": {
"outDir": "lib"
},
"include": ["./src/**/*"]
"include": ["src/**/*", "test/**/*"]
}

View File

@@ -61,7 +61,7 @@
"shx": "^0.2.2",
"tslint": "5.11.0",
"typedoc": "0xProject/typedoc",
"typescript": "2.7.1"
"typescript": "2.9.2"
},
"dependencies": {
"@0xproject/assert": "^1.0.4",

View File

@@ -98,7 +98,7 @@
"style-loader": "0.13.x",
"tslint": "5.11.0",
"tslint-config-0xproject": "^0.0.2",
"typescript": "2.7.1",
"typescript": "2.9.2",
"uglifyjs-webpack-plugin": "^1.2.5",
"webpack": "^3.1.0",
"webpack-dev-middleware": "^1.10.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -27,7 +27,7 @@ const teamRow1: ProfileInfo[] = [
title: 'Co-founder & CTO',
description: `Smart contract R&D. Previously fixed income trader at DRW. \
Finance at University of Illinois, Urbana-Champaign.`,
image: '/images/team/amir.jpeg',
image: '/images/team/amir.png',
linkedIn: 'https://www.linkedin.com/in/abandeali1/',
github: 'https://github.com/abandeali1',
medium: 'https://medium.com/@abandeali1',

View File

@@ -565,37 +565,6 @@
lodash "4.17.10"
uuid "3.2.1"
"@0xproject/contract-wrappers@^1.0.1-rc.2":
version "1.0.1-rc.1"
dependencies:
"@0xproject/assert" "^1.0.3"
"@0xproject/base-contract" "^1.0.3"
"@0xproject/fill-scenarios" "^1.0.1-rc.1"
"@0xproject/json-schemas" "^1.0.1-rc.2"
"@0xproject/order-utils" "^1.0.1-rc.1"
"@0xproject/types" "^1.0.1-rc.2"
"@0xproject/typescript-typings" "^1.0.3"
"@0xproject/utils" "^1.0.3"
"@0xproject/web3-wrapper" "^1.1.1"
ethereum-types "^1.0.3"
ethereumjs-blockstream "5.0.0"
ethereumjs-util "^5.1.1"
ethers "3.0.22"
js-sha3 "^0.7.0"
lodash "^4.17.4"
uuid "^3.1.0"
"@0xproject/dev-utils@^1.0.3":
version "1.0.2"
dependencies:
"@0xproject/subproviders" "^1.0.3"
"@0xproject/types" "^1.0.1-rc.2"
"@0xproject/typescript-typings" "^1.0.3"
"@0xproject/utils" "^1.0.3"
"@0xproject/web3-wrapper" "^1.1.1"
ethereum-types "^1.0.3"
lodash "^4.17.4"
"@0xproject/fill-scenarios@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@0xproject/fill-scenarios/-/fill-scenarios-0.0.4.tgz#4d23c75abda7e9f117b698c0b8b142af07e0c69e"
@@ -653,23 +622,6 @@
jsonschema "1.2.2"
lodash.values "4.3.0"
"@0xproject/migrations@^1.0.3":
version "1.0.2"
dependencies:
"@0xproject/base-contract" "^1.0.3"
"@0xproject/order-utils" "^1.0.1-rc.1"
"@0xproject/sol-compiler" "^1.0.3"
"@0xproject/subproviders" "^1.0.3"
"@0xproject/typescript-typings" "^1.0.3"
"@0xproject/utils" "^1.0.3"
"@0xproject/web3-wrapper" "^1.1.1"
"@ledgerhq/hw-app-eth" "^4.3.0"
ethereum-types "^1.0.3"
ethers "3.0.22"
lodash "^4.17.4"
optionalDependencies:
"@ledgerhq/hw-transport-node-hid" "^4.3.0"
"@0xproject/order-utils@^0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@0xproject/order-utils/-/order-utils-0.0.7.tgz#eaa465782ea5745bdad54e1a851533172d993b7c"
@@ -718,25 +670,6 @@
ethereumjs-util "5.1.5"
lodash "4.17.10"
"@0xproject/order-utils@^1.0.1-rc.2":
version "1.0.1-rc.1"
dependencies:
"@0xproject/assert" "^1.0.3"
"@0xproject/base-contract" "^1.0.3"
"@0xproject/json-schemas" "^1.0.1-rc.2"
"@0xproject/sol-compiler" "^1.0.3"
"@0xproject/types" "^1.0.1-rc.2"
"@0xproject/typescript-typings" "^1.0.3"
"@0xproject/utils" "^1.0.3"
"@0xproject/web3-wrapper" "^1.1.1"
"@types/node" "^8.0.53"
bn.js "^4.11.8"
ethereum-types "^1.0.3"
ethereumjs-abi "0.6.5"
ethereumjs-util "^5.1.1"
ethers "3.0.22"
lodash "^4.17.4"
"@0xproject/order-watcher@^0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@0xproject/order-watcher/-/order-watcher-0.0.7.tgz#fbe019aa33447781096b5d562e7a3a4ec91a1da2"
@@ -13146,6 +13079,10 @@ typescript@2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.1.tgz#bb3682c2c791ac90e7c6210b26478a8da085c359"
typescript@2.9.2:
version "2.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
typewise-core@^1.2, typewise-core@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195"