Compare commits

..

69 Commits

Author SHA1 Message Date
Leonid
c567249819 Merge pull request #157 from 0xProject/feature/throw-for-exchange-errors
Feature/throw for exchange errors
2017-09-06 18:32:02 +02:00
Leonid Logvinov
d2dc4d18be Add zeroEx.exchange.throwLogErrorsAsErrors to CHANGELOG 2017-09-06 18:27:25 +02:00
Leonid Logvinov
67ed341f24 Use LogError type 2017-09-06 18:26:17 +02:00
Leonid Logvinov
0b6e874a0d Fix a comment 2017-09-06 18:25:03 +02:00
Leonid Logvinov
88791f732f Make intervalUtils an object instead of a class and make instance variable local 2017-09-06 18:22:28 +02:00
Leonid Logvinov
b1feb5ac29 Fix a bug in intervalUtils 2017-09-06 17:49:51 +02:00
Leonid Logvinov
873aa26f63 Update CHANGELOG 2017-09-06 17:45:10 +02:00
Leonid Logvinov
a57b22a6bc Fix overlapping async intervals issue 2017-09-06 17:41:40 +02:00
Leonid Logvinov
7c61b09dce Add zeroEx.exchange.throwLogErrorsAsErrors 2017-09-06 17:29:15 +02:00
Leonid Logvinov
b38aff8808 Fix log decoder to return correct types 2017-09-06 17:26:55 +02:00
Leonid Logvinov
912d15cb73 0.13.0 2017-09-06 15:35:15 +02:00
Leonid Logvinov
9dc13360c9 Revert "0.13.0"
This reverts commit 92eb68bf2c.
2017-09-06 15:35:03 +02:00
Leonid
cb44b77d0b Merge pull request #156 from 0xProject/feature/custom-abi-decoder
Custom abi decoder
2017-09-06 15:26:29 +02:00
Leonid Logvinov
1baf065317 Use startsWith instead of includes 2017-09-06 15:26:15 +02:00
Leonid Logvinov
88c96c7052 Use template strings 2017-09-06 15:15:00 +02:00
Leonid Logvinov
9680cc1270 Use _.includes instead of indexOf 2017-09-06 15:14:36 +02:00
Leonid Logvinov
68d051d4a7 Rename methodID to methodId 2017-09-06 15:11:41 +02:00
Leonid Logvinov
f1ed572819 Remove redundant js prefix 2017-09-06 15:11:08 +02:00
Leonid Logvinov
9ae56485a9 Fix type error 2017-09-06 15:10:59 +02:00
Leonid Logvinov
a7ba16ef4a Handle the case, when it's not possible to decode args 2017-09-06 14:31:52 +02:00
Leonid Logvinov
35f3396295 Implement custom ABI decoder 2017-09-06 14:29:52 +02:00
Leonid Logvinov
10817aa337 Add types for web3/lib/solidity/coder.js 2017-09-06 14:29:09 +02:00
Leonid Logvinov
542aae6cd9 Remove abi-decoder 2017-09-06 14:28:29 +02:00
Leonid Logvinov
92eb68bf2c 0.13.0 2017-09-06 13:02:31 +02:00
Leonid Logvinov
aa7d10e510 Update web-typescript-typings 2017-09-06 13:01:33 +02:00
Leonid Logvinov
7377df8a4d Define sendAsync on HDWalletProvider 2017-09-06 13:00:13 +02:00
Leonid Logvinov
70a7f02d0f Fix import in order_validation_utils 2017-09-06 12:51:47 +02:00
Leonid Logvinov
5fb4b54153 Specify the release date 2017-09-06 12:48:47 +02:00
Leonid Logvinov
c64154e33a Add missing separators in CHANGELOG 2017-09-06 10:47:31 +02:00
Leonid
07da617c05 Merge pull request #152 from 0xProject/fix/signature-verification
Add signature verification as a part of order validation
2017-09-06 10:39:43 +02:00
Leonid
35c133caed Merge branch 'development' into fix/signature-verification 2017-09-06 10:35:19 +02:00
Leonid Logvinov
18a52a1ea7 Update CHANGELOG 2017-09-06 10:34:31 +02:00
Leonid Logvinov
501f054d51 Add signature verification as a part of order validation and tests for it 2017-09-06 10:33:34 +02:00
Leonid
f0a5ad2d20 Merge pull request #151 from 0xProject/feature/remove-truffle-contracts
Remove truffle contracts dependency
2017-09-06 10:28:37 +02:00
Leonid Logvinov
258b4fac31 Fix a typo in test name 2017-09-06 10:26:22 +02:00
Leonid Logvinov
8ebc724379 Add lifecycle methods 2017-09-06 00:40:22 +02:00
Leonid Logvinov
df904f80e3 Add test for logs decoding in awaitTransactionMinedAsync 2017-09-06 00:18:07 +02:00
Leonid Logvinov
e6e12e946e Update CHANGELOG 2017-09-05 18:59:46 +02:00
Leonid Logvinov
2fd5f2781b Add forgotten artifacts file 2017-09-05 18:59:18 +02:00
Leonid Logvinov
2f97ddb727 Fix the return types and export the required public types 2017-09-05 18:50:22 +02:00
Leonid Logvinov
a7b2131db7 Decode logs args in awaitTransactionMinedAsync 2017-09-05 18:45:20 +02:00
Leonid Logvinov
f057267955 Update json-schemas 2017-09-05 16:45:40 +02:00
Leonid Logvinov
b0547819fd Define AbiType 2017-09-05 15:44:42 +02:00
Leonid Logvinov
dff63f9b89 Use AbiType 2017-09-05 15:34:52 +02:00
Leonid Logvinov
ee00769be1 Use schema validation to distinguish txData argument 2017-09-05 15:29:29 +02:00
Leonid Logvinov
ec22097efb Don't override function arguments 2017-09-05 13:49:47 +02:00
Leonid Logvinov
bc5fd316df Cast to Artifat type 2017-09-05 13:47:17 +02:00
Leonid Logvinov
92b101fac8 Remove unused code 2017-09-05 13:46:13 +02:00
Leonid Logvinov
78c46d7cc9 Change the order of default overriding 2017-09-05 13:45:32 +02:00
Leonid Logvinov
9f12ef61b0 Rename x.call -> x.callAsync x() -> x.sendTransactionAsync() x.estimateGas() -> x.estimateGasAsync() 2017-09-05 13:43:46 +02:00
Leonid Logvinov
e05dfab1fc Add explaining comment 2017-09-05 13:14:27 +02:00
Leonid Logvinov
b5c6c91962 Increase the default polling interval to 1000 2017-09-05 12:52:44 +02:00
Leonid Logvinov
f6a945dfe4 Fix the comment at awaitTransactionMinedAsync 2017-09-05 12:52:00 +02:00
Leonid Logvinov
a12df1c73a Fix gasPrice regression 2017-09-05 11:38:28 +02:00
Leonid Logvinov
876032a8a7 Update CHANGELOG 2017-09-05 10:42:00 +02:00
Leonid Logvinov
96d2a55eff Add TransationReceipt as a public exported type 2017-09-05 10:29:51 +02:00
Leonid Logvinov
5d57a2f0e9 Increase timeout 2017-09-05 10:22:22 +02:00
Leonid Logvinov
2b547f94a4 Change non-exhange contracts to also return txHash 2017-09-05 10:07:16 +02:00
Leonid Logvinov
c9e490bdae Implement zeroEx.awaitTransactionMined 2017-09-04 19:08:14 +02:00
Leonid Logvinov
6325a03818 Temporarily remove web3_beta, cause it breaks installation and tests 2017-09-04 18:48:18 +02:00
Leonid Logvinov
2577d8f662 Use Web3.ContractInstance type 2017-09-04 18:23:12 +02:00
Leonid Logvinov
1ad395cf86 Make the functions immidiately return txHash instead of awaiting for a transaction to be mined 2017-09-04 18:14:48 +02:00
Leonid Logvinov
1c2d4cbb1a Fix tests descriptions 2017-09-04 16:57:22 +02:00
Leonid Logvinov
9818eb2835 Use custom contract abstraction 2017-09-04 14:45:01 +02:00
Leonid Logvinov
59fed02a8b Remove truffle-contract from deps 2017-09-04 12:07:17 +02:00
Fabio Berger
0275ac9dad Merge pull request #146 from 0xProject/greenkeeper/0x-json-schemas-0.4.0
Update 0x-json-schemas to the latest version 🚀
2017-09-03 10:15:30 +02:00
Leonid Logvinov
62452db5d8 0.12.1 2017-09-02 05:18:31 +02:00
Leonid Logvinov
792646888a Update CHANGELOG version 2017-09-02 05:18:24 +02:00
greenkeeper[bot]
66066b9722 fix(package): update 0x-json-schemas to version 0.4.0 2017-08-31 12:37:57 +00:00
27 changed files with 706 additions and 1143 deletions

View File

@@ -1,6 +1,20 @@
# CHANGELOG
v0.12.0 - _September 2, 2017_
v0.13.1 - _September 6, 2017_
------------------------
* Added `zeroEx.exchange.throwLogErrorsAsErrors` method to public interface (#157)
* Fixed an issue with overlapping async intervals in `zeroEx.awaitTransactionMinedAsync` (#157)
* Fixed an issue with log decoder returning `BigNumber`s as `strings` (#157)
v0.13.0 - _September 6, 2017_
------------------------
* Made all the functions submitting transactions to the network to immediately return transaction hash (#151)
* Added `zeroEx.awaitTransactionMinedAsync` (#151)
* Added `TransactionReceiptWithDecodedLogs`, `LogWithDecodedArgs`, `DecodedLogArgs` to public types (#151)
* Added signature validation to `validateFillOrderThrowIfInvalidAsync` (#152)
v0.12.1 - _September 2, 2017_
------------------------
* Added the support for web3@1.x.x provider (#142)
* Added the optional `zeroExConfig` parameter to the constructor of `ZeroEx` (#139)
* Added the ability to specify `gasPrice` when instantiating `ZeroEx` (#139)

View File

@@ -1,6 +1,6 @@
{
"name": "0x.js",
"version": "0.12.0",
"version": "0.13.0",
"description": "A javascript library for interacting with the 0x protocol",
"keywords": [
"0x.js",
@@ -36,7 +36,7 @@
"pretest:umd": "run-s clean build:umd:dev build:commonjs",
"substitute_umd_bundle": "npm run remove_src_files_not_used_by_tests; shx mv _bundles/* lib/src",
"remove_src_files_not_used_by_tests": "find ./lib/src \\( -path ./lib/src/utils -o -path ./lib/src/subproviders -o -path ./lib/src/schemas -o -path \"./lib/src/types.*\" \\) -prune -o -type f -print | xargs rm",
"run_mocha": "mocha lib/test/**/*_test.js --timeout 4000 --bail"
"run_mocha": "mocha lib/test/**/*_test.js --timeout 5000 --bail"
},
"config": {
"artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken",
@@ -85,13 +85,12 @@
"types-bn": "^0.0.1",
"types-ethereumjs-util": "^0.0.5",
"typescript": "^2.4.1",
"web3_beta": "ethereum/web3.js#1.0",
"web3-provider-engine": "^13.0.1",
"web3-typescript-typings": "^0.3.2",
"web3-typescript-typings": "^0.6.0",
"webpack": "^3.1.0"
},
"dependencies": {
"0x-json-schemas": "^0.3.0",
"0x-json-schemas": "^0.5.1",
"bignumber.js": "^4.0.2",
"compare-versions": "^3.0.1",
"es6-promisify": "^5.0.0",
@@ -100,7 +99,6 @@
"find-versions": "^2.0.0",
"lodash": "^4.17.4",
"publish-release": "^1.3.3",
"truffle-contract": "^2.0.1",
"web3": "^0.20.0"
}
}

View File

@@ -1,9 +1,10 @@
import * as _ from 'lodash';
import * as BigNumber from 'bignumber.js';
import * as Web3 from 'web3';
import * as abiDecoder from 'abi-decoder';
import {SchemaValidator, schemas} from '0x-json-schemas';
import {bigNumberConfigs} from './bignumber_config';
import * as ethUtil from 'ethereumjs-util';
import contract = require('truffle-contract');
import findVersions = require('find-versions');
import compareVersions = require('compare-versions');
import {Web3Wrapper} from './web3_wrapper';
@@ -11,12 +12,26 @@ import {constants} from './utils/constants';
import {utils} from './utils/utils';
import {signatureUtils} from './utils/signature_utils';
import {assert} from './utils/assert';
import {AbiDecoder} from './utils/abi_decoder';
import {intervalUtils} from './utils/interval_utils';
import {artifacts} from './artifacts';
import {ExchangeWrapper} from './contract_wrappers/exchange_wrapper';
import {TokenRegistryWrapper} from './contract_wrappers/token_registry_wrapper';
import {EtherTokenWrapper} from './contract_wrappers/ether_token_wrapper';
import {TokenWrapper} from './contract_wrappers/token_wrapper';
import {TokenTransferProxyWrapper} from './contract_wrappers/token_transfer_proxy_wrapper';
import {ECSignature, ZeroExError, Order, SignedOrder, Web3Provider, ZeroExConfig} from './types';
import {
ECSignature,
ZeroExError,
Order,
SignedOrder,
Web3Provider,
ZeroExConfig,
TransactionReceipt,
DecodedLogArgs,
TransactionReceiptWithDecodedLogs,
LogWithDecodedArgs,
} from './types';
// Customize our BigNumber instances
bigNumberConfigs.configure();
@@ -57,6 +72,7 @@ export class ZeroEx {
*/
public proxy: TokenTransferProxyWrapper;
private _web3Wrapper: Web3Wrapper;
private _abiDecoder: AbiDecoder;
/**
* Verifies that the elliptic curve signature `signature` was generated
* by signing `data` with the private key corresponding to the `signerAddress` address.
@@ -170,13 +186,19 @@ export class ZeroEx {
// We re-assign the send method so that Web3@1.0 providers work with 0x.js
(provider as any).sendAsync = (provider as any).send;
}
this._web3Wrapper = new Web3Wrapper(provider);
const artifactJSONs = _.values(artifacts);
const abiArrays = _.map(artifactJSONs, artifact => artifact.abi);
this._abiDecoder = new AbiDecoder(abiArrays);
const gasPrice = _.isUndefined(config) ? undefined : config.gasPrice;
this.token = new TokenWrapper(this._web3Wrapper, gasPrice);
this.proxy = new TokenTransferProxyWrapper(this._web3Wrapper, gasPrice);
this.exchange = new ExchangeWrapper(this._web3Wrapper, this.token, gasPrice);
this.tokenRegistry = new TokenRegistryWrapper(this._web3Wrapper, gasPrice);
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, this.token, gasPrice);
const defaults = {
gasPrice,
};
this._web3Wrapper = new Web3Wrapper(provider, defaults);
this.token = new TokenWrapper(this._web3Wrapper);
this.proxy = new TokenTransferProxyWrapper(this._web3Wrapper);
this.exchange = new ExchangeWrapper(this._web3Wrapper, this.token);
this.tokenRegistry = new TokenRegistryWrapper(this._web3Wrapper);
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, this.token);
}
/**
* Sets a new web3 provider for 0x.js. Updating the provider will stop all
@@ -249,4 +271,39 @@ export class ZeroEx {
throw new Error(ZeroExError.InvalidSignature);
}
/**
* Waits for a transaction to be mined and returns the transaction receipt.
* @param txHash Transaction hash
* @param pollingIntervalMs How often (in ms) should we check if the transaction is mined.
* @return Transaction receipt with decoded log args.
*/
public async awaitTransactionMinedAsync(
txHash: string, pollingIntervalMs: number = 1000): Promise<TransactionReceiptWithDecodedLogs> {
const txReceiptPromise = new Promise(
(resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => {
const intervalId = intervalUtils.setAsyncExcludingInterval(async () => {
const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash);
if (!_.isNull(transactionReceipt)) {
intervalUtils.clearAsyncExcludingInterval(intervalId);
const logsWithDecodedArgs = _.map(transactionReceipt.logs, (log: Web3.LogEntry) => {
const decodedLog = this._abiDecoder.decodeLog(log);
if (_.isUndefined(decodedLog)) {
return log;
}
const logWithDecodedArgs: LogWithDecodedArgs = {
...log,
...decodedLog,
};
return logWithDecodedArgs;
});
const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = {
...transactionReceipt,
logs: logsWithDecodedArgs,
};
resolve(transactionReceiptWithDecodedLogArgs);
}
}, pollingIntervalMs);
});
return txReceiptPromise;
}
}

13
src/artifacts.ts Normal file
View File

@@ -0,0 +1,13 @@
import * as TokenArtifact from './artifacts/Token.json';
import * as ExchangeArtifact from './artifacts/Exchange.json';
import * as EtherTokenArtifact from './artifacts/EtherToken.json';
import * as TokenRegistryArtifact from './artifacts/TokenRegistry.json';
import * as TokenTransferProxyArtifact from './artifacts/TokenTransferProxy.json';
export const artifacts = {
TokenArtifact: TokenArtifact as any as Artifact,
ExchangeArtifact: ExchangeArtifact as any as Artifact,
EtherTokenArtifact: EtherTokenArtifact as any as Artifact,
TokenRegistryArtifact: TokenRegistryArtifact as any as Artifact,
TokenTransferProxyArtifact: TokenTransferProxyArtifact as any as Artifact,
};

View File

@@ -391,4 +391,4 @@
},
"schema_version": "0.0.5",
"updated_at": 1503318938233
}
}

80
src/contract.ts Normal file
View File

@@ -0,0 +1,80 @@
import * as Web3 from 'web3';
import * as _ from 'lodash';
import promisify = require('es6-promisify');
import {SchemaValidator, schemas} from '0x-json-schemas';
import {AbiType} from './types';
export class Contract implements Web3.ContractInstance {
public address: string;
public abi: Web3.ContractAbi;
private contract: Web3.ContractInstance;
private defaults: Partial<Web3.TxData>;
private validator: SchemaValidator;
// This class instance is going to be populated with functions and events depending on the ABI
// and we don't know their types in advance
[name: string]: any;
constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<Web3.TxData>) {
this.contract = web3ContractInstance;
this.address = web3ContractInstance.address;
this.abi = web3ContractInstance.abi;
this.defaults = defaults;
this.populateEvents();
this.populateFunctions();
this.validator = new SchemaValidator();
}
private populateFunctions(): void {
const functionsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Function);
_.forEach(functionsAbi, (functionAbi: Web3.MethodAbi) => {
if (functionAbi.constant) {
const cbStyleCallFunction = this.contract[functionAbi.name].call;
this[functionAbi.name] = {
callAsync: promisify(cbStyleCallFunction, this.contract),
};
} else {
const cbStyleFunction = this.contract[functionAbi.name];
const cbStyleEstimateGasFunction = this.contract[functionAbi.name].estimateGas;
this[functionAbi.name] = {
estimateGasAsync: promisify(cbStyleEstimateGasFunction, this.contract),
sendTransactionAsync: this.promisifyWithDefaultParams(cbStyleFunction),
};
}
});
}
private populateEvents(): void {
const eventsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Event);
_.forEach(eventsAbi, (eventAbi: Web3.EventAbi) => {
this[eventAbi.name] = this.contract[eventAbi.name];
});
}
private promisifyWithDefaultParams(fn: (...args: any[]) => void): (...args: any[]) => Promise<any> {
const promisifiedWithDefaultParams = (...args: any[]) => {
const promise = new Promise((resolve, reject) => {
const lastArg = args[args.length - 1];
let txData: Partial<Web3.TxData> = {};
if (this.isTxData(lastArg)) {
txData = args.pop();
}
txData = {
...this.defaults,
...txData,
};
const callback = (err: Error, data: any) => {
if (_.isNull(err)) {
resolve(data);
} else {
reject(err);
}
};
args.push(txData);
args.push(callback);
fn.apply(this.contract, args);
});
return promise;
};
return promisifiedWithDefaultParams;
}
private isTxData(lastArg: any): boolean {
const isValid = this.validator.isValid(lastArg, schemas.txDataSchema);
return isValid;
}
}

View File

@@ -1,53 +1,18 @@
import * as _ from 'lodash';
import contract = require('truffle-contract');
import * as Web3 from 'web3';
import {Web3Wrapper} from '../web3_wrapper';
import {ZeroExError, Artifact, ContractInstance} from '../types';
import {ZeroExError} from '../types';
import {utils} from '../utils/utils';
export class ContractWrapper {
protected _web3Wrapper: Web3Wrapper;
private _gasPrice?: BigNumber.BigNumber;
constructor(web3Wrapper: Web3Wrapper, gasPrice?: BigNumber.BigNumber) {
constructor(web3Wrapper: Web3Wrapper) {
this._web3Wrapper = web3Wrapper;
this._gasPrice = gasPrice;
}
protected async _instantiateContractIfExistsAsync(artifact: Artifact, address?: string): Promise<ContractInstance> {
const c = await contract(artifact);
c.defaults({
gasPrice: this._gasPrice,
});
const providerObj = this._web3Wrapper.getCurrentProvider();
c.setProvider(providerObj);
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
const artifactNetworkConfigs = _.isUndefined(networkIdIfExists) ?
undefined :
artifact.networks[networkIdIfExists];
let contractAddress;
if (!_.isUndefined(address)) {
contractAddress = address;
} else if (!_.isUndefined(artifactNetworkConfigs)) {
contractAddress = artifactNetworkConfigs.address.toLowerCase();
}
if (!_.isUndefined(contractAddress)) {
const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress);
if (!doesContractExist) {
throw new Error(ZeroExError.ContractDoesNotExist);
}
}
try {
const contractInstance = _.isUndefined(address) ? await c.deployed() : await c.at(address);
return contractInstance;
} catch (err) {
const errMsg = `${err}`;
if (_.includes(errMsg, 'not been deployed to detected network')) {
throw new Error(ZeroExError.ContractDoesNotExist);
} else {
utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`);
throw new Error(ZeroExError.UnhandledError);
}
}
protected async _instantiateContractIfExistsAsync<A extends Web3.ContractInstance>(artifact: Artifact,
address?: string): Promise<A> {
const contractInstance =
await this._web3Wrapper.getContractInstanceFromArtifactAsync<A>(artifact, address);
return contractInstance;
}
}

View File

@@ -4,7 +4,7 @@ import {ContractWrapper} from './contract_wrapper';
import {TokenWrapper} from './token_wrapper';
import {EtherTokenContract, ZeroExError} from '../types';
import {assert} from '../utils/assert';
import * as EtherTokenArtifacts from '../artifacts/EtherToken.json';
import {artifacts} from '../artifacts';
/**
* This class includes all the functionality related to interacting with a wrapped Ether ERC20 token contract.
@@ -13,8 +13,8 @@ import * as EtherTokenArtifacts from '../artifacts/EtherToken.json';
export class EtherTokenWrapper extends ContractWrapper {
private _etherTokenContractIfExists?: EtherTokenContract;
private _tokenWrapper: TokenWrapper;
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper, gasPrice?: BigNumber.BigNumber) {
super(web3Wrapper, gasPrice);
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper) {
super(web3Wrapper);
this._tokenWrapper = tokenWrapper;
}
/**
@@ -23,8 +23,9 @@ export class EtherTokenWrapper extends ContractWrapper {
* for ETH.
* @param amountInWei Amount of ETH in Wei the caller wishes to deposit.
* @param depositor The hex encoded user Ethereum address that would like to make the deposit.
* @return Transaction hash.
*/
public async depositAsync(amountInWei: BigNumber.BigNumber, depositor: string): Promise<void> {
public async depositAsync(amountInWei: BigNumber.BigNumber, depositor: string): Promise<string> {
assert.isBigNumber('amountInWei', amountInWei);
await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper);
@@ -32,18 +33,20 @@ export class EtherTokenWrapper extends ContractWrapper {
assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit);
const wethContract = await this._getEtherTokenContractAsync();
await wethContract.deposit({
const txHash = await wethContract.deposit.sendTransactionAsync({
from: depositor,
value: amountInWei,
});
return txHash;
}
/**
* Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the
* equivalent number of wrapped ETH tokens.
* @param amountInWei Amount of ETH in Wei the caller wishes to withdraw.
* @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl.
* @return Transaction hash.
*/
public async withdrawAsync(amountInWei: BigNumber.BigNumber, withdrawer: string): Promise<void> {
public async withdrawAsync(amountInWei: BigNumber.BigNumber, withdrawer: string): Promise<string> {
assert.isBigNumber('amountInWei', amountInWei);
await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper);
@@ -52,9 +55,10 @@ export class EtherTokenWrapper extends ContractWrapper {
assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal);
const wethContract = await this._getEtherTokenContractAsync();
await wethContract.withdraw(amountInWei, {
const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, {
from: withdrawer,
});
return txHash;
}
/**
* Retrieves the Wrapped Ether token contract address
@@ -71,7 +75,9 @@ export class EtherTokenWrapper extends ContractWrapper {
if (!_.isUndefined(this._etherTokenContractIfExists)) {
return this._etherTokenContractIfExists;
}
const contractInstance = await this._instantiateContractIfExistsAsync((EtherTokenArtifacts as any));
const contractInstance = await this._instantiateContractIfExistsAsync<EtherTokenContract>(
artifacts.EtherTokenArtifact,
);
this._etherTokenContractIfExists = contractInstance as EtherTokenContract;
return this._etherTokenContractIfExists;
}

View File

@@ -21,12 +21,12 @@ import {
IndexedFilterValues,
CreateContractEvent,
ContractEventObj,
ContractResponse,
OrderCancellationRequest,
OrderFillRequest,
LogErrorContractEventArgs,
LogFillContractEventArgs,
LogCancelContractEventArgs,
LogWithDecodedArgs,
} from '../types';
import {assert} from '../utils/assert';
import {utils} from '../utils/utils';
@@ -36,13 +36,17 @@ import {ContractWrapper} from './contract_wrapper';
import {constants} from '../utils/constants';
import {TokenWrapper} from './token_wrapper';
import {decorators} from '../utils/decorators';
import * as ExchangeArtifacts from '../artifacts/Exchange.json';
import {artifacts} from '../artifacts';
/**
* This class includes all the functionality related to calling methods and subscribing to
* events of the 0x Exchange smart contract.
*/
export class ExchangeWrapper extends ContractWrapper {
private _exchangeContractIfExists?: ExchangeContract;
private _exchangeLogEventEmitters: ContractEventEmitter[];
private _orderValidationUtils: OrderValidationUtils;
private _tokenWrapper: TokenWrapper;
private _exchangeContractErrCodesToMsg = {
[ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.OrderFillExpired,
[ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.OrderFillExpired,
@@ -51,10 +55,6 @@ export class ExchangeWrapper extends ContractWrapper {
[ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.OrderFillRoundingError,
[ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FillBalanceAllowanceError,
};
private _exchangeContractIfExists?: ExchangeContract;
private _exchangeLogEventEmitters: ContractEventEmitter[];
private _orderValidationUtils: OrderValidationUtils;
private _tokenWrapper: TokenWrapper;
private static _getOrderAddressesAndValues(order: Order): [OrderAddresses, OrderValues] {
const orderAddresses: OrderAddresses = [
order.maker,
@@ -73,8 +73,8 @@ export class ExchangeWrapper extends ContractWrapper {
];
return [orderAddresses, orderValues];
}
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper, gasPrice?: BigNumber.BigNumber) {
super(web3Wrapper, gasPrice);
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper) {
super(web3Wrapper);
this._tokenWrapper = tokenWrapper;
this._orderValidationUtils = new OrderValidationUtils(tokenWrapper, this);
this._exchangeLogEventEmitters = [];
@@ -91,7 +91,7 @@ export class ExchangeWrapper extends ContractWrapper {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const exchangeContract = await this._getExchangeContractAsync();
let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.call(orderHash);
let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync(orderHash);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
unavailableTakerTokenAmount = new BigNumber(unavailableTakerTokenAmount);
return unavailableTakerTokenAmount;
@@ -105,7 +105,7 @@ export class ExchangeWrapper extends ContractWrapper {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const exchangeContract = await this._getExchangeContractAsync();
let fillAmountInBaseUnits = await exchangeContract.filled.call(orderHash);
let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits);
return fillAmountInBaseUnits;
@@ -120,7 +120,7 @@ export class ExchangeWrapper extends ContractWrapper {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const exchangeContract = await this._getExchangeContractAsync();
let cancelledAmountInBaseUnits = await exchangeContract.cancelled.call(orderHash);
let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits);
return cancelledAmountInBaseUnits;
@@ -141,12 +141,12 @@ export class ExchangeWrapper extends ContractWrapper {
* @param takerAddress The user Ethereum address who would like to fill this order.
* Must be available via the supplied Web3.Provider
* passed to 0x.js.
* @return The amount of the order that was filled (in taker token baseUnits).
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
takerAddress: string): Promise<BigNumber.BigNumber> {
takerAddress: string): Promise<string> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
@@ -157,7 +157,7 @@ export class ExchangeWrapper extends ContractWrapper {
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
const gas = await exchangeInstance.fillOrder.estimateGas(
const gas = await exchangeInstance.fillOrder.estimateGasAsync(
orderAddresses,
orderValues,
fillTakerTokenAmount,
@@ -169,7 +169,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: takerAddress,
},
);
const response: ContractResponse = await exchangeInstance.fillOrder(
const txHash: string = await exchangeInstance.fillOrder.sendTransactionAsync(
orderAddresses,
orderValues,
fillTakerTokenAmount,
@@ -182,10 +182,7 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
const logFillArgs = response.logs[0].args as LogFillContractEventArgs;
const filledTakerTokenAmount = new BigNumber(logFillArgs.filledTakerTokenAmount);
return filledTakerTokenAmount;
return txHash;
}
/**
* Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount.
@@ -201,12 +198,12 @@ export class ExchangeWrapper extends ContractWrapper {
* @param takerAddress The user Ethereum address who would like to fill these
* orders. Must be available via the supplied Web3.Provider
* passed to 0x.js.
* @return The amount of the orders that was filled (in taker token baseUnits).
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async fillOrdersUpToAsync(signedOrders: SignedOrder[], fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
takerAddress: string): Promise<BigNumber.BigNumber> {
takerAddress: string): Promise<string> {
assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress);
assert.hasAtMostOneUniqueValue(takerTokenAddresses,
@@ -222,7 +219,7 @@ export class ExchangeWrapper extends ContractWrapper {
signedOrder, fillTakerTokenAmount, takerAddress);
}
if (_.isEmpty(signedOrders)) {
return new BigNumber(0); // no-op
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
}
const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => {
@@ -239,7 +236,7 @@ export class ExchangeWrapper extends ContractWrapper {
);
const exchangeInstance = await this._getExchangeContractAsync();
const gas = await exchangeInstance.fillOrdersUpTo.estimateGas(
const gas = await exchangeInstance.fillOrdersUpTo.estimateGasAsync(
orderAddressesArray,
orderValuesArray,
fillTakerTokenAmount,
@@ -251,7 +248,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: takerAddress,
},
);
const response: ContractResponse = await exchangeInstance.fillOrdersUpTo(
const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync(
orderAddressesArray,
orderValuesArray,
fillTakerTokenAmount,
@@ -264,13 +261,7 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
let filledTakerTokenAmount = new BigNumber(0);
_.each(response.logs, log => {
filledTakerTokenAmount = filledTakerTokenAmount.plus(
(log.args as LogFillContractEventArgs).filledTakerTokenAmount);
});
return filledTakerTokenAmount;
return txHash;
}
/**
* Batch version of fillOrderAsync.
@@ -288,11 +279,12 @@ export class ExchangeWrapper extends ContractWrapper {
* @param takerAddress The user Ethereum address who would like to fill
* these orders. Must be available via the supplied
* Web3.Provider passed to 0x.js.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async batchFillOrdersAsync(orderFillRequests: OrderFillRequest[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
takerAddress: string): Promise<void> {
takerAddress: string): Promise<string> {
assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema);
const exchangeContractAddresses = _.map(
orderFillRequests,
@@ -307,7 +299,7 @@ export class ExchangeWrapper extends ContractWrapper {
orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, takerAddress);
}
if (_.isEmpty(orderFillRequests)) {
return; // no-op
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
}
const orderAddressesValuesAmountsAndSignatureArray = _.map(orderFillRequests, orderFillRequest => {
@@ -325,7 +317,7 @@ export class ExchangeWrapper extends ContractWrapper {
);
const exchangeInstance = await this._getExchangeContractAsync();
const gas = await exchangeInstance.batchFillOrders.estimateGas(
const gas = await exchangeInstance.batchFillOrders.estimateGasAsync(
orderAddressesArray,
orderValuesArray,
fillTakerTokenAmounts,
@@ -337,7 +329,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: takerAddress,
},
);
const response: ContractResponse = await exchangeInstance.batchFillOrders(
const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync(
orderAddressesArray,
orderValuesArray,
fillTakerTokenAmounts,
@@ -350,7 +342,7 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
return txHash;
}
/**
* Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled,
@@ -360,10 +352,11 @@ export class ExchangeWrapper extends ContractWrapper {
* @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
* @param takerAddress The user Ethereum address who would like to fill this order.
* Must be available via the supplied Web3.Provider passed to 0x.js.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async fillOrKillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber,
takerAddress: string): Promise<void> {
takerAddress: string): Promise<string> {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
@@ -374,7 +367,7 @@ export class ExchangeWrapper extends ContractWrapper {
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
const gas = await exchangeInstance.fillOrKillOrder.estimateGas(
const gas = await exchangeInstance.fillOrKillOrder.estimateGasAsync(
orderAddresses,
orderValues,
fillTakerTokenAmount,
@@ -385,7 +378,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: takerAddress,
},
);
const response: ContractResponse = await exchangeInstance.fillOrKillOrder(
const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync(
orderAddresses,
orderValues,
fillTakerTokenAmount,
@@ -397,7 +390,7 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
return txHash;
}
/**
* Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically
@@ -405,10 +398,11 @@ export class ExchangeWrapper extends ContractWrapper {
* @param orderFillOrKillRequests An array of objects that conform to the OrderFillOrKillRequest interface.
* @param takerAddress The user Ethereum address who would like to fill there orders.
* Must be available via the supplied Web3.Provider passed to 0x.js.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async batchFillOrKillAsync(orderFillOrKillRequests: OrderFillOrKillRequest[],
takerAddress: string): Promise<void> {
takerAddress: string): Promise<string> {
assert.doesConformToSchema('orderFillOrKillRequests', orderFillOrKillRequests,
schemas.orderFillOrKillRequestsSchema);
const exchangeContractAddresses = _.map(
@@ -419,7 +413,7 @@ export class ExchangeWrapper extends ContractWrapper {
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
if (_.isEmpty(orderFillOrKillRequests)) {
return; // no-op
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
}
const exchangeInstance = await this._getExchangeContractAsync();
for (const request of orderFillOrKillRequests) {
@@ -441,7 +435,7 @@ export class ExchangeWrapper extends ContractWrapper {
const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] =
_.unzip<any>(orderAddressesValuesAndTakerTokenFillAmounts);
const gas = await exchangeInstance.batchFillOrKillOrders.estimateGas(
const gas = await exchangeInstance.batchFillOrKillOrders.estimateGasAsync(
orderAddresses,
orderValues,
fillTakerTokenAmounts,
@@ -452,7 +446,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: takerAddress,
},
);
const response: ContractResponse = await exchangeInstance.batchFillOrKillOrders(
const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync(
orderAddresses,
orderValues,
fillTakerTokenAmounts,
@@ -464,18 +458,18 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
return txHash;
}
/**
* Cancel a given fill amount of an order. Cancellations are cumulative.
* @param order An object that conforms to the Order or SignedOrder interface.
* The order you would like to cancel.
* @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel.
* @return The amount of the order that was cancelled (in taker token baseUnits).
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async cancelOrderAsync(
order: Order|SignedOrder, cancelTakerTokenAmount: BigNumber.BigNumber): Promise<BigNumber.BigNumber> {
order: Order|SignedOrder, cancelTakerTokenAmount: BigNumber.BigNumber): Promise<string> {
assert.doesConformToSchema('order', order, schemas.orderSchema);
assert.isBigNumber('takerTokenCancelAmount', cancelTakerTokenAmount);
await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper);
@@ -484,7 +478,7 @@ export class ExchangeWrapper extends ContractWrapper {
await this.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount);
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order);
const gas = await exchangeInstance.cancelOrder.estimateGas(
const gas = await exchangeInstance.cancelOrder.estimateGasAsync(
orderAddresses,
orderValues,
cancelTakerTokenAmount,
@@ -492,7 +486,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: order.maker,
},
);
const response: ContractResponse = await exchangeInstance.cancelOrder(
const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync(
orderAddresses,
orderValues,
cancelTakerTokenAmount,
@@ -501,19 +495,17 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
const logFillArgs = response.logs[0].args as LogCancelContractEventArgs;
const cancelledTakerTokenAmount = new BigNumber(logFillArgs.cancelledTakerTokenAmount);
return cancelledTakerTokenAmount;
return txHash;
}
/**
* Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction.
* All orders must be from the same maker.
* @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest
* interface.
* @return Transaction hash.
*/
@decorators.contractCallErrorHandler
public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise<void> {
public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise<string> {
assert.doesConformToSchema('orderCancellationRequests', orderCancellationRequests,
schemas.orderCancellationRequestsSchema);
const exchangeContractAddresses = _.map(
@@ -532,7 +524,7 @@ export class ExchangeWrapper extends ContractWrapper {
);
}
if (_.isEmpty(orderCancellationRequests)) {
return; // no-op
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
}
const exchangeInstance = await this._getExchangeContractAsync();
const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => {
@@ -544,7 +536,7 @@ export class ExchangeWrapper extends ContractWrapper {
// We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
const [orderAddresses, orderValues, cancelTakerTokenAmounts] =
_.unzip<any>(orderAddressesValuesAndTakerTokenCancelAmounts);
const gas = await exchangeInstance.batchCancelOrders.estimateGas(
const gas = await exchangeInstance.batchCancelOrders.estimateGasAsync(
orderAddresses,
orderValues,
cancelTakerTokenAmounts,
@@ -552,7 +544,7 @@ export class ExchangeWrapper extends ContractWrapper {
from: maker,
},
);
const response: ContractResponse = await exchangeInstance.batchCancelOrders(
const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync(
orderAddresses,
orderValues,
cancelTakerTokenAmounts,
@@ -561,7 +553,7 @@ export class ExchangeWrapper extends ContractWrapper {
gas,
},
);
this._throwErrorLogsAsErrors(response.logs);
return txHash;
}
/**
* Subscribe to an event type emitted by the Exchange smart contract
@@ -686,11 +678,24 @@ export class ExchangeWrapper extends ContractWrapper {
assert.isBigNumber('takerTokenAmount', takerTokenAmount);
assert.isBigNumber('makerTokenAmount', makerTokenAmount);
const exchangeInstance = await this._getExchangeContractAsync();
const isRoundingError = await exchangeInstance.isRoundingError.call(
const isRoundingError = await exchangeInstance.isRoundingError.callAsync(
fillTakerTokenAmount, takerTokenAmount, makerTokenAmount,
);
return isRoundingError;
}
/**
* Checks if logs contain LogError, which is emmited by Exchange contract on transaction failure.
* @param logsWithDecodedArgs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync`
*/
public throwLogErrorsAsErrors(logsWithDecodedArgs: LogWithDecodedArgs[]): void {
const errLog = _.find(logsWithDecodedArgs, {event: ExchangeEvents.LogError});
if (!_.isUndefined(errLog)) {
const logArgs: LogErrorContractEventArgs = errLog.args as any;
const errCode = logArgs.errorId.toNumber();
const errMessage = this._exchangeContractErrCodesToMsg[errCode];
throw new Error(errMessage);
}
}
private async _invalidateContractInstancesAsync(): Promise<void> {
await this.stopWatchingAllEventsAsync();
delete this._exchangeContractIfExists;
@@ -703,7 +708,7 @@ export class ExchangeWrapper extends ContractWrapper {
const exchangeInstance = await this._getExchangeContractAsync();
const isValidSignature = await exchangeInstance.isValidSignature.call(
const isValidSignature = await exchangeInstance.isValidSignature.callAsync(
signerAddressHex,
dataHex,
ecSignature.v,
@@ -715,28 +720,22 @@ export class ExchangeWrapper extends ContractWrapper {
private async _getOrderHashHexUsingContractCallAsync(order: Order|SignedOrder): Promise<string> {
const exchangeInstance = await this._getExchangeContractAsync();
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order);
const orderHashHex = await exchangeInstance.getOrderHash.call(orderAddresses, orderValues);
const orderHashHex = await exchangeInstance.getOrderHash.callAsync(orderAddresses, orderValues);
return orderHashHex;
}
private _throwErrorLogsAsErrors(logs: ContractEvent[]): void {
const errEvent = _.find(logs, {event: 'LogError'});
if (!_.isUndefined(errEvent)) {
const errCode = (errEvent.args as LogErrorContractEventArgs).errorId.toNumber();
const errMessage = this._exchangeContractErrCodesToMsg[errCode];
throw new Error(errMessage);
}
}
private async _getExchangeContractAsync(): Promise<ExchangeContract> {
if (!_.isUndefined(this._exchangeContractIfExists)) {
return this._exchangeContractIfExists;
}
const contractInstance = await this._instantiateContractIfExistsAsync((ExchangeArtifacts as any));
const contractInstance = await this._instantiateContractIfExistsAsync<ExchangeContract>(
artifacts.ExchangeArtifact,
);
this._exchangeContractIfExists = contractInstance as ExchangeContract;
return this._exchangeContractIfExists;
}
private async _getZRXTokenAddressAsync(): Promise<string> {
const exchangeInstance = await this._getExchangeContractAsync();
const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.call();
const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.callAsync();
return ZRXtokenAddress;
}
}

View File

@@ -4,15 +4,15 @@ import {assert} from '../utils/assert';
import {Token, TokenRegistryContract, TokenMetadata} from '../types';
import {constants} from '../utils/constants';
import {ContractWrapper} from './contract_wrapper';
import * as TokenRegistryArtifacts from '../artifacts/TokenRegistry.json';
import {artifacts} from '../artifacts';
/**
* This class includes all the functionality related to interacting with the 0x Token Registry smart contract.
*/
export class TokenRegistryWrapper extends ContractWrapper {
private _tokenRegistryContractIfExists?: TokenRegistryContract;
constructor(web3Wrapper: Web3Wrapper, gasPrice?: BigNumber.BigNumber) {
super(web3Wrapper, gasPrice);
constructor(web3Wrapper: Web3Wrapper) {
super(web3Wrapper);
}
/**
* Retrieves all the tokens currently listed in the Token Registry smart contract
@@ -35,7 +35,7 @@ export class TokenRegistryWrapper extends ContractWrapper {
*/
public async getTokenAddressesAsync(): Promise<string[]> {
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addresses = await tokenRegistryContract.getTokenAddresses.call();
const addresses = await tokenRegistryContract.getTokenAddresses.callAsync();
return addresses;
}
/**
@@ -46,14 +46,14 @@ export class TokenRegistryWrapper extends ContractWrapper {
assert.isETHAddressHex('address', address);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const metadata = await tokenRegistryContract.getTokenMetaData.call(address);
const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address);
const token = this._createTokenFromMetadata(metadata);
return token;
}
public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string|undefined> {
assert.isString('symbol', symbol);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.call(symbol);
const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.callAsync(symbol);
if (addressIfExists === constants.NULL_ADDRESS) {
return undefined;
}
@@ -62,7 +62,7 @@ export class TokenRegistryWrapper extends ContractWrapper {
public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string|undefined> {
assert.isString('name', name);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addressIfExists = await tokenRegistryContract.getTokenAddressByName.call(name);
const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name);
if (addressIfExists === constants.NULL_ADDRESS) {
return undefined;
}
@@ -71,14 +71,14 @@ export class TokenRegistryWrapper extends ContractWrapper {
public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token|undefined> {
assert.isString('symbol', symbol);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const metadata = await tokenRegistryContract.getTokenBySymbol.call(symbol);
const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol);
const token = this._createTokenFromMetadata(metadata);
return token;
}
public async getTokenByNameIfExistsAsync(name: string): Promise<Token|undefined> {
assert.isString('name', name);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const metadata = await tokenRegistryContract.getTokenByName.call(name);
const metadata = await tokenRegistryContract.getTokenByName.callAsync(name);
const token = this._createTokenFromMetadata(metadata);
return token;
}
@@ -101,7 +101,9 @@ export class TokenRegistryWrapper extends ContractWrapper {
if (!_.isUndefined(this._tokenRegistryContractIfExists)) {
return this._tokenRegistryContractIfExists;
}
const contractInstance = await this._instantiateContractIfExistsAsync((TokenRegistryArtifacts as any));
const contractInstance = await this._instantiateContractIfExistsAsync<TokenRegistryContract>(
artifacts.TokenRegistryArtifact,
);
this._tokenRegistryContractIfExists = contractInstance as TokenRegistryContract;
return this._tokenRegistryContractIfExists;
}

View File

@@ -1,6 +1,6 @@
import * as _ from 'lodash';
import {ContractWrapper} from './contract_wrapper';
import * as TokenTransferProxyArtifacts from '../artifacts/TokenTransferProxy.json';
import {artifacts} from '../artifacts';
import {TokenTransferProxyContract} from '../types';
/**
@@ -15,7 +15,7 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
*/
public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> {
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
const isAuthorized = await tokenTransferProxyContractInstance.authorized.call(exchangeContractAddress);
const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(exchangeContractAddress);
return isAuthorized;
}
/**
@@ -24,7 +24,7 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
*/
public async getAuthorizedAddressesAsync(): Promise<string[]> {
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.call();
const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync();
return authorizedAddresses;
}
/**
@@ -44,7 +44,9 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) {
return this._tokenTransferProxyContractIfExists;
}
const contractInstance = await this._instantiateContractIfExistsAsync((TokenTransferProxyArtifacts as any));
const contractInstance = await this._instantiateContractIfExistsAsync<TokenTransferProxyContract>(
artifacts.TokenTransferProxyArtifact,
);
this._tokenTransferProxyContractIfExists = contractInstance as TokenTransferProxyContract;
return this._tokenTransferProxyContractIfExists;
}

View File

@@ -7,8 +7,7 @@ import {utils} from '../utils/utils';
import {eventUtils} from '../utils/event_utils';
import {constants} from '../utils/constants';
import {ContractWrapper} from './contract_wrapper';
import * as TokenArtifacts from '../artifacts/Token.json';
import * as TokenTransferProxyArtifacts from '../artifacts/TokenTransferProxy.json';
import {artifacts} from '../artifacts';
import {
TokenContract,
ZeroExError,
@@ -31,8 +30,8 @@ export class TokenWrapper extends ContractWrapper {
public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
private _tokenContractsByAddress: {[address: string]: TokenContract};
private _tokenLogEventEmitters: ContractEventEmitter[];
constructor(web3Wrapper: Web3Wrapper, gasPrice?: BigNumber.BigNumber) {
super(web3Wrapper, gasPrice);
constructor(web3Wrapper: Web3Wrapper) {
super(web3Wrapper);
this._tokenContractsByAddress = {};
this._tokenLogEventEmitters = [];
}
@@ -47,7 +46,7 @@ export class TokenWrapper extends ContractWrapper {
assert.isETHAddressHex('tokenAddress', tokenAddress);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
let balance = await tokenContract.balanceOf.call(ownerAddress);
let balance = await tokenContract.balanceOf.callAsync(ownerAddress);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
balance = new BigNumber(balance);
return balance;
@@ -60,9 +59,10 @@ export class TokenWrapper extends ContractWrapper {
* for spenderAddress.
* @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
* @param amountInBaseUnits The allowance amount you would like to set.
* @return Transaction hash.
*/
public async setAllowanceAsync(tokenAddress: string, ownerAddress: string, spenderAddress: string,
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
amountInBaseUnits: BigNumber.BigNumber): Promise<string> {
await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
assert.isETHAddressHex('spenderAddress', spenderAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
@@ -74,10 +74,11 @@ export class TokenWrapper extends ContractWrapper {
// TODO: Debug issue in testrpc and submit a PR, then remove this hack
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
const gas = networkIdIfExists === constants.TESTRPC_NETWORK_ID ? ALLOWANCE_TO_ZERO_GAS_AMOUNT : undefined;
await tokenContract.approve(spenderAddress, amountInBaseUnits, {
const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, {
from: ownerAddress,
gas,
});
return txHash;
}
/**
* Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address.
@@ -88,12 +89,14 @@ export class TokenWrapper extends ContractWrapper {
* @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance
* for spenderAddress.
* @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
* @return Transaction hash.
*/
public async setUnlimitedAllowanceAsync(tokenAddress: string, ownerAddress: string,
spenderAddress: string): Promise<void> {
await this.setAllowanceAsync(
spenderAddress: string): Promise<string> {
const txHash = await this.setAllowanceAsync(
tokenAddress, ownerAddress, spenderAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
);
return txHash;
}
/**
* Retrieves the owners allowance in baseUnits set to the spender's address.
@@ -102,12 +105,13 @@ export class TokenWrapper extends ContractWrapper {
* you would like to retrieve.
* @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching.
*/
public async getAllowanceAsync(tokenAddress: string, ownerAddress: string, spenderAddress: string) {
public async getAllowanceAsync(tokenAddress: string, ownerAddress: string,
spenderAddress: string): Promise<BigNumber.BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
let allowanceInBaseUnits = await tokenContract.allowance.call(ownerAddress, spenderAddress);
let allowanceInBaseUnits = await tokenContract.allowance.callAsync(ownerAddress, spenderAddress);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits);
return allowanceInBaseUnits;
@@ -117,7 +121,7 @@ export class TokenWrapper extends ContractWrapper {
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address whose proxy contract allowance we are retrieving.
*/
public async getProxyAllowanceAsync(tokenAddress: string, ownerAddress: string) {
public async getProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<BigNumber.BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
@@ -132,15 +136,17 @@ export class TokenWrapper extends ContractWrapper {
* @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
* for the Proxy contract.
* @param amountInBaseUnits The allowance amount specified in baseUnits.
* @return Transaction hash.
*/
public async setProxyAllowanceAsync(tokenAddress: string, ownerAddress: string,
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
amountInBaseUnits: BigNumber.BigNumber): Promise<string> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isBigNumber('amountInBaseUnits', amountInBaseUnits);
const proxyAddress = await this._getProxyAddressAsync();
await this.setAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits);
const txHash = await this.setAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits);
return txHash;
}
/**
* Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf
@@ -150,9 +156,13 @@ export class TokenWrapper extends ContractWrapper {
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
* for the Proxy contract.
* @return Transaction hash.
*/
public async setUnlimitedProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<void> {
await this.setProxyAllowanceAsync(tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
public async setUnlimitedProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<string> {
const txHash = await this.setProxyAllowanceAsync(
tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
);
return txHash;
}
/**
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
@@ -160,9 +170,10 @@ export class TokenWrapper extends ContractWrapper {
* @param fromAddress The hex encoded user Ethereum address that will send the funds.
* @param toAddress The hex encoded user Ethereum address that will receive the funds.
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
* @return Transaction hash.
*/
public async transferAsync(tokenAddress: string, fromAddress: string, toAddress: string,
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
amountInBaseUnits: BigNumber.BigNumber): Promise<string> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper);
assert.isETHAddressHex('toAddress', toAddress);
@@ -175,9 +186,10 @@ export class TokenWrapper extends ContractWrapper {
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
}
await tokenContract.transfer(toAddress, amountInBaseUnits, {
const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, {
from: fromAddress,
});
return txHash;
}
/**
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
@@ -190,10 +202,11 @@ export class TokenWrapper extends ContractWrapper {
* `fromAddress` must have set an allowance to the `senderAddress`
* before this call.
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
* @return Transaction hash.
*/
public async transferFromAsync(tokenAddress: string, fromAddress: string, toAddress: string,
senderAddress: string, amountInBaseUnits: BigNumber.BigNumber):
Promise<void> {
Promise<string> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isETHAddressHex('fromAddress', fromAddress);
assert.isETHAddressHex('toAddress', toAddress);
@@ -212,9 +225,13 @@ export class TokenWrapper extends ContractWrapper {
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
}
await tokenContract.transferFrom(fromAddress, toAddress, amountInBaseUnits, {
from: senderAddress,
});
const txHash = await tokenContract.transferFrom.sendTransactionAsync(
fromAddress, toAddress, amountInBaseUnits,
{
from: senderAddress,
},
);
return txHash;
}
/**
* Subscribe to an event type emitted by the Token contract.
@@ -267,7 +284,9 @@ export class TokenWrapper extends ContractWrapper {
if (!_.isUndefined(tokenContract)) {
return tokenContract;
}
const contractInstance = await this._instantiateContractIfExistsAsync((TokenArtifacts as any), tokenAddress);
const contractInstance = await this._instantiateContractIfExistsAsync<TokenContract>(
artifacts.TokenArtifact, tokenAddress,
);
tokenContract = contractInstance as TokenContract;
this._tokenContractsByAddress[tokenAddress] = tokenContract;
return tokenContract;
@@ -276,7 +295,7 @@ export class TokenWrapper extends ContractWrapper {
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
const proxyNetworkConfigsIfExists = _.isUndefined(networkIdIfExists) ?
undefined :
(TokenTransferProxyArtifacts as any).networks[networkIdIfExists];
artifacts.TokenTransferProxyArtifact.networks[networkIdIfExists];
if (_.isUndefined(proxyNetworkConfigsIfExists)) {
throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
}

50
src/globals.d.ts vendored
View File

@@ -33,23 +33,11 @@ declare module '*.json' {
/* tslint:enable */
}
// truffle-contract declarations
declare interface ContractInstance {
address: string;
}
declare interface ContractFactory {
setProvider: (providerObj: any) => void;
deployed: () => ContractInstance;
// Both any's are Web3.CallData, but I was unable to import it in this file
defaults: (config: any) => any;
at: (address: string) => ContractInstance;
}
declare interface Artifact {
networks: {[networkId: number]: any};
}
declare function contract(artifacts: Artifact): ContractFactory;
declare module 'truffle-contract' {
export = contract;
abi: any;
networks: {[networkId: number]: {
address: string;
}};
}
// find-version declarations
@@ -75,9 +63,33 @@ declare module 'ethereumjs-abi' {
}
// truffle-hdwallet-provider declarations
declare class HDWalletProvider {
constructor(mnemonic: string, rpcUrl: string);
}
declare module 'truffle-hdwallet-provider' {
import * as Web3 from 'web3';
class HDWalletProvider implements Web3.Provider {
constructor(mnemonic: string, rpcUrl: string);
public sendAsync(
payload: Web3.JSONRPCRequestPayload,
callback: (err: Error, result: Web3.JSONRPCResponsePayload) => void,
): void;
}
export = HDWalletProvider;
}
// abi-decoder declarations
interface DecodedLogArg {
name: string;
value: string|BigNumber.BigNumber;
}
interface DecodedLog {
name: string;
events: DecodedLogArg[];
}
declare module 'abi-decoder' {
import * as Web3 from 'web3';
const addABI: (abi: Web3.AbiDefinition) => void;
const decodeLogs: (logs: Web3.LogEntry[]) => DecodedLog[];
}
declare module 'web3/lib/solidity/coder' {
const decodeParams: (types: string[], data: string) => any[];
}

View File

@@ -30,4 +30,7 @@ export {
ContractEventArgs,
Web3Provider,
ZeroExConfig,
TransactionReceiptWithDecodedLogs,
LogWithDecodedArgs,
DecodedLogArgs,
} from './types';

View File

@@ -14,6 +14,7 @@ export enum ZeroExError {
InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL',
InvalidJump = 'INVALID_JUMP',
OutOfGas = 'OUT_OF_GAS',
NoNetworkId = 'NO_NETWORK_ID',
}
/**
@@ -39,137 +40,154 @@ export interface ContractEventObj {
}
export type CreateContractEvent = (indexFilterValues: IndexedFilterValues,
subscriptionOpts: SubscriptionOpts) => ContractEventObj;
export interface ExchangeContract extends ContractInstance {
export interface ExchangeContract extends Web3.ContractInstance {
isValidSignature: {
call: (signerAddressHex: string, dataHex: string, v: number, r: string, s: string,
txOpts?: TxOpts) => Promise<boolean>;
callAsync: (signerAddressHex: string, dataHex: string, v: number, r: string, s: string,
txOpts?: TxOpts) => Promise<boolean>;
};
LogFill: CreateContractEvent;
LogCancel: CreateContractEvent;
LogError: CreateContractEvent;
ZRX_TOKEN_CONTRACT: {
call: () => Promise<string>;
callAsync: () => Promise<string>;
};
getUnavailableTakerTokenAmount: {
call: (orderHash: string) => Promise<BigNumber.BigNumber>;
callAsync: (orderHash: string) => Promise<BigNumber.BigNumber>;
};
isRoundingError: {
call: (fillTakerAmount: BigNumber.BigNumber, takerTokenAmount: BigNumber.BigNumber,
makerTokenAmount: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<boolean>;
callAsync: (fillTakerAmount: BigNumber.BigNumber, takerTokenAmount: BigNumber.BigNumber,
makerTokenAmount: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<boolean>;
};
fillOrder: {
(orderAddresses: OrderAddresses, orderValues: OrderValues, fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number, r: string, s: string, txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<number>;
};
batchFillOrders: {
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmounts: BigNumber.BigNumber[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
};
fillOrdersUpTo: {
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmount: BigNumber.BigNumber,
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
};
cancelOrder: {
(orderAddresses: OrderAddresses, orderValues: OrderValues, cancelTakerTokenAmount: BigNumber.BigNumber,
txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues,
cancelTakerTokenAmount: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
cancelTakerTokenAmount: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
cancelTakerTokenAmount: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<number>;
};
batchCancelOrders: {
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], cancelTakerTokenAmounts: BigNumber.BigNumber[],
txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
cancelTakerTokenAmounts: BigNumber.BigNumber[],
txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
cancelTakerTokenAmounts: BigNumber.BigNumber[], txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
cancelTakerTokenAmounts: BigNumber.BigNumber[],
txOpts?: TxOpts) => Promise<number>;
};
fillOrKillOrder: {
(orderAddresses: OrderAddresses, orderValues: OrderValues, fillTakerTokenAmount: BigNumber.BigNumber,
v: number, r: string, s: string, txOpts?: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
fillTakerTokenAmount: BigNumber.BigNumber,
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<number>;
};
batchFillOrKillOrders: {
(orderAddresses: OrderAddresses[], orderValues: OrderValues[], fillTakerTokenAmounts: BigNumber.BigNumber[],
v: number[], r: string[], s: string[], txOpts: TxOpts): Promise<ContractResponse>;
estimateGas: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
v: number[], r: string[], s: string[], txOpts: TxOpts) => Promise<string>;
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
fillTakerTokenAmounts: BigNumber.BigNumber[],
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
};
filled: {
call: (orderHash: string) => Promise<BigNumber.BigNumber>;
callAsync: (orderHash: string) => Promise<BigNumber.BigNumber>;
};
cancelled: {
call: (orderHash: string) => Promise<BigNumber.BigNumber>;
callAsync: (orderHash: string) => Promise<BigNumber.BigNumber>;
};
getOrderHash: {
call: (orderAddresses: OrderAddresses, orderValues: OrderValues) => Promise<string>;
callAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues) => Promise<string>;
};
}
export interface TokenContract extends ContractInstance {
export interface TokenContract extends Web3.ContractInstance {
Transfer: CreateContractEvent;
Approval: CreateContractEvent;
balanceOf: {
call: (address: string) => Promise<BigNumber.BigNumber>;
callAsync: (address: string) => Promise<BigNumber.BigNumber>;
};
allowance: {
call: (ownerAddress: string, allowedAddress: string) => Promise<BigNumber.BigNumber>;
callAsync: (ownerAddress: string, allowedAddress: string) => Promise<BigNumber.BigNumber>;
};
transfer: {
sendTransactionAsync: (toAddress: string, amountInBaseUnits: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<string>;
};
transferFrom: {
sendTransactionAsync: (fromAddress: string, toAddress: string, amountInBaseUnits: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<string>;
};
approve: {
sendTransactionAsync: (proxyAddress: string, amountInBaseUnits: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<string>;
};
transfer: (toAddress: string, amountInBaseUnits: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<boolean>;
transferFrom: (fromAddress: string, toAddress: string, amountInBaseUnits: BigNumber.BigNumber,
txOpts?: TxOpts) => Promise<boolean>;
approve: (proxyAddress: string, amountInBaseUnits: BigNumber.BigNumber, txOpts?: TxOpts) => Promise<void>;
}
export interface TokenRegistryContract extends ContractInstance {
export interface TokenRegistryContract extends Web3.ContractInstance {
getTokenMetaData: {
call: (address: string) => Promise<TokenMetadata>;
callAsync: (address: string) => Promise<TokenMetadata>;
};
getTokenAddresses: {
call: () => Promise<string[]>;
callAsync: () => Promise<string[]>;
};
getTokenAddressBySymbol: {
call: (symbol: string) => Promise<string>;
callAsync: (symbol: string) => Promise<string>;
};
getTokenAddressByName: {
call: (name: string) => Promise<string>;
callAsync: (name: string) => Promise<string>;
};
getTokenBySymbol: {
call: (symbol: string) => Promise<TokenMetadata>;
callAsync: (symbol: string) => Promise<TokenMetadata>;
};
getTokenByName: {
call: (name: string) => Promise<TokenMetadata>;
callAsync: (name: string) => Promise<TokenMetadata>;
};
}
export interface EtherTokenContract extends ContractInstance {
deposit: (txOpts: TxOpts) => Promise<void>;
withdraw: (amount: BigNumber.BigNumber, txOpts: TxOpts) => Promise<void>;
export interface EtherTokenContract extends Web3.ContractInstance {
deposit: {
sendTransactionAsync: (txOpts: TxOpts) => Promise<string>;
};
withdraw: {
sendTransactionAsync: (amount: BigNumber.BigNumber, txOpts: TxOpts) => Promise<string>;
};
}
export interface TokenTransferProxyContract extends ContractInstance {
export interface TokenTransferProxyContract extends Web3.ContractInstance {
getAuthorizedAddresses: {
call: () => Promise<string[]>;
callAsync: () => Promise<string[]>;
};
authorized: {
call: (address: string) => Promise<boolean>;
callAsync: (address: string) => Promise<boolean>;
};
}
@@ -209,10 +227,7 @@ export enum ExchangeContractErrs {
InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT',
MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED',
BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS',
}
export interface ContractResponse {
logs: ContractEvent[];
BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM',
}
export interface ContractEvent {
@@ -349,14 +364,6 @@ export interface OrderFillRequest {
export type AsyncMethod = (...args: any[]) => Promise<any>;
export interface ContractInstance {
address: string;
}
export interface Artifact {
networks: {[networkId: number]: any};
}
export interface ContractEventEmitter {
watch: (eventCallback: EventCallback) => void;
stopWatchingAsync: () => Promise<void>;
@@ -373,14 +380,6 @@ export interface ExchangeContractByAddress {
[address: string]: ExchangeContract;
}
export interface ContractArtifact {
networks: {
[networkId: number]: {
address: string;
};
};
}
export interface JSONRPCPayload {
params: any[];
method: string;
@@ -389,3 +388,27 @@ export interface JSONRPCPayload {
export interface ZeroExConfig {
gasPrice?: BigNumber.BigNumber; // Gas price to use with every transaction
}
export type TransactionReceipt = Web3.TransactionReceipt;
export enum AbiType {
Function = 'function',
Constructor = 'constructor',
Event = 'event',
Fallback = 'fallback',
}
export interface DecodedLogArgs {
[argName: string]: ContractEventArg;
}
export interface DecodedArgs {
args: DecodedLogArgs;
event: string;
}
export interface LogWithDecodedArgs extends Web3.LogEntry, DecodedArgs {}
export interface TransactionReceiptWithDecodedLogs extends Web3.TransactionReceipt {
logs: Array<LogWithDecodedArgs|Web3.LogEntry>;
}

67
src/utils/abi_decoder.ts Normal file
View File

@@ -0,0 +1,67 @@
import * as Web3 from 'web3';
import * as _ from 'lodash';
import * as BigNumber from 'bignumber.js';
import {AbiType, DecodedLogArgs, DecodedArgs} from '../types';
import * as SolidityCoder from 'web3/lib/solidity/coder';
export class AbiDecoder {
private savedABIs: Web3.AbiDefinition[] = [];
private methodIds: {[signatureHash: string]: Web3.EventAbi} = {};
constructor(abiArrays: Web3.AbiDefinition[][]) {
_.map(abiArrays, this.addABI.bind(this));
}
public decodeLog(logItem: Web3.LogEntry): DecodedArgs|undefined {
const methodId = logItem.topics[0];
const event = this.methodIds[methodId];
if (!_.isUndefined(event)) {
const logData = logItem.data;
const decodedParams: DecodedLogArgs = {};
let dataIndex = 0;
let topicsIndex = 1;
const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed);
const dataTypes = _.map(nonIndexedInputs, input => input.type);
const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice(2));
_.map(event.inputs, (param: Web3.EventParameter) => {
let value;
if (param.indexed) {
value = logItem.topics[topicsIndex];
topicsIndex++;
} else {
value = decodedData[dataIndex];
dataIndex++;
}
if (param.type === 'address') {
value = this.padZeros(new BigNumber(value).toString(16));
} else if (param.type === 'uint256' || param.type === 'uint8' || param.type === 'int' ) {
value = new BigNumber(value);
}
decodedParams[param.name] = value;
});
return {
event: event.name,
args: decodedParams,
};
}
}
private addABI(abiArray: Web3.AbiDefinition[]): void {
_.map(abiArray, (abi: Web3.AbiDefinition) => {
if (abi.type === AbiType.Event) {
const signature = `${abi.name}(${_.map(abi.inputs, input => input.type).join(',')})`;
const signatureHash = new Web3().sha3(signature);
this.methodIds[signatureHash] = abi;
}
});
this.savedABIs = this.savedABIs.concat(abiArray);
}
private padZeros(address: string) {
let formatted = address;
if (_.startsWith(formatted, '0x')) {
formatted = formatted.slice(2);
}
formatted = _.padStart(formatted, 40, '0');
return `0x${formatted}`;
}
}

View File

@@ -0,0 +1,20 @@
import * as _ from 'lodash';
export const intervalUtils = {
setAsyncExcludingInterval(fn: () => Promise<void>, intervalMs: number) {
let locked = false;
const intervalId = setInterval(async () => {
if (locked) {
return;
} else {
locked = true;
await fn();
locked = false;
}
});
return intervalId;
},
clearAsyncExcludingInterval(intervalId: number): void {
clearInterval(intervalId);
},
};

View File

@@ -1,4 +1,5 @@
import {ExchangeContractErrs, SignedOrder, Order} from '../types';
import {ExchangeContractErrs, SignedOrder, Order, ZeroExError} from '../types';
import {ZeroEx} from '../0x';
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
import {utils} from '../utils/utils';
@@ -19,6 +20,9 @@ export class OrderValidationUtils {
throw new Error(ExchangeContractErrs.OrderFillAmountZero);
}
const orderHash = utils.getOrderHashHex(signedOrder);
if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) {
throw new Error(ZeroExError.InvalidSignature);
}
const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
if (signedOrder.makerTokenAmount.eq(unavailableTakerTokenAmount)) {
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);

View File

@@ -2,13 +2,17 @@ import * as _ from 'lodash';
import * as Web3 from 'web3';
import * as BigNumber from 'bignumber.js';
import promisify = require('es6-promisify');
import {ZeroExError} from './types';
import {Contract} from './contract';
export class Web3Wrapper {
private web3: Web3;
private defaults: Partial<Web3.TxData>;
private networkIdIfExists?: number;
constructor(provider: Web3.Provider) {
constructor(provider: Web3.Provider, defaults: Partial<Web3.TxData>) {
this.web3 = new Web3();
this.web3.setProvider(provider);
this.defaults = defaults;
}
public setProvider(provider: Web3.Provider) {
delete this.networkIdIfExists;
@@ -25,6 +29,10 @@ export class Web3Wrapper {
const nodeVersion = await promisify(this.web3.version.getNode)();
return nodeVersion;
}
public async getTransactionReceiptAsync(txHash: string): Promise<Web3.TransactionReceipt> {
const transactionReceipt = await promisify(this.web3.eth.getTransactionReceipt)(txHash);
return transactionReceipt;
}
public getCurrentProvider(): Web3.Provider {
return this.web3.currentProvider;
}
@@ -41,6 +49,30 @@ export class Web3Wrapper {
return undefined;
}
}
public async getContractInstanceFromArtifactAsync<A extends Web3.ContractInstance>(artifact: Artifact,
address?: string): Promise<A> {
let contractAddress: string;
if (_.isUndefined(address)) {
const networkIdIfExists = await this.getNetworkIdIfExistsAsync();
if (_.isUndefined(networkIdIfExists)) {
throw new Error(ZeroExError.NoNetworkId);
}
if (_.isUndefined(artifact.networks[networkIdIfExists])) {
throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
}
contractAddress = artifact.networks[networkIdIfExists].address.toLowerCase();
} else {
contractAddress = address;
}
const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress);
if (!doesContractExist) {
throw new Error(ZeroExError.ContractDoesNotExist);
}
const contractInstance = this.getContractInstance<A>(
artifact.abi, contractAddress,
);
return contractInstance;
}
public toWei(ethAmount: BigNumber.BigNumber): BigNumber.BigNumber {
const balanceWei = this.web3.toWei(ethAmount, 'ether');
return balanceWei;
@@ -68,6 +100,11 @@ export class Web3Wrapper {
const addresses: string[] = await promisify(this.web3.eth.getAccounts)();
return addresses;
}
private getContractInstance<A extends Web3.ContractInstance>(abi: Web3.ContractAbi, address: string): A {
const web3ContractInstance = this.web3.eth.contract(abi).at(address);
const contractInstance = new Contract(web3ContractInstance, this.defaults) as any as A;
return contractInstance;
}
private async getNetworkAsync(): Promise<number> {
const networkId = await promisify(this.web3.version.getNetwork)();
return networkId;

View File

@@ -6,8 +6,12 @@ import * as BigNumber from 'bignumber.js';
import * as Sinon from 'sinon';
import {ZeroEx, Order} from '../src';
import {constants} from './utils/constants';
import {TokenUtils} from './utils/token_utils';
import {web3Factory} from './utils/web3_factory';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
import {LogWithDecodedArgs} from '../src';
const blockchainLifecycle = new BlockchainLifecycle();
chaiSetup.configure();
const expect = chai.expect;
@@ -204,4 +208,27 @@ describe('ZeroEx library', () => {
expect(ecSignature).to.deep.equal(expectedECSignature);
});
});
describe('#awaitTransactionMinedAsync', () => {
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
it('returns transaction receipt with decoded logs', async () => {
const availableAddresses = await zeroEx.getAvailableAddressesAsync();
const coinbase = availableAddresses[0];
const tokens = await zeroEx.tokenRegistry.getTokensAsync();
const tokenUtils = new TokenUtils(tokens);
const zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
const proxyAddress = await zeroEx.proxy.getContractAddressAsync();
const txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(zrxTokenAddress, coinbase);
const txReceiptWithDecodedLogs = await zeroEx.awaitTransactionMinedAsync(txHash);
const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs;
expect(log.event).to.be.equal('Approval');
expect(log.args._owner).to.be.equal(coinbase);
expect(log.args._spender).to.be.equal(proxyAddress);
expect(log.args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
});
});

View File

@@ -52,7 +52,8 @@ describe('EtherTokenWrapper', () => {
expect(preETHBalance).to.be.bignumber.gt(0);
expect(preWETHBalance).to.be.bignumber.equal(0);
await zeroEx.etherToken.depositAsync(depositWeiAmount, addressWithETH);
const txHash = await zeroEx.etherToken.depositAsync(depositWeiAmount, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
@@ -86,7 +87,8 @@ describe('EtherTokenWrapper', () => {
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount);
await zeroEx.etherToken.withdrawAsync(depositWeiAmount, addressWithETH);
const txHash = await zeroEx.etherToken.withdrawAsync(depositWeiAmount, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);

View File

@@ -169,8 +169,9 @@ describe('ExchangeWrapper', () => {
.to.be.bignumber.equal(0);
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
.to.be.bignumber.equal(fillableAmount);
await zeroEx.exchange.fillOrderAsync(
const txHash = await zeroEx.exchange.fillOrderAsync(
signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
await zeroEx.awaitTransactionMinedAsync(txHash);
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
@@ -185,8 +186,9 @@ describe('ExchangeWrapper', () => {
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
const partialFillAmount = new BigNumber(3);
await zeroEx.exchange.fillOrderAsync(
const txHash = await zeroEx.exchange.fillOrderAsync(
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
await zeroEx.awaitTransactionMinedAsync(txHash);
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
@@ -196,34 +198,6 @@ describe('ExchangeWrapper', () => {
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
});
it('should return filled amount', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
const partialFillAmount = new BigNumber(3);
const filledAmount = await zeroEx.exchange.fillOrderAsync(
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
expect(filledAmount).to.be.bignumber.equal(partialFillAmount);
});
it('should return the partially filled amount \
if the fill amount specified is greater then the amount available', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
const partialFillAmount = new BigNumber(3);
await zeroEx.exchange.fillOrderAsync(
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
const missingBalance = new BigNumber(1);
const totalBalance = partialFillAmount.plus(missingBalance);
await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, missingBalance);
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, totalBalance);
await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, missingBalance);
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, totalBalance);
const remainingFillAmount = fillableAmount.minus(partialFillAmount);
const filledAmount = await zeroEx.exchange.fillOrderAsync(
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
expect(filledAmount).to.be.bignumber.equal(remainingFillAmount);
});
it('should fill the valid orders with fees', async () => {
const makerFee = new BigNumber(1);
const takerFee = new BigNumber(2);
@@ -231,8 +205,9 @@ describe('ExchangeWrapper', () => {
makerTokenAddress, takerTokenAddress, makerFee, takerFee,
makerAddress, takerAddress, fillableAmount, feeRecipient,
);
await zeroEx.exchange.fillOrderAsync(
const txHash = await zeroEx.exchange.fillOrderAsync(
signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
await zeroEx.awaitTransactionMinedAsync(txHash);
expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient))
.to.be.bignumber.equal(makerFee.plus(takerFee));
});
@@ -265,13 +240,15 @@ describe('ExchangeWrapper', () => {
];
});
describe('successful batch fills', () => {
it('should no-op for an empty batch', async () => {
await zeroEx.exchange.batchFillOrdersAsync(
[], shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
it('should throw if a batch is empty', async () => {
return expect(zeroEx.exchange.batchFillOrdersAsync(
[], shouldThrowOnInsufficientBalanceOrAllowance, takerAddress),
).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
});
it('should successfully fill multiple orders', async () => {
await zeroEx.exchange.batchFillOrdersAsync(
const txHash = await zeroEx.exchange.batchFillOrdersAsync(
orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
await zeroEx.awaitTransactionMinedAsync(txHash);
const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
expect(filledAmount).to.be.bignumber.equal(fillTakerAmount);
@@ -298,26 +275,22 @@ describe('ExchangeWrapper', () => {
signedOrders = [signedOrder, anotherSignedOrder];
});
describe('successful batch fills', () => {
it('should no-op for an empty batch', async () => {
await zeroEx.exchange.fillOrdersUpToAsync(
[], fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
it('should throw if a batch is empty', async () => {
return expect(zeroEx.exchange.fillOrdersUpToAsync(
[], fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress),
).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
});
it('should successfully fill up to specified amount', async () => {
await zeroEx.exchange.fillOrdersUpToAsync(
const txHash = await zeroEx.exchange.fillOrdersUpToAsync(
signedOrders, fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
);
await zeroEx.awaitTransactionMinedAsync(txHash);
const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
expect(filledAmount).to.be.bignumber.equal(fillableAmount);
const remainingFillAmount = fillableAmount.minus(1);
expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount);
});
it('should return filled amount', async () => {
const filledTakerTokenAmount = await zeroEx.exchange.fillOrdersUpToAsync(
signedOrders, fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
);
expect(filledTakerTokenAmount).to.be.bignumber.equal(fillUpToAmount);
});
});
});
});
@@ -344,14 +317,11 @@ describe('ExchangeWrapper', () => {
describe('#cancelOrderAsync', () => {
describe('successful cancels', () => {
it('should cancel an order', async () => {
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
const txHash = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
const cancelledAmount = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHashHex);
expect(cancelledAmount).to.be.bignumber.equal(cancelAmount);
});
it('should return cancelled amount', async () => {
const cancelledAmount = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
expect(cancelledAmount).to.be.bignumber.equal(cancelAmount);
});
});
});
describe('#batchCancelOrdersAsync', () => {

View File

@@ -4,7 +4,7 @@ import * as BigNumber from 'bignumber.js';
import promisify = require('es6-promisify');
import {chaiSetup} from './utils/chai_setup';
import {web3Factory} from './utils/web3_factory';
import {ZeroEx, SignedOrder, Token, ExchangeContractErrs} from '../src';
import {ZeroEx, SignedOrder, Token, ExchangeContractErrs, ZeroExError} from '../src';
import {TokenUtils} from './utils/token_utils';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
import {FillScenarios} from './utils/fill_scenarios';
@@ -64,6 +64,16 @@ describe('OrderValidation', () => {
signedOrder, zeroFillAmount, takerAddress,
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
});
it('should throw when the signature is invalid', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
);
// 27 <--> 28
signedOrder.ecSignature.v = 27 + (28 - signedOrder.ecSignature.v);
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
signedOrder, fillableAmount, takerAddress,
)).to.be.rejectedWith(ZeroExError.InvalidSignature);
});
it('should throw when the order is fully filled or cancelled', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,

View File

@@ -58,7 +58,8 @@ describe('TokenWrapper', () => {
const toAddress = addressWithoutFunds;
const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
expect(preBalance).to.be.bignumber.equal(0);
await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount);
const txHash = await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
return expect(postBalance).to.be.bignumber.equal(transferAmount);
});
@@ -356,7 +357,7 @@ describe('TokenWrapper', () => {
// we do need both. A hack is to make the top-level a sync fn w/ a done callback and then
// wrap the rest of the test in an async block
// Source: https://github.com/mochajs/mocha/issues/2407
it('Should receive the Transfer event when an order is filled', (done: DoneCallback) => {
it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => {
(async () => {
const zeroExEvent = await zeroEx.token.subscribeAsync(
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
@@ -372,7 +373,7 @@ describe('TokenWrapper', () => {
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
})().catch(done);
});
it('Should receive the Approval event when an order is cancelled', (done: DoneCallback) => {
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
(async () => {
const zeroExEvent = await zeroEx.token.subscribeAsync(
tokenAddress, TokenEvents.Approval, subscriptionOpts, indexFilterValues);

View File

@@ -6,7 +6,6 @@
import ProviderEngine = require('web3-provider-engine');
import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
import * as Web3 from 'web3';
import * as Web3_beta from 'web3_beta';
import {constants} from './constants';
import {EmptyWalletSubProvider} from '../../src/subproviders/empty_wallet_subprovider';
@@ -29,9 +28,4 @@ export const web3Factory = {
provider.start();
return provider;
},
getProviderBeta(): Web3.Provider {
const rpcUrl = `http://${constants.RPC_HOST}:${constants.RPC_PORT}`;
const providerBeta = new Web3_beta.providers.HttpProvider(rpcUrl);
return providerBeta;
},
};

View File

@@ -1,16 +0,0 @@
import * as chai from 'chai';
import {chaiSetup} from './utils/chai_setup';
import 'mocha';
import {ZeroEx, Order, SubscriptionOpts, TokenEvents, ContractEvent} from '../src';
import {web3Factory} from './utils/web3_factory';
chaiSetup.configure();
const expect = chai.expect;
describe('ZeroEx with beta web3', () => {
const web3_beta_provider = web3Factory.getProviderBeta();
const zeroEx = new ZeroEx(web3_beta_provider);
it('is able to make a call using a beta provider', async () => {
await zeroEx.tokenRegistry.getTokenAddressesAsync();
});
});

808
yarn.lock

File diff suppressed because it is too large Load Diff