Merge branch 'development' into sol-cov-fixes
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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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