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:
@@ -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:
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
[
|
||||
{
|
||||
"version": "1.0.1-rc.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.0.1-rc.2",
|
||||
"changes": [
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
114
packages/base-contract/test/base_contract_test.ts
Normal file
114
packages/base-contract/test/base_contract_test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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"
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
130
packages/contract-wrappers/test/forwarder_wrapper_test.ts
Normal file
130
packages/contract-wrappers/test/forwarder_wrapper_test.ts
Normal 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
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
{
|
||||
"note": "Allow for additional properties in txData schema",
|
||||
"pr": 938
|
||||
},
|
||||
{
|
||||
"note": "Change hexSchema to match `0x`",
|
||||
"pr": 937
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
133
packages/order-utils/src/market_utils.ts
Normal file
133
packages/order-utils/src/market_utils.ts
Normal 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
|
||||
},
|
||||
};
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
280
packages/order-utils/test/market_utils_test.ts
Normal file
280
packages/order-utils/test/market_utils_test.ts
Normal 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));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
32
packages/order-utils/test/utils/test_order_factory.ts
Normal file
32
packages/order-utils/test/utils/test_order_factory.ts
Normal 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);
|
||||
}
|
||||
@@ -1,4 +1,12 @@
|
||||
[
|
||||
{
|
||||
"version": "1.0.1-rc.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.0.1-rc.2",
|
||||
"changes": [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -5,7 +5,6 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
|
||||
## v1.0.4 - _July 26, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
0
packages/utils/coverage/.gitkeep
Normal file
0
packages/utils/coverage/.gitkeep
Normal 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",
|
||||
|
||||
@@ -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}
|
||||
|
||||
19
packages/utils/test/abi_utils_test.ts
Normal file
19
packages/utils/test/abi_utils_test.ts
Normal 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']);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,5 +3,5 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["./src/**/*"]
|
||||
"include": ["src/**/*", "test/**/*"]
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 |
BIN
packages/website/public/images/team/amir.png
Normal file
BIN
packages/website/public/images/team/amir.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
@@ -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',
|
||||
|
||||
71
yarn.lock
71
yarn.lock
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user