Merge branch 'sol-cov-fixes' of github.com:0xProject/0x-monorepo into sol-cov-fixes

* 'sol-cov-fixes' of github.com:0xProject/0x-monorepo: (49 commits)
  Add @return comments
  Import marshaller directly
  Update comment about ethers checksummed address behavior
  Add packages/coverage/.gitkeep file
  Update CI config and package.json to run @0xproject/utils tests on CI
  Update remaining CHANGELOG.json files
  Change amir picture
  Update CHANGELOG.json for contract-wrappers
  Update ethers typings for TypeScript 2.9.2
  Update CHANGELOG.json for base-contract
  Move some ethers-related types to typescript-typings/ethers
  Apply prettier
  Add strictArgumentEncodingCheck to BaseContract and use it in contract templates
  fix(monorepo-scripts): Fix typo in git tag command
  feat(monorepo-scripts): Add confirmation prompt before publishing
  fix comments and styling for MixinSignatureValidator
  Update TypeScript to version 2.9.2
  Use asm for hashEIP712Message, increment free memory pointer after asm hashing functions
  Fix comments, styling, and optimize hashOrder
  Remove assertion comments
  ...
This commit is contained in:
Fabio Berger
2018-08-13 13:01:32 -07:00
86 changed files with 1673 additions and 203 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

@@ -1,5 +1,4 @@
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { marshaller } from '@0xproject/web3-wrapper/lib/src/marshaller';
import { marshaller, Web3Wrapper } from '@0xproject/web3-wrapper';
import { JSONRPCRequestPayload, Provider } from 'ethereum-types';
import { Callback, ErrorCallback } from '../types';

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

@@ -32,6 +32,7 @@ export const marshaller = {
/**
* Unmarshall block without transaction data
* @param blockWithHexValues block to unmarshall
* @return unmarshalled block without transaction data
*/
unmarshalIntoBlockWithoutTransactionData(
blockWithHexValues: BlockWithoutTransactionDataRPC,
@@ -51,6 +52,7 @@ export const marshaller = {
/**
* Unmarshall block with transaction data
* @param blockWithHexValues block to unmarshall
* @return unmarshalled block with transaction data
*/
unmarshalIntoBlockWithTransactionData(blockWithHexValues: BlockWithTransactionDataRPC): BlockWithTransactionData {
const block = {
@@ -73,6 +75,7 @@ export const marshaller = {
/**
* Unmarshall transaction
* @param txRpc transaction to unmarshall
* @return unmarshalled transaction
*/
unmarshalTransaction(txRpc: TransactionRPC): Transaction {
const tx = {
@@ -91,6 +94,7 @@ export const marshaller = {
/**
* Unmarshall transaction data
* @param txDataRpc transaction data to unmarshall
* @return unmarshalled transaction data
*/
unmarshalTxData(txDataRpc: TxDataRPC): TxData {
if (_.isUndefined(txDataRpc.from)) {
@@ -108,6 +112,7 @@ export const marshaller = {
/**
* Marshall transaction data
* @param txData transaction data to marshall
* @return marshalled transaction data
*/
marshalTxData(txData: Partial<TxData>): Partial<TxDataRPC> {
if (_.isUndefined(txData.from)) {
@@ -133,6 +138,7 @@ export const marshaller = {
/**
* Marshall call data
* @param callData call data to marshall
* @return marshalled call data
*/
marshalCallData(callData: Partial<CallData>): Partial<CallDataRPC> {
const callTxDataBase = {
@@ -149,6 +155,7 @@ export const marshaller = {
/**
* Marshall address
* @param address address to marshall
* @return marshalled address
*/
marshalAddress(address: string): string {
if (addressUtils.isAddress(address)) {
@@ -159,6 +166,7 @@ export const marshaller = {
/**
* Marshall block param
* @param blockParam block param to marshall
* @return marshalled block param
*/
marshalBlockParam(blockParam: BlockParam | string | number | undefined): string | undefined {
if (_.isUndefined(blockParam)) {
@@ -170,6 +178,7 @@ export const marshaller = {
/**
* Unmarshall log
* @param rawLog log to unmarshall
* @return unmarshalled log
*/
unmarshalLog(rawLog: RawLogEntry): LogEntry {
const formattedLog = {

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"