Add config file specifically in prettier command and fix files

This commit is contained in:
Fabio Berger
2018-01-30 13:26:46 +01:00
parent 93a5b3f457
commit c6dece6bd1
379 changed files with 33806 additions and 33806 deletions

View File

@@ -1,94 +1,94 @@
{
"name": "0x.js",
"version": "0.30.2",
"description": "A javascript library for interacting with the 0x protocol",
"keywords": ["0x.js", "0xproject", "ethereum", "tokens", "exchange"],
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
"prebuild": "run-s clean generate_contract_wrappers",
"build": "run-p build:umd:prod build:commonjs; exit 0;",
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR",
"upload_docs_json":
"aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json",
"generate_contract_wrappers":
"node ../abi-gen/lib/index.js --abiGlob 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --templates contract_templates --output src/contract_wrappers/generated",
"lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'",
"test:circleci": "run-s test:coverage report_test_coverage",
"test": "run-s clean test:commonjs",
"test:coverage": "nyc npm run test --all",
"report_test_coverage": "nyc report --reporter=text-lcov | coveralls",
"update_contracts":
"for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../0x.js/src/artifacts; done;",
"clean": "shx rm -rf _bundles lib test_temp",
"build:umd:prod": "NODE_ENV=production webpack",
"build:commonjs": "tsc && copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts;",
"test:commonjs": "run-s build:commonjs run_mocha",
"run_mocha": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit"
},
"config": {
"artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken"
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/0x.js"
},
"license": "Apache-2.0",
"engines": {
"node": ">=6.0.0"
},
"devDependencies": {
"@0xproject/abi-gen": "^0.1.4",
"@0xproject/dev-utils": "^0.0.7",
"@0xproject/tslint-config": "^0.4.4",
"@types/bintrees": "^1.0.2",
"@types/jsonschema": "^1.1.1",
"@types/lodash": "^4.14.86",
"@types/mocha": "^2.2.42",
"@types/node": "^8.0.53",
"@types/sinon": "^2.2.2",
"@types/uuid": "^3.4.2",
"awesome-typescript-loader": "^3.1.3",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-as-promised-typescript-typings": "^0.0.6",
"chai-bignumber": "^2.0.1",
"chai-typescript-typings": "^0.0.2",
"copyfiles": "^1.2.0",
"coveralls": "^3.0.0",
"dirty-chai": "^2.0.1",
"json-loader": "^0.5.4",
"mocha": "^4.0.1",
"npm-run-all": "^4.1.2",
"nyc": "^11.0.1",
"opn-cli": "^3.1.0",
"request": "^2.81.0",
"request-promise-native": "^1.0.4",
"shx": "^0.2.2",
"sinon": "^4.0.0",
"source-map-support": "^0.5.0",
"truffle-hdwallet-provider": "^0.0.3",
"tslint": "5.8.0",
"typedoc": "~0.8.0",
"typescript": "~2.6.1",
"web3-provider-engine": "^13.0.1",
"web3-typescript-typings": "^0.9.6",
"webpack": "^3.1.0"
},
"dependencies": {
"@0xproject/assert": "^0.0.13",
"@0xproject/json-schemas": "^0.7.5",
"@0xproject/types": "^0.1.6",
"@0xproject/utils": "^0.2.2",
"@0xproject/web3-wrapper": "^0.1.7",
"bintrees": "^1.0.2",
"bn.js": "^4.11.8",
"ethereumjs-abi": "^0.6.4",
"ethereumjs-blockstream": "^2.0.6",
"ethereumjs-util": "^5.1.1",
"js-sha3": "^0.6.1",
"lodash": "^4.17.4",
"uuid": "^3.1.0",
"web3": "^0.20.0"
}
"name": "0x.js",
"version": "0.30.2",
"description": "A javascript library for interacting with the 0x protocol",
"keywords": ["0x.js", "0xproject", "ethereum", "tokens", "exchange"],
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
"prebuild": "run-s clean generate_contract_wrappers",
"build": "run-p build:umd:prod build:commonjs; exit 0;",
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR",
"upload_docs_json":
"aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json",
"generate_contract_wrappers":
"node ../abi-gen/lib/index.js --abiGlob 'src/artifacts/@(Exchange|Token|TokenTransferProxy|EtherToken|TokenRegistry|DummyToken).json' --templates contract_templates --output src/contract_wrappers/generated",
"lint": "tslint --project . 'src/**/*.ts' 'test/**/*.ts'",
"test:circleci": "run-s test:coverage report_test_coverage",
"test": "run-s clean test:commonjs",
"test:coverage": "nyc npm run test --all",
"report_test_coverage": "nyc report --reporter=text-lcov | coveralls",
"update_contracts":
"for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../0x.js/src/artifacts; done;",
"clean": "shx rm -rf _bundles lib test_temp",
"build:umd:prod": "NODE_ENV=production webpack",
"build:commonjs": "tsc && copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts;",
"test:commonjs": "run-s build:commonjs run_mocha",
"run_mocha": "mocha lib/test/**/*_test.js --timeout 10000 --bail --exit"
},
"config": {
"artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken"
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/0x.js"
},
"license": "Apache-2.0",
"engines": {
"node": ">=6.0.0"
},
"devDependencies": {
"@0xproject/abi-gen": "^0.1.4",
"@0xproject/dev-utils": "^0.0.7",
"@0xproject/tslint-config": "^0.4.4",
"@types/bintrees": "^1.0.2",
"@types/jsonschema": "^1.1.1",
"@types/lodash": "^4.14.86",
"@types/mocha": "^2.2.42",
"@types/node": "^8.0.53",
"@types/sinon": "^2.2.2",
"@types/uuid": "^3.4.2",
"awesome-typescript-loader": "^3.1.3",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-as-promised-typescript-typings": "^0.0.6",
"chai-bignumber": "^2.0.1",
"chai-typescript-typings": "^0.0.2",
"copyfiles": "^1.2.0",
"coveralls": "^3.0.0",
"dirty-chai": "^2.0.1",
"json-loader": "^0.5.4",
"mocha": "^4.0.1",
"npm-run-all": "^4.1.2",
"nyc": "^11.0.1",
"opn-cli": "^3.1.0",
"request": "^2.81.0",
"request-promise-native": "^1.0.4",
"shx": "^0.2.2",
"sinon": "^4.0.0",
"source-map-support": "^0.5.0",
"truffle-hdwallet-provider": "^0.0.3",
"tslint": "5.8.0",
"typedoc": "~0.8.0",
"typescript": "~2.6.1",
"web3-provider-engine": "^13.0.1",
"web3-typescript-typings": "^0.9.6",
"webpack": "^3.1.0"
},
"dependencies": {
"@0xproject/assert": "^0.0.13",
"@0xproject/json-schemas": "^0.7.5",
"@0xproject/types": "^0.1.6",
"@0xproject/utils": "^0.2.2",
"@0xproject/web3-wrapper": "^0.1.7",
"bintrees": "^1.0.2",
"bn.js": "^4.11.8",
"ethereumjs-abi": "^0.6.4",
"ethereumjs-blockstream": "^2.0.6",
"ethereumjs-util": "^5.1.1",
"js-sha3": "^0.6.1",
"lodash": "^4.17.4",
"uuid": "^3.1.0",
"web3": "^0.20.0"
}
}

View File

@@ -13,13 +13,13 @@ import { TokenWrapper } from './contract_wrappers/token_wrapper';
import { OrderStateWatcher } from './order_watcher/order_state_watcher';
import { zeroExConfigSchema } from './schemas/zero_ex_config_schema';
import {
ECSignature,
Order,
SignedOrder,
TransactionReceiptWithDecodedLogs,
Web3Provider,
ZeroExConfig,
ZeroExError,
ECSignature,
Order,
SignedOrder,
TransactionReceiptWithDecodedLogs,
Web3Provider,
ZeroExConfig,
ZeroExError,
} from './types';
import { AbiDecoder } from './utils/abi_decoder';
import { assert } from './utils/assert';
@@ -33,318 +33,318 @@ import { utils } from './utils/utils';
* and all calls to the library should be made through a ZeroEx instance.
*/
export class ZeroEx {
/**
* When creating an order without a specified taker or feeRecipient you must supply the Solidity
* address null type (as opposed to Javascripts `null`, `undefined` or empty string). We expose
* this constant for your convenience.
*/
public static NULL_ADDRESS = constants.NULL_ADDRESS;
/**
* When creating an order without a specified taker or feeRecipient you must supply the Solidity
* address null type (as opposed to Javascripts `null`, `undefined` or empty string). We expose
* this constant for your convenience.
*/
public static NULL_ADDRESS = constants.NULL_ADDRESS;
/**
* An instance of the ExchangeWrapper class containing methods for interacting with the 0x Exchange smart contract.
*/
public exchange: ExchangeWrapper;
/**
* An instance of the TokenRegistryWrapper class containing methods for interacting with the 0x
* TokenRegistry smart contract.
*/
public tokenRegistry: TokenRegistryWrapper;
/**
* An instance of the TokenWrapper class containing methods for interacting with any ERC20 token smart contract.
*/
public token: TokenWrapper;
/**
* An instance of the EtherTokenWrapper class containing methods for interacting with the
* wrapped ETH ERC20 token smart contract.
*/
public etherToken: EtherTokenWrapper;
/**
* An instance of the TokenTransferProxyWrapper class containing methods for interacting with the
* tokenTransferProxy smart contract.
*/
public proxy: TokenTransferProxyWrapper;
/**
* An instance of the OrderStateWatcher class containing methods for watching a set of orders for relevant
* blockchain state changes.
*/
public orderStateWatcher: OrderStateWatcher;
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.
* @param data The hex encoded data signed by the supplied signature.
* @param signature An object containing the elliptic curve signature parameters.
* @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
* @return Whether the signature is valid for the supplied signerAddress and data.
*/
public static isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
assert.isHexString('data', data);
assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
assert.isETHAddressHex('signerAddress', signerAddress);
/**
* An instance of the ExchangeWrapper class containing methods for interacting with the 0x Exchange smart contract.
*/
public exchange: ExchangeWrapper;
/**
* An instance of the TokenRegistryWrapper class containing methods for interacting with the 0x
* TokenRegistry smart contract.
*/
public tokenRegistry: TokenRegistryWrapper;
/**
* An instance of the TokenWrapper class containing methods for interacting with any ERC20 token smart contract.
*/
public token: TokenWrapper;
/**
* An instance of the EtherTokenWrapper class containing methods for interacting with the
* wrapped ETH ERC20 token smart contract.
*/
public etherToken: EtherTokenWrapper;
/**
* An instance of the TokenTransferProxyWrapper class containing methods for interacting with the
* tokenTransferProxy smart contract.
*/
public proxy: TokenTransferProxyWrapper;
/**
* An instance of the OrderStateWatcher class containing methods for watching a set of orders for relevant
* blockchain state changes.
*/
public orderStateWatcher: OrderStateWatcher;
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.
* @param data The hex encoded data signed by the supplied signature.
* @param signature An object containing the elliptic curve signature parameters.
* @param signerAddress The hex encoded address that signed the data, producing the supplied signature.
* @return Whether the signature is valid for the supplied signerAddress and data.
*/
public static isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
assert.isHexString('data', data);
assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
assert.isETHAddressHex('signerAddress', signerAddress);
const isValidSignature = signatureUtils.isValidSignature(data, signature, signerAddress);
return isValidSignature;
}
/**
* Generates a pseudo-random 256-bit salt.
* The salt can be included in an 0x order, ensuring that the order generates a unique orderHash
* and will not collide with other outstanding orders that are identical in all other parameters.
* @return A pseudo-random 256-bit number that can be used as a salt.
*/
public static generatePseudoRandomSalt(): BigNumber {
// BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places.
// Source: https://mikemcl.github.io/bignumber.js/#random
const randomNumber = BigNumber.random(constants.MAX_DIGITS_IN_UNSIGNED_256_INT);
const factor = new BigNumber(10).pow(constants.MAX_DIGITS_IN_UNSIGNED_256_INT - 1);
const salt = randomNumber.times(factor).round();
return salt;
}
/**
* Checks if the supplied hex encoded order hash is valid.
* Note: Valid means it has the expected format, not that an order with the orderHash exists.
* Use this method when processing orderHashes submitted as user input.
* @param orderHash Hex encoded orderHash.
* @return Whether the supplied orderHash has the expected format.
*/
public static isValidOrderHash(orderHash: string): boolean {
// Since this method can be called to check if any arbitrary string conforms to an orderHash's
// format, we only assert that we were indeed passed a string.
assert.isString('orderHash', orderHash);
const schemaValidator = new SchemaValidator();
const isValidOrderHash = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid;
return isValidOrderHash;
}
/**
* A unit amount is defined as the amount of a token above the specified decimal places (integer part).
* E.g: If a currency has 18 decimal places, 1e18 or one quintillion of the currency is equivalent
* to 1 unit.
* @param amount The amount in baseUnits that you would like converted to units.
* @param decimals The number of decimal places the unit amount has.
* @return The amount in units.
*/
public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber {
assert.isValidBaseUnitAmount('amount', amount);
assert.isNumber('decimals', decimals);
const isValidSignature = signatureUtils.isValidSignature(data, signature, signerAddress);
return isValidSignature;
}
/**
* Generates a pseudo-random 256-bit salt.
* The salt can be included in an 0x order, ensuring that the order generates a unique orderHash
* and will not collide with other outstanding orders that are identical in all other parameters.
* @return A pseudo-random 256-bit number that can be used as a salt.
*/
public static generatePseudoRandomSalt(): BigNumber {
// BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places.
// Source: https://mikemcl.github.io/bignumber.js/#random
const randomNumber = BigNumber.random(constants.MAX_DIGITS_IN_UNSIGNED_256_INT);
const factor = new BigNumber(10).pow(constants.MAX_DIGITS_IN_UNSIGNED_256_INT - 1);
const salt = randomNumber.times(factor).round();
return salt;
}
/**
* Checks if the supplied hex encoded order hash is valid.
* Note: Valid means it has the expected format, not that an order with the orderHash exists.
* Use this method when processing orderHashes submitted as user input.
* @param orderHash Hex encoded orderHash.
* @return Whether the supplied orderHash has the expected format.
*/
public static isValidOrderHash(orderHash: string): boolean {
// Since this method can be called to check if any arbitrary string conforms to an orderHash's
// format, we only assert that we were indeed passed a string.
assert.isString('orderHash', orderHash);
const schemaValidator = new SchemaValidator();
const isValidOrderHash = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid;
return isValidOrderHash;
}
/**
* A unit amount is defined as the amount of a token above the specified decimal places (integer part).
* E.g: If a currency has 18 decimal places, 1e18 or one quintillion of the currency is equivalent
* to 1 unit.
* @param amount The amount in baseUnits that you would like converted to units.
* @param decimals The number of decimal places the unit amount has.
* @return The amount in units.
*/
public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber {
assert.isValidBaseUnitAmount('amount', amount);
assert.isNumber('decimals', decimals);
const aUnit = new BigNumber(10).pow(decimals);
const unit = amount.div(aUnit);
return unit;
}
/**
* A baseUnit is defined as the smallest denomination of a token. An amount expressed in baseUnits
* is the amount expressed in the smallest denomination.
* E.g: 1 unit of a token with 18 decimal places is expressed in baseUnits as 1000000000000000000
* @param amount The amount of units that you would like converted to baseUnits.
* @param decimals The number of decimal places the unit amount has.
* @return The amount in baseUnits.
*/
public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber {
assert.isBigNumber('amount', amount);
assert.isNumber('decimals', decimals);
const aUnit = new BigNumber(10).pow(decimals);
const unit = amount.div(aUnit);
return unit;
}
/**
* A baseUnit is defined as the smallest denomination of a token. An amount expressed in baseUnits
* is the amount expressed in the smallest denomination.
* E.g: 1 unit of a token with 18 decimal places is expressed in baseUnits as 1000000000000000000
* @param amount The amount of units that you would like converted to baseUnits.
* @param decimals The number of decimal places the unit amount has.
* @return The amount in baseUnits.
*/
public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber {
assert.isBigNumber('amount', amount);
assert.isNumber('decimals', decimals);
const unit = new BigNumber(10).pow(decimals);
const baseUnitAmount = amount.times(unit);
const hasDecimals = baseUnitAmount.decimalPlaces() !== 0;
if (hasDecimals) {
throw new Error(`Invalid unit amount: ${amount.toString()} - Too many decimal places`);
}
return baseUnitAmount;
}
/**
* Computes the orderHash for a supplied order.
* @param order An object that conforms to the Order or SignedOrder interface definitions.
* @return The resulting orderHash from hashing the supplied order.
*/
@decorators.syncZeroExErrorHandler
public static getOrderHashHex(order: Order | SignedOrder): string {
assert.doesConformToSchema('order', order, schemas.orderSchema);
const orderHashHex = utils.getOrderHashHex(order);
return orderHashHex;
}
/**
* Instantiates a new ZeroEx instance that provides the public interface to the 0x.js library.
* @param provider The Web3.js Provider instance you would like the 0x.js library to use for interacting with
* the Ethereum network.
* @param config The configuration object. Look up the type for the description.
* @return An instance of the 0x.js ZeroEx class.
*/
constructor(provider: Web3Provider, config: ZeroExConfig) {
assert.isWeb3Provider('provider', provider);
assert.doesConformToSchema('config', config, zeroExConfigSchema);
const artifactJSONs = _.values(artifacts);
const abiArrays = _.map(artifactJSONs, artifact => artifact.abi);
this._abiDecoder = new AbiDecoder(abiArrays);
const defaults = {
gasPrice: config.gasPrice,
};
this._web3Wrapper = new Web3Wrapper(provider, defaults);
this.proxy = new TokenTransferProxyWrapper(
this._web3Wrapper,
config.networkId,
config.tokenTransferProxyContractAddress,
);
this.token = new TokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.proxy);
this.exchange = new ExchangeWrapper(
this._web3Wrapper,
config.networkId,
this._abiDecoder,
this.token,
config.exchangeContractAddress,
config.zrxContractAddress,
);
this.tokenRegistry = new TokenRegistryWrapper(
this._web3Wrapper,
config.networkId,
config.tokenRegistryContractAddress,
);
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.token);
this.orderStateWatcher = new OrderStateWatcher(
this._web3Wrapper,
this._abiDecoder,
this.token,
this.exchange,
config.orderWatcherConfig,
);
}
/**
* Sets a new web3 provider for 0x.js. Updating the provider will stop all
* subscriptions so you will need to re-subscribe to all events relevant to your app after this call.
* @param provider The Web3Provider you would like the 0x.js library to use from now on.
* @param networkId The id of the network your provider is connected to
*/
public setProvider(provider: Web3Provider, networkId: number): void {
this._web3Wrapper.setProvider(provider);
(this.exchange as any)._invalidateContractInstances();
(this.exchange as any)._setNetworkId(networkId);
(this.tokenRegistry as any)._invalidateContractInstance();
(this.tokenRegistry as any)._setNetworkId(networkId);
(this.token as any)._invalidateContractInstances();
(this.token as any)._setNetworkId(networkId);
(this.proxy as any)._invalidateContractInstance();
(this.proxy as any)._setNetworkId(networkId);
(this.etherToken as any)._invalidateContractInstance();
(this.etherToken as any)._setNetworkId(networkId);
}
/**
* Get user Ethereum addresses available through the supplied web3 provider available for sending transactions.
* @return An array of available user Ethereum addresses.
*/
public async getAvailableAddressesAsync(): Promise<string[]> {
const availableAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
return availableAddresses;
}
/**
* Signs an orderHash and returns it's elliptic curve signature.
* This method currently supports TestRPC, Geth and Parity above and below V1.6.6
* @param orderHash Hex encoded orderHash to sign.
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
* must be available via the Web3.Provider supplied to 0x.js.
* @param shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message`
* themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client
* (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix
* before sending the request.
* @return An object containing the Elliptic curve signature parameters generated by signing the orderHash.
*/
public async signOrderHashAsync(
orderHash: string,
signerAddress: string,
shouldAddPersonalMessagePrefix: boolean,
): Promise<ECSignature> {
assert.isHexString('orderHash', orderHash);
await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper);
const unit = new BigNumber(10).pow(decimals);
const baseUnitAmount = amount.times(unit);
const hasDecimals = baseUnitAmount.decimalPlaces() !== 0;
if (hasDecimals) {
throw new Error(`Invalid unit amount: ${amount.toString()} - Too many decimal places`);
}
return baseUnitAmount;
}
/**
* Computes the orderHash for a supplied order.
* @param order An object that conforms to the Order or SignedOrder interface definitions.
* @return The resulting orderHash from hashing the supplied order.
*/
@decorators.syncZeroExErrorHandler
public static getOrderHashHex(order: Order | SignedOrder): string {
assert.doesConformToSchema('order', order, schemas.orderSchema);
const orderHashHex = utils.getOrderHashHex(order);
return orderHashHex;
}
/**
* Instantiates a new ZeroEx instance that provides the public interface to the 0x.js library.
* @param provider The Web3.js Provider instance you would like the 0x.js library to use for interacting with
* the Ethereum network.
* @param config The configuration object. Look up the type for the description.
* @return An instance of the 0x.js ZeroEx class.
*/
constructor(provider: Web3Provider, config: ZeroExConfig) {
assert.isWeb3Provider('provider', provider);
assert.doesConformToSchema('config', config, zeroExConfigSchema);
const artifactJSONs = _.values(artifacts);
const abiArrays = _.map(artifactJSONs, artifact => artifact.abi);
this._abiDecoder = new AbiDecoder(abiArrays);
const defaults = {
gasPrice: config.gasPrice,
};
this._web3Wrapper = new Web3Wrapper(provider, defaults);
this.proxy = new TokenTransferProxyWrapper(
this._web3Wrapper,
config.networkId,
config.tokenTransferProxyContractAddress,
);
this.token = new TokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.proxy);
this.exchange = new ExchangeWrapper(
this._web3Wrapper,
config.networkId,
this._abiDecoder,
this.token,
config.exchangeContractAddress,
config.zrxContractAddress,
);
this.tokenRegistry = new TokenRegistryWrapper(
this._web3Wrapper,
config.networkId,
config.tokenRegistryContractAddress,
);
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.token);
this.orderStateWatcher = new OrderStateWatcher(
this._web3Wrapper,
this._abiDecoder,
this.token,
this.exchange,
config.orderWatcherConfig,
);
}
/**
* Sets a new web3 provider for 0x.js. Updating the provider will stop all
* subscriptions so you will need to re-subscribe to all events relevant to your app after this call.
* @param provider The Web3Provider you would like the 0x.js library to use from now on.
* @param networkId The id of the network your provider is connected to
*/
public setProvider(provider: Web3Provider, networkId: number): void {
this._web3Wrapper.setProvider(provider);
(this.exchange as any)._invalidateContractInstances();
(this.exchange as any)._setNetworkId(networkId);
(this.tokenRegistry as any)._invalidateContractInstance();
(this.tokenRegistry as any)._setNetworkId(networkId);
(this.token as any)._invalidateContractInstances();
(this.token as any)._setNetworkId(networkId);
(this.proxy as any)._invalidateContractInstance();
(this.proxy as any)._setNetworkId(networkId);
(this.etherToken as any)._invalidateContractInstance();
(this.etherToken as any)._setNetworkId(networkId);
}
/**
* Get user Ethereum addresses available through the supplied web3 provider available for sending transactions.
* @return An array of available user Ethereum addresses.
*/
public async getAvailableAddressesAsync(): Promise<string[]> {
const availableAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
return availableAddresses;
}
/**
* Signs an orderHash and returns it's elliptic curve signature.
* This method currently supports TestRPC, Geth and Parity above and below V1.6.6
* @param orderHash Hex encoded orderHash to sign.
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
* must be available via the Web3.Provider supplied to 0x.js.
* @param shouldAddPersonalMessagePrefix Some signers add the personal message prefix `\x19Ethereum Signed Message`
* themselves (e.g Parity Signer, Ledger, TestRPC) and others expect it to already be done by the client
* (e.g Metamask). Depending on which signer this request is going to, decide on whether to add the prefix
* before sending the request.
* @return An object containing the Elliptic curve signature parameters generated by signing the orderHash.
*/
public async signOrderHashAsync(
orderHash: string,
signerAddress: string,
shouldAddPersonalMessagePrefix: boolean,
): Promise<ECSignature> {
assert.isHexString('orderHash', orderHash);
await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper);
let msgHashHex = orderHash;
if (shouldAddPersonalMessagePrefix) {
const orderHashBuff = ethUtil.toBuffer(orderHash);
const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff);
msgHashHex = ethUtil.bufferToHex(msgHashBuff);
}
let msgHashHex = orderHash;
if (shouldAddPersonalMessagePrefix) {
const orderHashBuff = ethUtil.toBuffer(orderHash);
const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff);
msgHashHex = ethUtil.bufferToHex(msgHashBuff);
}
const signature = await this._web3Wrapper.signTransactionAsync(signerAddress, msgHashHex);
const signature = await this._web3Wrapper.signTransactionAsync(signerAddress, msgHashHex);
// HACK: There is no consensus on whether the signatureHex string should be formatted as
// v + r + s OR r + s + v, and different clients (even different versions of the same client)
// return the signature params in different orders. In order to support all client implementations,
// we parse the signature in both ways, and evaluate if either one is a valid signature.
const validVParamValues = [27, 28];
const ecSignatureVRS = signatureUtils.parseSignatureHexAsVRS(signature);
if (_.includes(validVParamValues, ecSignatureVRS.v)) {
const isValidVRSSignature = ZeroEx.isValidSignature(orderHash, ecSignatureVRS, signerAddress);
if (isValidVRSSignature) {
return ecSignatureVRS;
}
}
// HACK: There is no consensus on whether the signatureHex string should be formatted as
// v + r + s OR r + s + v, and different clients (even different versions of the same client)
// return the signature params in different orders. In order to support all client implementations,
// we parse the signature in both ways, and evaluate if either one is a valid signature.
const validVParamValues = [27, 28];
const ecSignatureVRS = signatureUtils.parseSignatureHexAsVRS(signature);
if (_.includes(validVParamValues, ecSignatureVRS.v)) {
const isValidVRSSignature = ZeroEx.isValidSignature(orderHash, ecSignatureVRS, signerAddress);
if (isValidVRSSignature) {
return ecSignatureVRS;
}
}
const ecSignatureRSV = signatureUtils.parseSignatureHexAsRSV(signature);
if (_.includes(validVParamValues, ecSignatureRSV.v)) {
const isValidRSVSignature = ZeroEx.isValidSignature(orderHash, ecSignatureRSV, signerAddress);
if (isValidRSVSignature) {
return ecSignatureRSV;
}
}
const ecSignatureRSV = signatureUtils.parseSignatureHexAsRSV(signature);
if (_.includes(validVParamValues, ecSignatureRSV.v)) {
const isValidRSVSignature = ZeroEx.isValidSignature(orderHash, ecSignatureRSV, signerAddress);
if (isValidRSVSignature) {
return ecSignatureRSV;
}
}
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.
* @param timeoutMs How long (in ms) to poll for transaction mined until aborting.
* @return Transaction receipt with decoded log args.
*/
public async awaitTransactionMinedAsync(
txHash: string,
pollingIntervalMs = 1000,
timeoutMs?: number,
): Promise<TransactionReceiptWithDecodedLogs> {
let timeoutExceeded = false;
if (timeoutMs) {
setTimeout(() => (timeoutExceeded = true), timeoutMs);
}
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.
* @param timeoutMs How long (in ms) to poll for transaction mined until aborting.
* @return Transaction receipt with decoded log args.
*/
public async awaitTransactionMinedAsync(
txHash: string,
pollingIntervalMs = 1000,
timeoutMs?: number,
): Promise<TransactionReceiptWithDecodedLogs> {
let timeoutExceeded = false;
if (timeoutMs) {
setTimeout(() => (timeoutExceeded = true), timeoutMs);
}
const txReceiptPromise = new Promise(
(resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => {
const intervalId = intervalUtils.setAsyncExcludingInterval(
async () => {
if (timeoutExceeded) {
intervalUtils.clearAsyncExcludingInterval(intervalId);
return reject(ZeroExError.TransactionMiningTimeout);
}
const txReceiptPromise = new Promise(
(resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => {
const intervalId = intervalUtils.setAsyncExcludingInterval(
async () => {
if (timeoutExceeded) {
intervalUtils.clearAsyncExcludingInterval(intervalId);
return reject(ZeroExError.TransactionMiningTimeout);
}
const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash);
if (!_.isNull(transactionReceipt)) {
intervalUtils.clearAsyncExcludingInterval(intervalId);
const logsWithDecodedArgs = _.map(
transactionReceipt.logs,
this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder),
);
const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = {
...transactionReceipt,
logs: logsWithDecodedArgs,
};
resolve(transactionReceiptWithDecodedLogArgs);
}
},
pollingIntervalMs,
(err: Error) => {
intervalUtils.clearAsyncExcludingInterval(intervalId);
reject(err);
},
);
},
);
const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash);
if (!_.isNull(transactionReceipt)) {
intervalUtils.clearAsyncExcludingInterval(intervalId);
const logsWithDecodedArgs = _.map(
transactionReceipt.logs,
this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder),
);
const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = {
...transactionReceipt,
logs: logsWithDecodedArgs,
};
resolve(transactionReceiptWithDecodedLogArgs);
}
},
pollingIntervalMs,
(err: Error) => {
intervalUtils.clearAsyncExcludingInterval(intervalId);
reject(err);
},
);
},
);
return txReceiptPromise;
}
/*
return txReceiptPromise;
}
/*
* HACK: `TokenWrapper` needs a token transfer proxy address. `TokenTransferProxy` address is fetched from
* an `ExchangeWrapper`. `ExchangeWrapper` needs `TokenWrapper` to validate orders, creating a dependency cycle.
* In order to break this - we create this function here and pass it as a parameter to the `TokenWrapper`
* and `ProxyWrapper`.
*/
private async _getTokenTransferProxyAddressAsync(): Promise<string> {
const tokenTransferProxyAddress = await (this.exchange as any)._getTokenTransferProxyAddressAsync();
return tokenTransferProxyAddress;
}
private async _getTokenTransferProxyAddressAsync(): Promise<string> {
const tokenTransferProxyAddress = await (this.exchange as any)._getTokenTransferProxyAddressAsync();
return tokenTransferProxyAddress;
}
}

View File

@@ -8,11 +8,11 @@ import * as ZRXArtifact from './artifacts/ZRX.json';
import { Artifact } from './types';
export const artifacts = {
ZRXArtifact: (ZRXArtifact as any) as Artifact,
DummyTokenArtifact: (DummyTokenArtifact as any) as Artifact,
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,
ZRXArtifact: (ZRXArtifact as any) as Artifact,
DummyTokenArtifact: (DummyTokenArtifact as any) as Artifact,
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

@@ -1,22 +1,22 @@
{
"contract_name": "DummyToken",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "_target",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "setBalance",
"outputs": [],
"payable": false,
"type": "function"
}
]
"contract_name": "DummyToken",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "_target",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "setBalance",
"outputs": [],
"payable": false,
"type": "function"
}
]
}

View File

@@ -1,287 +1,287 @@
{
"contract_name": "EtherToken",
"abi": [
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "amount",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "deposit",
"outputs": [],
"payable": true,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"payable": true,
"type": "fallback"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Deposit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Withdrawal",
"type": "event"
}
],
"networks": {
"1": {
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
},
"3": {
"address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a"
},
"4": {
"address": "0xc778417e063141139fce010982780140aa0cd5ab"
},
"42": {
"address": "0x653e49e301e508a13237c0ddc98ae7d4cd2667a1"
},
"50": {
"address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c"
}
}
"contract_name": "EtherToken",
"abi": [
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "amount",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "deposit",
"outputs": [],
"payable": true,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"payable": true,
"type": "fallback"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Deposit",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Withdrawal",
"type": "event"
}
],
"networks": {
"1": {
"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
},
"3": {
"address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a"
},
"4": {
"address": "0xc778417e063141139fce010982780140aa0cd5ab"
},
"42": {
"address": "0x653e49e301e508a13237c0ddc98ae7d4cd2667a1"
},
"50": {
"address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,172 +1,172 @@
{
"contract_name": "Token",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "supply",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "remaining",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
]
"contract_name": "Token",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "supply",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "success",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "remaining",
"type": "uint256"
}
],
"payable": false,
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_from",
"type": "address"
},
{
"indexed": true,
"name": "_to",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "_owner",
"type": "address"
},
{
"indexed": true,
"name": "_spender",
"type": "address"
},
{
"indexed": false,
"name": "_value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,187 +1,187 @@
{
"contract_name": "TokenTransferProxy",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "token",
"type": "address"
},
{
"name": "from",
"type": "address"
},
{
"name": "to",
"type": "address"
},
{
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "target",
"type": "address"
}
],
"name": "addAuthorizedAddress",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "uint256"
}
],
"name": "authorities",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "target",
"type": "address"
}
],
"name": "removeAuthorizedAddress",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "owner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "address"
}
],
"name": "authorized",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getAuthorizedAddresses",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"payable": false,
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressRemoved",
"type": "event"
}
],
"networks": {
"1": {
"address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4"
},
"3": {
"address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6"
},
"4": {
"address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d"
},
"42": {
"address": "0x087eed4bc1ee3de49befbd66c662b434b15d49d4"
},
"50": {
"address": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48"
}
}
"contract_name": "TokenTransferProxy",
"abi": [
{
"constant": false,
"inputs": [
{
"name": "token",
"type": "address"
},
{
"name": "from",
"type": "address"
},
{
"name": "to",
"type": "address"
},
{
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "target",
"type": "address"
}
],
"name": "addAuthorizedAddress",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "uint256"
}
],
"name": "authorities",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "target",
"type": "address"
}
],
"name": "removeAuthorizedAddress",
"outputs": [],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "owner",
"outputs": [
{
"name": "",
"type": "address"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "address"
}
],
"name": "authorized",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getAuthorizedAddresses",
"outputs": [
{
"name": "",
"type": "address[]"
}
],
"payable": false,
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"payable": false,
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "target",
"type": "address"
},
{
"indexed": true,
"name": "caller",
"type": "address"
}
],
"name": "LogAuthorizedAddressRemoved",
"type": "event"
}
],
"networks": {
"1": {
"address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4"
},
"3": {
"address": "0x4e9aad8184de8833365fea970cd9149372fdf1e6"
},
"4": {
"address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d"
},
"42": {
"address": "0x087eed4bc1ee3de49befbd66c662b434b15d49d4"
},
"50": {
"address": "0x1dc4c1cefef38a777b15aa20260a54e584b16c48"
}
}
}

View File

@@ -1,20 +1,20 @@
{
"contract_name": "ZRX",
"networks": {
"1": {
"address": "0xe41d2489571d322189246dafa5ebde1f4699f498"
},
"3": {
"address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d"
},
"4": {
"address": "0x00f58d6d585f84b2d7267940cede30ce2fe6eae8"
},
"42": {
"address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570"
},
"50": {
"address": "0x1d7022f5b17d2f8b695918fb48fa1089c9f85401"
}
}
"contract_name": "ZRX",
"networks": {
"1": {
"address": "0xe41d2489571d322189246dafa5ebde1f4699f498"
},
"3": {
"address": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d"
},
"4": {
"address": "0x00f58d6d585f84b2d7267940cede30ce2fe6eae8"
},
"42": {
"address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570"
},
"50": {
"address": "0x1d7022f5b17d2f8b695918fb48fa1089c9f85401"
}
}
}

View File

@@ -5,203 +5,203 @@ import * as _ from 'lodash';
import * as Web3 from 'web3';
import {
Artifact,
BlockParamLiteral,
BlockRange,
ContractEventArgs,
ContractEvents,
EventCallback,
IndexedFilterValues,
InternalZeroExError,
LogWithDecodedArgs,
RawLog,
ZeroExError,
Artifact,
BlockParamLiteral,
BlockRange,
ContractEventArgs,
ContractEvents,
EventCallback,
IndexedFilterValues,
InternalZeroExError,
LogWithDecodedArgs,
RawLog,
ZeroExError,
} from '../types';
import { AbiDecoder } from '../utils/abi_decoder';
import { constants } from '../utils/constants';
import { filterUtils } from '../utils/filter_utils';
const CONTRACT_NAME_TO_NOT_FOUND_ERROR: {
[contractName: string]: ZeroExError;
[contractName: string]: ZeroExError;
} = {
ZRX: ZeroExError.ZRXContractDoesNotExist,
EtherToken: ZeroExError.EtherTokenContractDoesNotExist,
Token: ZeroExError.TokenContractDoesNotExist,
TokenRegistry: ZeroExError.TokenRegistryContractDoesNotExist,
TokenTransferProxy: ZeroExError.TokenTransferProxyContractDoesNotExist,
Exchange: ZeroExError.ExchangeContractDoesNotExist,
ZRX: ZeroExError.ZRXContractDoesNotExist,
EtherToken: ZeroExError.EtherTokenContractDoesNotExist,
Token: ZeroExError.TokenContractDoesNotExist,
TokenRegistry: ZeroExError.TokenRegistryContractDoesNotExist,
TokenTransferProxy: ZeroExError.TokenTransferProxyContractDoesNotExist,
Exchange: ZeroExError.ExchangeContractDoesNotExist,
};
export class ContractWrapper {
protected _web3Wrapper: Web3Wrapper;
private _networkId: number;
private _abiDecoder?: AbiDecoder;
private _blockAndLogStreamerIfExists: BlockAndLogStreamer | undefined;
private _blockAndLogStreamInterval: NodeJS.Timer;
private _filters: { [filterToken: string]: Web3.FilterObject };
private _filterCallbacks: {
[filterToken: string]: EventCallback<ContractEventArgs>;
};
private _onLogAddedSubscriptionToken: string | undefined;
private _onLogRemovedSubscriptionToken: string | undefined;
constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder?: AbiDecoder) {
this._web3Wrapper = web3Wrapper;
this._networkId = networkId;
this._abiDecoder = abiDecoder;
this._filters = {};
this._filterCallbacks = {};
this._blockAndLogStreamerIfExists = undefined;
this._onLogAddedSubscriptionToken = undefined;
this._onLogRemovedSubscriptionToken = undefined;
}
protected unsubscribeAll(): void {
const filterTokens = _.keys(this._filterCallbacks);
_.each(filterTokens, filterToken => {
this._unsubscribe(filterToken);
});
}
protected _unsubscribe(filterToken: string, err?: Error): void {
if (_.isUndefined(this._filters[filterToken])) {
throw new Error(ZeroExError.SubscriptionNotFound);
}
if (!_.isUndefined(err)) {
const callback = this._filterCallbacks[filterToken];
callback(err, undefined);
}
delete this._filters[filterToken];
delete this._filterCallbacks[filterToken];
if (_.isEmpty(this._filters)) {
this._stopBlockAndLogStream();
}
}
protected _subscribe<ArgsType extends ContractEventArgs>(
address: string,
eventName: ContractEvents,
indexFilterValues: IndexedFilterValues,
abi: Web3.ContractAbi,
callback: EventCallback<ArgsType>,
): string {
const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi);
if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
this._startBlockAndLogStream();
}
const filterToken = filterUtils.generateUUID();
this._filters[filterToken] = filter;
this._filterCallbacks[filterToken] = callback as EventCallback<ContractEventArgs>;
return filterToken;
}
protected async _getLogsAsync<ArgsType extends ContractEventArgs>(
address: string,
eventName: ContractEvents,
blockRange: BlockRange,
indexFilterValues: IndexedFilterValues,
abi: Web3.ContractAbi,
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, blockRange);
const logs = await this._web3Wrapper.getLogsAsync(filter);
const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this));
return logsWithDecodedArguments;
}
protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>(
log: Web3.LogEntry,
): LogWithDecodedArgs<ArgsType> | RawLog {
if (_.isUndefined(this._abiDecoder)) {
throw new Error(InternalZeroExError.NoAbiDecoder);
}
const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log);
return logWithDecodedArgs;
}
protected async _instantiateContractIfExistsAsync(
artifact: Artifact,
addressIfExists?: string,
): Promise<Web3.ContractInstance> {
let contractAddress: string;
if (_.isUndefined(addressIfExists)) {
if (_.isUndefined(artifact.networks[this._networkId])) {
throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
}
contractAddress = artifact.networks[this._networkId].address.toLowerCase();
} else {
contractAddress = addressIfExists;
}
const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress);
if (!doesContractExist) {
throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]);
}
const contractInstance = this._web3Wrapper.getContractInstance(artifact.abi, contractAddress);
return contractInstance;
}
protected _getContractAddress(artifact: Artifact, addressIfExists?: string): string {
if (_.isUndefined(addressIfExists)) {
const contractAddress = artifact.networks[this._networkId].address;
if (_.isUndefined(contractAddress)) {
throw new Error(ZeroExError.ExchangeContractDoesNotExist);
}
return contractAddress;
} else {
return addressIfExists;
}
}
private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, log: Web3.LogEntry): void {
_.forEach(this._filters, (filter: Web3.FilterObject, filterToken: string) => {
if (filterUtils.matchesFilter(log, filter)) {
const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>;
const logEvent = {
log: decodedLog,
isRemoved,
};
this._filterCallbacks[filterToken](null, logEvent);
}
});
}
private _startBlockAndLogStream(): void {
if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
}
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper),
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
);
const catchAllLogFilter = {};
this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter);
this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval(
this._reconcileBlockAsync.bind(this),
constants.DEFAULT_BLOCK_POLLING_INTERVAL,
this._onReconcileBlockError.bind(this),
);
let isRemoved = false;
this._onLogAddedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogAdded(
this._onLogStateChanged.bind(this, isRemoved),
);
isRemoved = true;
this._onLogRemovedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogRemoved(
this._onLogStateChanged.bind(this, isRemoved),
);
}
private _onReconcileBlockError(err: Error): void {
const filterTokens = _.keys(this._filterCallbacks);
_.each(filterTokens, filterToken => {
this._unsubscribe(filterToken, err);
});
}
private _setNetworkId(networkId: number): void {
this._networkId = networkId;
}
private _stopBlockAndLogStream(): void {
if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
throw new Error(ZeroExError.SubscriptionNotFound);
}
this._blockAndLogStreamerIfExists.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string);
this._blockAndLogStreamerIfExists.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string);
intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval);
delete this._blockAndLogStreamerIfExists;
}
private async _reconcileBlockAsync(): Promise<void> {
const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest);
// We need to coerce to Block type cause Web3.Block includes types for mempool blocks
if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
// If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined
await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlock as any) as Block);
}
}
protected _web3Wrapper: Web3Wrapper;
private _networkId: number;
private _abiDecoder?: AbiDecoder;
private _blockAndLogStreamerIfExists: BlockAndLogStreamer | undefined;
private _blockAndLogStreamInterval: NodeJS.Timer;
private _filters: { [filterToken: string]: Web3.FilterObject };
private _filterCallbacks: {
[filterToken: string]: EventCallback<ContractEventArgs>;
};
private _onLogAddedSubscriptionToken: string | undefined;
private _onLogRemovedSubscriptionToken: string | undefined;
constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder?: AbiDecoder) {
this._web3Wrapper = web3Wrapper;
this._networkId = networkId;
this._abiDecoder = abiDecoder;
this._filters = {};
this._filterCallbacks = {};
this._blockAndLogStreamerIfExists = undefined;
this._onLogAddedSubscriptionToken = undefined;
this._onLogRemovedSubscriptionToken = undefined;
}
protected unsubscribeAll(): void {
const filterTokens = _.keys(this._filterCallbacks);
_.each(filterTokens, filterToken => {
this._unsubscribe(filterToken);
});
}
protected _unsubscribe(filterToken: string, err?: Error): void {
if (_.isUndefined(this._filters[filterToken])) {
throw new Error(ZeroExError.SubscriptionNotFound);
}
if (!_.isUndefined(err)) {
const callback = this._filterCallbacks[filterToken];
callback(err, undefined);
}
delete this._filters[filterToken];
delete this._filterCallbacks[filterToken];
if (_.isEmpty(this._filters)) {
this._stopBlockAndLogStream();
}
}
protected _subscribe<ArgsType extends ContractEventArgs>(
address: string,
eventName: ContractEvents,
indexFilterValues: IndexedFilterValues,
abi: Web3.ContractAbi,
callback: EventCallback<ArgsType>,
): string {
const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi);
if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
this._startBlockAndLogStream();
}
const filterToken = filterUtils.generateUUID();
this._filters[filterToken] = filter;
this._filterCallbacks[filterToken] = callback as EventCallback<ContractEventArgs>;
return filterToken;
}
protected async _getLogsAsync<ArgsType extends ContractEventArgs>(
address: string,
eventName: ContractEvents,
blockRange: BlockRange,
indexFilterValues: IndexedFilterValues,
abi: Web3.ContractAbi,
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, blockRange);
const logs = await this._web3Wrapper.getLogsAsync(filter);
const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this));
return logsWithDecodedArguments;
}
protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>(
log: Web3.LogEntry,
): LogWithDecodedArgs<ArgsType> | RawLog {
if (_.isUndefined(this._abiDecoder)) {
throw new Error(InternalZeroExError.NoAbiDecoder);
}
const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log);
return logWithDecodedArgs;
}
protected async _instantiateContractIfExistsAsync(
artifact: Artifact,
addressIfExists?: string,
): Promise<Web3.ContractInstance> {
let contractAddress: string;
if (_.isUndefined(addressIfExists)) {
if (_.isUndefined(artifact.networks[this._networkId])) {
throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
}
contractAddress = artifact.networks[this._networkId].address.toLowerCase();
} else {
contractAddress = addressIfExists;
}
const doesContractExist = await this._web3Wrapper.doesContractExistAtAddressAsync(contractAddress);
if (!doesContractExist) {
throw new Error(CONTRACT_NAME_TO_NOT_FOUND_ERROR[artifact.contract_name]);
}
const contractInstance = this._web3Wrapper.getContractInstance(artifact.abi, contractAddress);
return contractInstance;
}
protected _getContractAddress(artifact: Artifact, addressIfExists?: string): string {
if (_.isUndefined(addressIfExists)) {
const contractAddress = artifact.networks[this._networkId].address;
if (_.isUndefined(contractAddress)) {
throw new Error(ZeroExError.ExchangeContractDoesNotExist);
}
return contractAddress;
} else {
return addressIfExists;
}
}
private _onLogStateChanged<ArgsType extends ContractEventArgs>(isRemoved: boolean, log: Web3.LogEntry): void {
_.forEach(this._filters, (filter: Web3.FilterObject, filterToken: string) => {
if (filterUtils.matchesFilter(log, filter)) {
const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>;
const logEvent = {
log: decodedLog,
isRemoved,
};
this._filterCallbacks[filterToken](null, logEvent);
}
});
}
private _startBlockAndLogStream(): void {
if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
}
this._blockAndLogStreamerIfExists = new BlockAndLogStreamer(
this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper),
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
);
const catchAllLogFilter = {};
this._blockAndLogStreamerIfExists.addLogFilter(catchAllLogFilter);
this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval(
this._reconcileBlockAsync.bind(this),
constants.DEFAULT_BLOCK_POLLING_INTERVAL,
this._onReconcileBlockError.bind(this),
);
let isRemoved = false;
this._onLogAddedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogAdded(
this._onLogStateChanged.bind(this, isRemoved),
);
isRemoved = true;
this._onLogRemovedSubscriptionToken = this._blockAndLogStreamerIfExists.subscribeToOnLogRemoved(
this._onLogStateChanged.bind(this, isRemoved),
);
}
private _onReconcileBlockError(err: Error): void {
const filterTokens = _.keys(this._filterCallbacks);
_.each(filterTokens, filterToken => {
this._unsubscribe(filterToken, err);
});
}
private _setNetworkId(networkId: number): void {
this._networkId = networkId;
}
private _stopBlockAndLogStream(): void {
if (_.isUndefined(this._blockAndLogStreamerIfExists)) {
throw new Error(ZeroExError.SubscriptionNotFound);
}
this._blockAndLogStreamerIfExists.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string);
this._blockAndLogStreamerIfExists.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string);
intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval);
delete this._blockAndLogStreamerIfExists;
}
private async _reconcileBlockAsync(): Promise<void> {
const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest);
// We need to coerce to Block type cause Web3.Block includes types for mempool blocks
if (!_.isUndefined(this._blockAndLogStreamerIfExists)) {
// If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined
await this._blockAndLogStreamerIfExists.reconcileNewBlock((latestBlock as any) as Block);
}
}
}

View File

@@ -5,14 +5,14 @@ import * as _ from 'lodash';
import { artifacts } from '../artifacts';
import {
BlockRange,
EtherTokenContractEventArgs,
EtherTokenEvents,
EventCallback,
IndexedFilterValues,
LogWithDecodedArgs,
TransactionOpts,
ZeroExError,
BlockRange,
EtherTokenContractEventArgs,
EtherTokenEvents,
EventCallback,
IndexedFilterValues,
LogWithDecodedArgs,
TransactionOpts,
ZeroExError,
} from '../types';
import { AbiDecoder } from '../utils/abi_decoder';
import { assert } from '../utils/assert';
@@ -26,159 +26,159 @@ import { TokenWrapper } from './token_wrapper';
* The caller can convert ETH into the equivalent number of wrapped ETH ERC20 tokens and back.
*/
export class EtherTokenWrapper extends ContractWrapper {
private _etherTokenContractsByAddress: {
[address: string]: EtherTokenContract;
} = {};
private _tokenWrapper: TokenWrapper;
constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder: AbiDecoder, tokenWrapper: TokenWrapper) {
super(web3Wrapper, networkId, abiDecoder);
this._tokenWrapper = tokenWrapper;
}
/**
* Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens
* to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1
* for ETH.
* @param etherTokenAddress EtherToken address you wish to deposit into.
* @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.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async depositAsync(
etherTokenAddress: string,
amountInWei: BigNumber,
depositor: string,
txOpts: TransactionOpts = {},
): Promise<string> {
assert.isValidBaseUnitAmount('amountInWei', amountInWei);
await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper);
private _etherTokenContractsByAddress: {
[address: string]: EtherTokenContract;
} = {};
private _tokenWrapper: TokenWrapper;
constructor(web3Wrapper: Web3Wrapper, networkId: number, abiDecoder: AbiDecoder, tokenWrapper: TokenWrapper) {
super(web3Wrapper, networkId, abiDecoder);
this._tokenWrapper = tokenWrapper;
}
/**
* Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens
* to the depositor address. These wrapped ETH tokens can be used in 0x trades and are redeemable for 1-to-1
* for ETH.
* @param etherTokenAddress EtherToken address you wish to deposit into.
* @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.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async depositAsync(
etherTokenAddress: string,
amountInWei: BigNumber,
depositor: string,
txOpts: TransactionOpts = {},
): Promise<string> {
assert.isValidBaseUnitAmount('amountInWei', amountInWei);
await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper);
const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor);
assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit);
const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor);
assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit);
const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress);
const txHash = await wethContract.deposit.sendTransactionAsync({
from: depositor,
value: amountInWei,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
});
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 etherTokenAddress EtherToken address you wish to withdraw from.
* @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.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async withdrawAsync(
etherTokenAddress: string,
amountInWei: BigNumber,
withdrawer: string,
txOpts: TransactionOpts = {},
): Promise<string> {
assert.isValidBaseUnitAmount('amountInWei', amountInWei);
await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper);
const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress);
const txHash = await wethContract.deposit.sendTransactionAsync({
from: depositor,
value: amountInWei,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
});
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 etherTokenAddress EtherToken address you wish to withdraw from.
* @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.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async withdrawAsync(
etherTokenAddress: string,
amountInWei: BigNumber,
withdrawer: string,
txOpts: TransactionOpts = {},
): Promise<string> {
assert.isValidBaseUnitAmount('amountInWei', amountInWei);
await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper);
const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(etherTokenAddress, withdrawer);
assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal);
const WETHBalanceInBaseUnits = await this._tokenWrapper.getBalanceAsync(etherTokenAddress, withdrawer);
assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal);
const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress);
const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, {
from: withdrawer,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
});
return txHash;
}
/**
* Gets historical logs without creating a subscription
* @param etherTokenAddress An address of the ether token that emmited the logs.
* @param eventName The ether token contract event you would like to subscribe to.
* @param blockRange Block range to get logs from.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{_owner: aUserAddressHex}`
* @return Array of logs that match the parameters
*/
public async getLogsAsync<ArgsType extends EtherTokenContractEventArgs>(
etherTokenAddress: string,
eventName: EtherTokenEvents,
blockRange: BlockRange,
indexFilterValues: IndexedFilterValues,
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents);
assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
const logs = await this._getLogsAsync<ArgsType>(
etherTokenAddress,
eventName,
blockRange,
indexFilterValues,
artifacts.EtherTokenArtifact.abi,
);
return logs;
}
/**
* Subscribe to an event type emitted by the Token contract.
* @param etherTokenAddress The hex encoded address where the ether token is deployed.
* @param eventName The ether token contract event you would like to subscribe to.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{_owner: aUserAddressHex}`
* @param callback Callback that gets called when a log is added/removed
* @return Subscription token used later to unsubscribe
*/
public subscribe<ArgsType extends EtherTokenContractEventArgs>(
etherTokenAddress: string,
eventName: EtherTokenEvents,
indexFilterValues: IndexedFilterValues,
callback: EventCallback<ArgsType>,
): string {
assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
assert.isFunction('callback', callback);
const subscriptionToken = this._subscribe<ArgsType>(
etherTokenAddress,
eventName,
indexFilterValues,
artifacts.EtherTokenArtifact.abi,
callback,
);
return subscriptionToken;
}
/**
* Cancel a subscription
* @param subscriptionToken Subscription token returned by `subscribe()`
*/
public unsubscribe(subscriptionToken: string): void {
this._unsubscribe(subscriptionToken);
}
/**
* Cancels all existing subscriptions
*/
public unsubscribeAll(): void {
super.unsubscribeAll();
}
private _invalidateContractInstance(): void {
this.unsubscribeAll();
this._etherTokenContractsByAddress = {};
}
private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> {
let etherTokenContract = this._etherTokenContractsByAddress[etherTokenAddress];
if (!_.isUndefined(etherTokenContract)) {
return etherTokenContract;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
artifacts.EtherTokenArtifact,
etherTokenAddress,
);
const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults());
etherTokenContract = contractInstance;
this._etherTokenContractsByAddress[etherTokenAddress] = etherTokenContract;
return etherTokenContract;
}
const wethContract = await this._getEtherTokenContractAsync(etherTokenAddress);
const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, {
from: withdrawer,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
});
return txHash;
}
/**
* Gets historical logs without creating a subscription
* @param etherTokenAddress An address of the ether token that emmited the logs.
* @param eventName The ether token contract event you would like to subscribe to.
* @param blockRange Block range to get logs from.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{_owner: aUserAddressHex}`
* @return Array of logs that match the parameters
*/
public async getLogsAsync<ArgsType extends EtherTokenContractEventArgs>(
etherTokenAddress: string,
eventName: EtherTokenEvents,
blockRange: BlockRange,
indexFilterValues: IndexedFilterValues,
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents);
assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
const logs = await this._getLogsAsync<ArgsType>(
etherTokenAddress,
eventName,
blockRange,
indexFilterValues,
artifacts.EtherTokenArtifact.abi,
);
return logs;
}
/**
* Subscribe to an event type emitted by the Token contract.
* @param etherTokenAddress The hex encoded address where the ether token is deployed.
* @param eventName The ether token contract event you would like to subscribe to.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{_owner: aUserAddressHex}`
* @param callback Callback that gets called when a log is added/removed
* @return Subscription token used later to unsubscribe
*/
public subscribe<ArgsType extends EtherTokenContractEventArgs>(
etherTokenAddress: string,
eventName: EtherTokenEvents,
indexFilterValues: IndexedFilterValues,
callback: EventCallback<ArgsType>,
): string {
assert.isETHAddressHex('etherTokenAddress', etherTokenAddress);
assert.doesBelongToStringEnum('eventName', eventName, EtherTokenEvents);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
assert.isFunction('callback', callback);
const subscriptionToken = this._subscribe<ArgsType>(
etherTokenAddress,
eventName,
indexFilterValues,
artifacts.EtherTokenArtifact.abi,
callback,
);
return subscriptionToken;
}
/**
* Cancel a subscription
* @param subscriptionToken Subscription token returned by `subscribe()`
*/
public unsubscribe(subscriptionToken: string): void {
this._unsubscribe(subscriptionToken);
}
/**
* Cancels all existing subscriptions
*/
public unsubscribeAll(): void {
super.unsubscribeAll();
}
private _invalidateContractInstance(): void {
this.unsubscribeAll();
this._etherTokenContractsByAddress = {};
}
private async _getEtherTokenContractAsync(etherTokenAddress: string): Promise<EtherTokenContract> {
let etherTokenContract = this._etherTokenContractsByAddress[etherTokenAddress];
if (!_.isUndefined(etherTokenContract)) {
return etherTokenContract;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
artifacts.EtherTokenArtifact,
etherTokenAddress,
);
const contractInstance = new EtherTokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults());
etherTokenContract = contractInstance;
this._etherTokenContractsByAddress[etherTokenAddress] = etherTokenContract;
return etherTokenContract;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,117 +13,117 @@ import { TokenRegistryContract } from './generated/token_registry';
* This class includes all the functionality related to interacting with the 0x Token Registry smart contract.
*/
export class TokenRegistryWrapper extends ContractWrapper {
private _tokenRegistryContractIfExists?: TokenRegistryContract;
private _contractAddressIfExists?: string;
private static _createTokenFromMetadata(metadata: TokenMetadata): Token | undefined {
if (metadata[0] === constants.NULL_ADDRESS) {
return undefined;
}
const token = {
address: metadata[0],
name: metadata[1],
symbol: metadata[2],
decimals: metadata[3].toNumber(),
};
return token;
}
constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) {
super(web3Wrapper, networkId);
this._contractAddressIfExists = contractAddressIfExists;
}
/**
* Retrieves all the tokens currently listed in the Token Registry smart contract
* @return An array of objects that conform to the Token interface.
*/
public async getTokensAsync(): Promise<Token[]> {
const addresses = await this.getTokenAddressesAsync();
const tokenPromises: Array<Promise<Token | undefined>> = _.map(addresses, async (address: string) =>
this.getTokenIfExistsAsync(address),
);
const tokens = await Promise.all(tokenPromises);
return tokens as Token[];
}
/**
* Retrieves all the addresses of the tokens currently listed in the Token Registry smart contract
* @return An array of token addresses.
*/
public async getTokenAddressesAsync(): Promise<string[]> {
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addresses = await tokenRegistryContract.getTokenAddresses.callAsync();
return addresses;
}
/**
* Retrieves a token by address currently listed in the Token Registry smart contract
* @return An object that conforms to the Token interface or undefined if token not found.
*/
public async getTokenIfExistsAsync(address: string): Promise<Token | undefined> {
assert.isETHAddressHex('address', address);
private _tokenRegistryContractIfExists?: TokenRegistryContract;
private _contractAddressIfExists?: string;
private static _createTokenFromMetadata(metadata: TokenMetadata): Token | undefined {
if (metadata[0] === constants.NULL_ADDRESS) {
return undefined;
}
const token = {
address: metadata[0],
name: metadata[1],
symbol: metadata[2],
decimals: metadata[3].toNumber(),
};
return token;
}
constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) {
super(web3Wrapper, networkId);
this._contractAddressIfExists = contractAddressIfExists;
}
/**
* Retrieves all the tokens currently listed in the Token Registry smart contract
* @return An array of objects that conform to the Token interface.
*/
public async getTokensAsync(): Promise<Token[]> {
const addresses = await this.getTokenAddressesAsync();
const tokenPromises: Array<Promise<Token | undefined>> = _.map(addresses, async (address: string) =>
this.getTokenIfExistsAsync(address),
);
const tokens = await Promise.all(tokenPromises);
return tokens as Token[];
}
/**
* Retrieves all the addresses of the tokens currently listed in the Token Registry smart contract
* @return An array of token addresses.
*/
public async getTokenAddressesAsync(): Promise<string[]> {
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addresses = await tokenRegistryContract.getTokenAddresses.callAsync();
return addresses;
}
/**
* Retrieves a token by address currently listed in the Token Registry smart contract
* @return An object that conforms to the Token interface or undefined if token not found.
*/
public async getTokenIfExistsAsync(address: string): Promise<Token | undefined> {
assert.isETHAddressHex('address', address);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address);
const token = TokenRegistryWrapper._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.callAsync(symbol);
if (addressIfExists === constants.NULL_ADDRESS) {
return undefined;
}
return addressIfExists;
}
public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string | undefined> {
assert.isString('name', name);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name);
if (addressIfExists === constants.NULL_ADDRESS) {
return undefined;
}
return addressIfExists;
}
public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token | undefined> {
assert.isString('symbol', symbol);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol);
const token = TokenRegistryWrapper._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.callAsync(name);
const token = TokenRegistryWrapper._createTokenFromMetadata(metadata);
return token;
}
/**
* Retrieves the Ethereum address of the TokenRegistry contract deployed on the network
* that the user-passed web3 provider is connected to.
* @returns The Ethereum address of the TokenRegistry contract being used.
*/
public getContractAddress(): string {
const contractAddress = this._getContractAddress(
artifacts.TokenRegistryArtifact,
this._contractAddressIfExists,
);
return contractAddress;
}
private _invalidateContractInstance(): void {
delete this._tokenRegistryContractIfExists;
}
private async _getTokenRegistryContractAsync(): Promise<TokenRegistryContract> {
if (!_.isUndefined(this._tokenRegistryContractIfExists)) {
return this._tokenRegistryContractIfExists;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
artifacts.TokenRegistryArtifact,
this._contractAddressIfExists,
);
const contractInstance = new TokenRegistryContract(
web3ContractInstance,
this._web3Wrapper.getContractDefaults(),
);
this._tokenRegistryContractIfExists = contractInstance;
return this._tokenRegistryContractIfExists;
}
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address);
const token = TokenRegistryWrapper._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.callAsync(symbol);
if (addressIfExists === constants.NULL_ADDRESS) {
return undefined;
}
return addressIfExists;
}
public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string | undefined> {
assert.isString('name', name);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name);
if (addressIfExists === constants.NULL_ADDRESS) {
return undefined;
}
return addressIfExists;
}
public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token | undefined> {
assert.isString('symbol', symbol);
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol);
const token = TokenRegistryWrapper._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.callAsync(name);
const token = TokenRegistryWrapper._createTokenFromMetadata(metadata);
return token;
}
/**
* Retrieves the Ethereum address of the TokenRegistry contract deployed on the network
* that the user-passed web3 provider is connected to.
* @returns The Ethereum address of the TokenRegistry contract being used.
*/
public getContractAddress(): string {
const contractAddress = this._getContractAddress(
artifacts.TokenRegistryArtifact,
this._contractAddressIfExists,
);
return contractAddress;
}
private _invalidateContractInstance(): void {
delete this._tokenRegistryContractIfExists;
}
private async _getTokenRegistryContractAsync(): Promise<TokenRegistryContract> {
if (!_.isUndefined(this._tokenRegistryContractIfExists)) {
return this._tokenRegistryContractIfExists;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
artifacts.TokenRegistryArtifact,
this._contractAddressIfExists,
);
const contractInstance = new TokenRegistryContract(
web3ContractInstance,
this._web3Wrapper.getContractDefaults(),
);
this._tokenRegistryContractIfExists = contractInstance;
return this._tokenRegistryContractIfExists;
}
}

View File

@@ -10,59 +10,59 @@ import { TokenTransferProxyContract } from './generated/token_transfer_proxy';
* This class includes the functionality related to interacting with the TokenTransferProxy contract.
*/
export class TokenTransferProxyWrapper extends ContractWrapper {
private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract;
private _contractAddressIfExists?: string;
constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) {
super(web3Wrapper, networkId);
this._contractAddressIfExists = contractAddressIfExists;
}
/**
* Check if the Exchange contract address is authorized by the TokenTransferProxy contract.
* @param exchangeContractAddress The hex encoded address of the Exchange contract to call.
* @return Whether the exchangeContractAddress is authorized.
*/
public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> {
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(exchangeContractAddress);
return isAuthorized;
}
/**
* Get the list of all Exchange contract addresses authorized by the TokenTransferProxy contract.
* @return The list of authorized addresses.
*/
public async getAuthorizedAddressesAsync(): Promise<string[]> {
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync();
return authorizedAddresses;
}
/**
* Retrieves the Ethereum address of the TokenTransferProxy contract deployed on the network
* that the user-passed web3 provider is connected to.
* @returns The Ethereum address of the TokenTransferProxy contract being used.
*/
public getContractAddress(): string {
const contractAddress = this._getContractAddress(
artifacts.TokenTransferProxyArtifact,
this._contractAddressIfExists,
);
return contractAddress;
}
private _invalidateContractInstance(): void {
delete this._tokenTransferProxyContractIfExists;
}
private async _getTokenTransferProxyContractAsync(): Promise<TokenTransferProxyContract> {
if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) {
return this._tokenTransferProxyContractIfExists;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
artifacts.TokenTransferProxyArtifact,
this._contractAddressIfExists,
);
const contractInstance = new TokenTransferProxyContract(
web3ContractInstance,
this._web3Wrapper.getContractDefaults(),
);
this._tokenTransferProxyContractIfExists = contractInstance;
return this._tokenTransferProxyContractIfExists;
}
private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract;
private _contractAddressIfExists?: string;
constructor(web3Wrapper: Web3Wrapper, networkId: number, contractAddressIfExists?: string) {
super(web3Wrapper, networkId);
this._contractAddressIfExists = contractAddressIfExists;
}
/**
* Check if the Exchange contract address is authorized by the TokenTransferProxy contract.
* @param exchangeContractAddress The hex encoded address of the Exchange contract to call.
* @return Whether the exchangeContractAddress is authorized.
*/
public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> {
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(exchangeContractAddress);
return isAuthorized;
}
/**
* Get the list of all Exchange contract addresses authorized by the TokenTransferProxy contract.
* @return The list of authorized addresses.
*/
public async getAuthorizedAddressesAsync(): Promise<string[]> {
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync();
return authorizedAddresses;
}
/**
* Retrieves the Ethereum address of the TokenTransferProxy contract deployed on the network
* that the user-passed web3 provider is connected to.
* @returns The Ethereum address of the TokenTransferProxy contract being used.
*/
public getContractAddress(): string {
const contractAddress = this._getContractAddress(
artifacts.TokenTransferProxyArtifact,
this._contractAddressIfExists,
);
return contractAddress;
}
private _invalidateContractInstance(): void {
delete this._tokenTransferProxyContractIfExists;
}
private async _getTokenTransferProxyContractAsync(): Promise<TokenTransferProxyContract> {
if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) {
return this._tokenTransferProxyContractIfExists;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
artifacts.TokenTransferProxyArtifact,
this._contractAddressIfExists,
);
const contractInstance = new TokenTransferProxyContract(
web3ContractInstance,
this._web3Wrapper.getContractDefaults(),
);
this._tokenTransferProxyContractIfExists = contractInstance;
return this._tokenTransferProxyContractIfExists;
}
}

View File

@@ -5,15 +5,15 @@ import * as _ from 'lodash';
import { artifacts } from '../artifacts';
import {
BlockRange,
EventCallback,
IndexedFilterValues,
LogWithDecodedArgs,
MethodOpts,
TokenContractEventArgs,
TokenEvents,
TransactionOpts,
ZeroExError,
BlockRange,
EventCallback,
IndexedFilterValues,
LogWithDecodedArgs,
MethodOpts,
TokenContractEventArgs,
TokenEvents,
TransactionOpts,
ZeroExError,
} from '../types';
import { AbiDecoder } from '../utils/abi_decoder';
import { assert } from '../utils/assert';
@@ -29,367 +29,367 @@ import { TokenTransferProxyWrapper } from './token_transfer_proxy_wrapper';
* to the 0x Proxy smart contract.
*/
export class TokenWrapper extends ContractWrapper {
public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
private _tokenContractsByAddress: { [address: string]: TokenContract };
private _tokenTransferProxyWrapper: TokenTransferProxyWrapper;
constructor(
web3Wrapper: Web3Wrapper,
networkId: number,
abiDecoder: AbiDecoder,
tokenTransferProxyWrapper: TokenTransferProxyWrapper,
) {
super(web3Wrapper, networkId, abiDecoder);
this._tokenContractsByAddress = {};
this._tokenTransferProxyWrapper = tokenTransferProxyWrapper;
}
/**
* Retrieves an owner's ERC20 token balance.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check.
* @param methodOpts Optional arguments this method accepts.
* @return The owner's ERC20 token balance in base units.
*/
public async getBalanceAsync(
tokenAddress: string,
ownerAddress: string,
methodOpts?: MethodOpts,
): Promise<BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
private _tokenContractsByAddress: { [address: string]: TokenContract };
private _tokenTransferProxyWrapper: TokenTransferProxyWrapper;
constructor(
web3Wrapper: Web3Wrapper,
networkId: number,
abiDecoder: AbiDecoder,
tokenTransferProxyWrapper: TokenTransferProxyWrapper,
) {
super(web3Wrapper, networkId, abiDecoder);
this._tokenContractsByAddress = {};
this._tokenTransferProxyWrapper = tokenTransferProxyWrapper;
}
/**
* Retrieves an owner's ERC20 token balance.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check.
* @param methodOpts Optional arguments this method accepts.
* @return The owner's ERC20 token balance in base units.
*/
public async getBalanceAsync(
tokenAddress: string,
ownerAddress: string,
methodOpts?: MethodOpts,
): Promise<BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
let balance = await tokenContract.balanceOf.callAsync(ownerAddress, defaultBlock);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
balance = new BigNumber(balance);
return balance;
}
/**
* Sets the spender's allowance to a specified number of baseUnits on behalf of the owner address.
* Equivalent to the ERC20 spec method `approve`.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @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.
* @param amountInBaseUnits The allowance amount you would like to set.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
spenderAddress: string,
amountInBaseUnits: BigNumber,
txOpts: TransactionOpts = {},
): Promise<string> {
await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
assert.isETHAddressHex('spenderAddress', spenderAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
let balance = await tokenContract.balanceOf.callAsync(ownerAddress, defaultBlock);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
balance = new BigNumber(balance);
return balance;
}
/**
* Sets the spender's allowance to a specified number of baseUnits on behalf of the owner address.
* Equivalent to the ERC20 spec method `approve`.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @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.
* @param amountInBaseUnits The allowance amount you would like to set.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
spenderAddress: string,
amountInBaseUnits: BigNumber,
txOpts: TransactionOpts = {},
): Promise<string> {
await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
assert.isETHAddressHex('spenderAddress', spenderAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, {
from: ownerAddress,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
});
return txHash;
}
/**
* Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address.
* Equivalent to the ERC20 spec method `approve`.
* Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating
* allowances set to the max amount (e.g ZRX, WETH)
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @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.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setUnlimitedAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
spenderAddress: string,
txOpts: TransactionOpts = {},
): Promise<string> {
const txHash = await this.setAllowanceAsync(
tokenAddress,
ownerAddress,
spenderAddress,
this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
txOpts,
);
return txHash;
}
/**
* Retrieves the owners allowance in baseUnits set to the spender's address.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address whose allowance to spenderAddress
* you would like to retrieve.
* @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching.
* @param methodOpts Optional arguments this method accepts.
*/
public async getAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
spenderAddress: string,
methodOpts?: MethodOpts,
): Promise<BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, {
from: ownerAddress,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
});
return txHash;
}
/**
* Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address.
* Equivalent to the ERC20 spec method `approve`.
* Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating
* allowances set to the max amount (e.g ZRX, WETH)
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @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.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setUnlimitedAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
spenderAddress: string,
txOpts: TransactionOpts = {},
): Promise<string> {
const txHash = await this.setAllowanceAsync(
tokenAddress,
ownerAddress,
spenderAddress,
this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
txOpts,
);
return txHash;
}
/**
* Retrieves the owners allowance in baseUnits set to the spender's address.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param ownerAddress The hex encoded user Ethereum address whose allowance to spenderAddress
* you would like to retrieve.
* @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching.
* @param methodOpts Optional arguments this method accepts.
*/
public async getAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
spenderAddress: string,
methodOpts?: MethodOpts,
): Promise<BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
let allowanceInBaseUnits = await tokenContract.allowance.callAsync(ownerAddress, spenderAddress, defaultBlock);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits);
return allowanceInBaseUnits;
}
/**
* Retrieves the owner's allowance in baseUnits set to the 0x proxy contract.
* @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.
* @param methodOpts Optional arguments this method accepts.
*/
public async getProxyAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
methodOpts?: MethodOpts,
): Promise<BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
let allowanceInBaseUnits = await tokenContract.allowance.callAsync(ownerAddress, spenderAddress, defaultBlock);
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits);
return allowanceInBaseUnits;
}
/**
* Retrieves the owner's allowance in baseUnits set to the 0x proxy contract.
* @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.
* @param methodOpts Optional arguments this method accepts.
*/
public async getProxyAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
methodOpts?: MethodOpts,
): Promise<BigNumber> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress();
const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts);
return allowanceInBaseUnits;
}
/**
* Sets the 0x proxy contract's allowance to a specified number of a tokens' baseUnits on behalf
* of an owner address.
* @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.
* @param amountInBaseUnits The allowance amount specified in baseUnits.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setProxyAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
amountInBaseUnits: BigNumber,
txOpts: TransactionOpts = {},
): Promise<string> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress();
const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts);
return allowanceInBaseUnits;
}
/**
* Sets the 0x proxy contract's allowance to a specified number of a tokens' baseUnits on behalf
* of an owner address.
* @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.
* @param amountInBaseUnits The allowance amount specified in baseUnits.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setProxyAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
amountInBaseUnits: BigNumber,
txOpts: TransactionOpts = {},
): Promise<string> {
assert.isETHAddressHex('ownerAddress', ownerAddress);
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress();
const txHash = await this.setAllowanceAsync(
tokenAddress,
ownerAddress,
proxyAddress,
amountInBaseUnits,
txOpts,
);
return txHash;
}
/**
* Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf
* of an owner address.
* Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating
* allowances set to the max amount (e.g ZRX, WETH)
* @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.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setUnlimitedProxyAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
txOpts: TransactionOpts = {},
): Promise<string> {
const txHash = await this.setProxyAllowanceAsync(
tokenAddress,
ownerAddress,
this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
txOpts,
);
return txHash;
}
/**
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @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.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async transferAsync(
tokenAddress: string,
fromAddress: string,
toAddress: string,
amountInBaseUnits: BigNumber,
txOpts: TransactionOpts = {},
): Promise<string> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper);
assert.isETHAddressHex('toAddress', toAddress);
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
const proxyAddress = this._tokenTransferProxyWrapper.getContractAddress();
const txHash = await this.setAllowanceAsync(
tokenAddress,
ownerAddress,
proxyAddress,
amountInBaseUnits,
txOpts,
);
return txHash;
}
/**
* Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf
* of an owner address.
* Setting an unlimited allowance will lower the gas cost for filling orders involving tokens that forego updating
* allowances set to the max amount (e.g ZRX, WETH)
* @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.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async setUnlimitedProxyAllowanceAsync(
tokenAddress: string,
ownerAddress: string,
txOpts: TransactionOpts = {},
): Promise<string> {
const txHash = await this.setProxyAllowanceAsync(
tokenAddress,
ownerAddress,
this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
txOpts,
);
return txHash;
}
/**
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @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.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async transferAsync(
tokenAddress: string,
fromAddress: string,
toAddress: string,
amountInBaseUnits: BigNumber,
txOpts: TransactionOpts = {},
): Promise<string> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper);
assert.isETHAddressHex('toAddress', toAddress);
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress);
if (fromAddressBalance.lessThan(amountInBaseUnits)) {
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
}
const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress);
if (fromAddressBalance.lessThan(amountInBaseUnits)) {
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
}
const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, {
from: fromAddress,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
});
return txHash;
}
/**
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
* Requires the fromAddress to have sufficient funds and to have approved an allowance of
* `amountInBaseUnits` to `senderAddress`.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param fromAddress The hex encoded user Ethereum address whose funds are being sent.
* @param toAddress The hex encoded user Ethereum address that will receive the funds.
* @param senderAddress The hex encoded user Ethereum address whose initiates the fund transfer. The
* `fromAddress` must have set an allowance to the `senderAddress`
* before this call.
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async transferFromAsync(
tokenAddress: string,
fromAddress: string,
toAddress: string,
senderAddress: string,
amountInBaseUnits: BigNumber,
txOpts: TransactionOpts = {},
): Promise<string> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isETHAddressHex('fromAddress', fromAddress);
assert.isETHAddressHex('toAddress', toAddress);
await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, {
from: fromAddress,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
});
return txHash;
}
/**
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
* Requires the fromAddress to have sufficient funds and to have approved an allowance of
* `amountInBaseUnits` to `senderAddress`.
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
* @param fromAddress The hex encoded user Ethereum address whose funds are being sent.
* @param toAddress The hex encoded user Ethereum address that will receive the funds.
* @param senderAddress The hex encoded user Ethereum address whose initiates the fund transfer. The
* `fromAddress` must have set an allowance to the `senderAddress`
* before this call.
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
* @param txOpts Transaction parameters.
* @return Transaction hash.
*/
public async transferFromAsync(
tokenAddress: string,
fromAddress: string,
toAddress: string,
senderAddress: string,
amountInBaseUnits: BigNumber,
txOpts: TransactionOpts = {},
): Promise<string> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isETHAddressHex('fromAddress', fromAddress);
assert.isETHAddressHex('toAddress', toAddress);
await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
const tokenContract = await this._getTokenContractAsync(tokenAddress);
const fromAddressAllowance = await this.getAllowanceAsync(tokenAddress, fromAddress, senderAddress);
if (fromAddressAllowance.lessThan(amountInBaseUnits)) {
throw new Error(ZeroExError.InsufficientAllowanceForTransfer);
}
const fromAddressAllowance = await this.getAllowanceAsync(tokenAddress, fromAddress, senderAddress);
if (fromAddressAllowance.lessThan(amountInBaseUnits)) {
throw new Error(ZeroExError.InsufficientAllowanceForTransfer);
}
const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress);
if (fromAddressBalance.lessThan(amountInBaseUnits)) {
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
}
const fromAddressBalance = await this.getBalanceAsync(tokenAddress, fromAddress);
if (fromAddressBalance.lessThan(amountInBaseUnits)) {
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
}
const txHash = await tokenContract.transferFrom.sendTransactionAsync(
fromAddress,
toAddress,
amountInBaseUnits,
{
from: senderAddress,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
},
);
return txHash;
}
/**
* Subscribe to an event type emitted by the Token contract.
* @param tokenAddress The hex encoded address where the ERC20 token is deployed.
* @param eventName The token contract event you would like to subscribe to.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
* @param callback Callback that gets called when a log is added/removed
* @return Subscription token used later to unsubscribe
*/
public subscribe<ArgsType extends TokenContractEventArgs>(
tokenAddress: string,
eventName: TokenEvents,
indexFilterValues: IndexedFilterValues,
callback: EventCallback<ArgsType>,
): string {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
assert.isFunction('callback', callback);
const subscriptionToken = this._subscribe<ArgsType>(
tokenAddress,
eventName,
indexFilterValues,
artifacts.TokenArtifact.abi,
callback,
);
return subscriptionToken;
}
/**
* Cancel a subscription
* @param subscriptionToken Subscription token returned by `subscribe()`
*/
public unsubscribe(subscriptionToken: string): void {
this._unsubscribe(subscriptionToken);
}
/**
* Cancels all existing subscriptions
*/
public unsubscribeAll(): void {
super.unsubscribeAll();
}
/**
* Gets historical logs without creating a subscription
* @param tokenAddress An address of the token that emmited the logs.
* @param eventName The token contract event you would like to subscribe to.
* @param blockRange Block range to get logs from.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
* @return Array of logs that match the parameters
*/
public async getLogsAsync<ArgsType extends TokenContractEventArgs>(
tokenAddress: string,
eventName: TokenEvents,
blockRange: BlockRange,
indexFilterValues: IndexedFilterValues,
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
const logs = await this._getLogsAsync<ArgsType>(
tokenAddress,
eventName,
blockRange,
indexFilterValues,
artifacts.TokenArtifact.abi,
);
return logs;
}
private _invalidateContractInstances(): void {
this.unsubscribeAll();
this._tokenContractsByAddress = {};
}
private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> {
let tokenContract = this._tokenContractsByAddress[tokenAddress];
if (!_.isUndefined(tokenContract)) {
return tokenContract;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
artifacts.TokenArtifact,
tokenAddress,
);
const contractInstance = new TokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults());
tokenContract = contractInstance;
this._tokenContractsByAddress[tokenAddress] = tokenContract;
return tokenContract;
}
const txHash = await tokenContract.transferFrom.sendTransactionAsync(
fromAddress,
toAddress,
amountInBaseUnits,
{
from: senderAddress,
gas: txOpts.gasLimit,
gasPrice: txOpts.gasPrice,
},
);
return txHash;
}
/**
* Subscribe to an event type emitted by the Token contract.
* @param tokenAddress The hex encoded address where the ERC20 token is deployed.
* @param eventName The token contract event you would like to subscribe to.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
* @param callback Callback that gets called when a log is added/removed
* @return Subscription token used later to unsubscribe
*/
public subscribe<ArgsType extends TokenContractEventArgs>(
tokenAddress: string,
eventName: TokenEvents,
indexFilterValues: IndexedFilterValues,
callback: EventCallback<ArgsType>,
): string {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
assert.isFunction('callback', callback);
const subscriptionToken = this._subscribe<ArgsType>(
tokenAddress,
eventName,
indexFilterValues,
artifacts.TokenArtifact.abi,
callback,
);
return subscriptionToken;
}
/**
* Cancel a subscription
* @param subscriptionToken Subscription token returned by `subscribe()`
*/
public unsubscribe(subscriptionToken: string): void {
this._unsubscribe(subscriptionToken);
}
/**
* Cancels all existing subscriptions
*/
public unsubscribeAll(): void {
super.unsubscribeAll();
}
/**
* Gets historical logs without creating a subscription
* @param tokenAddress An address of the token that emmited the logs.
* @param eventName The token contract event you would like to subscribe to.
* @param blockRange Block range to get logs from.
* @param indexFilterValues An object where the keys are indexed args returned by the event and
* the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
* @return Array of logs that match the parameters
*/
public async getLogsAsync<ArgsType extends TokenContractEventArgs>(
tokenAddress: string,
eventName: TokenEvents,
blockRange: BlockRange,
indexFilterValues: IndexedFilterValues,
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
assert.doesConformToSchema('blockRange', blockRange, schemas.blockRangeSchema);
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
const logs = await this._getLogsAsync<ArgsType>(
tokenAddress,
eventName,
blockRange,
indexFilterValues,
artifacts.TokenArtifact.abi,
);
return logs;
}
private _invalidateContractInstances(): void {
this.unsubscribeAll();
this._tokenContractsByAddress = {};
}
private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> {
let tokenContract = this._tokenContractsByAddress[tokenAddress];
if (!_.isUndefined(tokenContract)) {
return tokenContract;
}
const web3ContractInstance = await this._instantiateContractIfExistsAsync(
artifacts.TokenArtifact,
tokenAddress,
);
const contractInstance = new TokenContract(web3ContractInstance, this._web3Wrapper.getContractDefaults());
tokenContract = contractInstance;
this._tokenContractsByAddress[tokenAddress] = tokenContract;
return tokenContract;
}
}

View File

@@ -10,50 +10,50 @@ declare module 'web3-provider-engine/subproviders/rpc';
// disallow `namespace`, we disable tslint for the following.
/* tslint:disable */
declare namespace Chai {
interface Assertion {
bignumber: Assertion;
// HACK: In order to comply with chai-as-promised we make eventually a `PromisedAssertion` not an `Assertion`
eventually: PromisedAssertion;
}
interface Assertion {
bignumber: Assertion;
// HACK: In order to comply with chai-as-promised we make eventually a `PromisedAssertion` not an `Assertion`
eventually: PromisedAssertion;
}
}
/* tslint:enable */
declare module '*.json' {
const json: any;
/* tslint:disable */
export default json;
/* tslint:enable */
const json: any;
/* tslint:disable */
export default json;
/* tslint:enable */
}
declare module 'ethereumjs-abi' {
const soliditySHA3: (argTypes: string[], args: any[]) => Buffer;
const soliditySHA3: (argTypes: string[], args: any[]) => Buffer;
}
// truffle-hdwallet-provider declarations
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;
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 {}
interface DecodedLog {
name: string;
events: DecodedLogArg[];
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[];
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[];
const decodeParams: (types: string[], data: string) => any[];
}

View File

@@ -3,21 +3,21 @@ import { BigNumber } from '@0xproject/utils';
// HACK: This module overrides the Chai namespace so that we can use BigNumber types inside.
// Source: https://github.com/Microsoft/TypeScript/issues/7352#issuecomment-191547232
declare global {
// HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion
// interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise
// disallow `namespace`, we disable tslint for the following.
/* tslint:disable */
namespace Chai {
interface NumberComparer {
(value: number | BigNumber, message?: string): Assertion;
}
interface NumericComparison {
greaterThan: NumberComparer;
}
}
/* tslint:enable */
interface DecodedLogArg {
name: string;
value: string | BigNumber;
}
// HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion
// interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise
// disallow `namespace`, we disable tslint for the following.
/* tslint:disable */
namespace Chai {
interface NumberComparer {
(value: number | BigNumber, message?: string): Assertion;
}
interface NumericComparison {
greaterThan: NumberComparer;
}
}
/* tslint:enable */
interface DecodedLogArg {
name: string;
value: string | BigNumber;
}
}

View File

@@ -1,50 +1,50 @@
export { ZeroEx } from './0x';
export {
Order,
BlockParamLiteral,
SignedOrder,
ECSignature,
ZeroExError,
EventCallback,
ExchangeContractErrs,
ContractEvent,
Token,
ExchangeEvents,
TokenEvents,
IndexedFilterValues,
BlockRange,
BlockParam,
OrderCancellationRequest,
OrderFillRequest,
LogErrorContractEventArgs,
LogCancelContractEventArgs,
LogFillContractEventArgs,
ExchangeContractEventArgs,
TransferContractEventArgs,
ApprovalContractEventArgs,
TokenContractEventArgs,
EtherTokenContractEventArgs,
WithdrawalContractEventArgs,
DepositContractEventArgs,
ContractEventArgs,
ContractEventArg,
Web3Provider,
ZeroExConfig,
EtherTokenEvents,
TransactionReceiptWithDecodedLogs,
LogWithDecodedArgs,
MethodOpts,
OrderTransactionOpts,
TransactionOpts,
FilterObject,
LogEvent,
DecodedLogEvent,
EventWatcherCallback,
OnOrderStateChangeCallback,
OrderStateValid,
OrderStateInvalid,
OrderState,
Order,
BlockParamLiteral,
SignedOrder,
ECSignature,
ZeroExError,
EventCallback,
ExchangeContractErrs,
ContractEvent,
Token,
ExchangeEvents,
TokenEvents,
IndexedFilterValues,
BlockRange,
BlockParam,
OrderCancellationRequest,
OrderFillRequest,
LogErrorContractEventArgs,
LogCancelContractEventArgs,
LogFillContractEventArgs,
ExchangeContractEventArgs,
TransferContractEventArgs,
ApprovalContractEventArgs,
TokenContractEventArgs,
EtherTokenContractEventArgs,
WithdrawalContractEventArgs,
DepositContractEventArgs,
ContractEventArgs,
ContractEventArg,
Web3Provider,
ZeroExConfig,
EtherTokenEvents,
TransactionReceiptWithDecodedLogs,
LogWithDecodedArgs,
MethodOpts,
OrderTransactionOpts,
TransactionOpts,
FilterObject,
LogEvent,
DecodedLogEvent,
EventWatcherCallback,
OnOrderStateChangeCallback,
OrderStateValid,
OrderStateInvalid,
OrderState,
} from './types';
export { TransactionReceipt } from '@0xproject/types';

View File

@@ -9,8 +9,8 @@ import { assert } from '../utils/assert';
const DEFAULT_EVENT_POLLING_INTERVAL_MS = 200;
enum LogEventState {
Removed,
Added,
Removed,
Added,
}
/*
@@ -18,76 +18,76 @@ enum LogEventState {
* depth.
*/
export class EventWatcher {
private _web3Wrapper: Web3Wrapper;
private _pollingIntervalMs: number;
private _intervalIdIfExists?: NodeJS.Timer;
private _lastEvents: Web3.LogEntry[] = [];
constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number) {
this._web3Wrapper = web3Wrapper;
this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs)
? DEFAULT_EVENT_POLLING_INTERVAL_MS
: pollingIntervalIfExistsMs;
}
public subscribe(callback: EventWatcherCallback): void {
assert.isFunction('callback', callback);
if (!_.isUndefined(this._intervalIdIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
}
this._intervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
this._pollForBlockchainEventsAsync.bind(this, callback),
this._pollingIntervalMs,
(err: Error) => {
this.unsubscribe();
callback(err);
},
);
}
public unsubscribe(): void {
this._lastEvents = [];
if (!_.isUndefined(this._intervalIdIfExists)) {
intervalUtils.clearAsyncExcludingInterval(this._intervalIdIfExists);
delete this._intervalIdIfExists;
}
}
private async _pollForBlockchainEventsAsync(callback: EventWatcherCallback): Promise<void> {
const pendingEvents = await this._getEventsAsync();
if (_.isUndefined(pendingEvents)) {
// HACK: This should never happen, but happens frequently on CI due to a ganache bug
return;
}
if (pendingEvents.length === 0) {
// HACK: Sometimes when node rebuilds the pending block we get back the empty result.
// We don't want to emit a lot of removal events and bring them back after a couple of miliseconds,
// that's why we just ignore those cases.
return;
}
const removedEvents = _.differenceBy(this._lastEvents, pendingEvents, JSON.stringify);
const newEvents = _.differenceBy(pendingEvents, this._lastEvents, JSON.stringify);
await this._emitDifferencesAsync(removedEvents, LogEventState.Removed, callback);
await this._emitDifferencesAsync(newEvents, LogEventState.Added, callback);
this._lastEvents = pendingEvents;
}
private async _getEventsAsync(): Promise<Web3.LogEntry[]> {
const eventFilter = {
fromBlock: BlockParamLiteral.Pending,
toBlock: BlockParamLiteral.Pending,
};
const events = await this._web3Wrapper.getLogsAsync(eventFilter);
return events;
}
private async _emitDifferencesAsync(
logs: Web3.LogEntry[],
logEventState: LogEventState,
callback: EventWatcherCallback,
): Promise<void> {
for (const log of logs) {
const logEvent = {
removed: logEventState === LogEventState.Removed,
...log,
};
if (!_.isUndefined(this._intervalIdIfExists)) {
callback(null, logEvent);
}
}
}
private _web3Wrapper: Web3Wrapper;
private _pollingIntervalMs: number;
private _intervalIdIfExists?: NodeJS.Timer;
private _lastEvents: Web3.LogEntry[] = [];
constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number) {
this._web3Wrapper = web3Wrapper;
this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs)
? DEFAULT_EVENT_POLLING_INTERVAL_MS
: pollingIntervalIfExistsMs;
}
public subscribe(callback: EventWatcherCallback): void {
assert.isFunction('callback', callback);
if (!_.isUndefined(this._intervalIdIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
}
this._intervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
this._pollForBlockchainEventsAsync.bind(this, callback),
this._pollingIntervalMs,
(err: Error) => {
this.unsubscribe();
callback(err);
},
);
}
public unsubscribe(): void {
this._lastEvents = [];
if (!_.isUndefined(this._intervalIdIfExists)) {
intervalUtils.clearAsyncExcludingInterval(this._intervalIdIfExists);
delete this._intervalIdIfExists;
}
}
private async _pollForBlockchainEventsAsync(callback: EventWatcherCallback): Promise<void> {
const pendingEvents = await this._getEventsAsync();
if (_.isUndefined(pendingEvents)) {
// HACK: This should never happen, but happens frequently on CI due to a ganache bug
return;
}
if (pendingEvents.length === 0) {
// HACK: Sometimes when node rebuilds the pending block we get back the empty result.
// We don't want to emit a lot of removal events and bring them back after a couple of miliseconds,
// that's why we just ignore those cases.
return;
}
const removedEvents = _.differenceBy(this._lastEvents, pendingEvents, JSON.stringify);
const newEvents = _.differenceBy(pendingEvents, this._lastEvents, JSON.stringify);
await this._emitDifferencesAsync(removedEvents, LogEventState.Removed, callback);
await this._emitDifferencesAsync(newEvents, LogEventState.Added, callback);
this._lastEvents = pendingEvents;
}
private async _getEventsAsync(): Promise<Web3.LogEntry[]> {
const eventFilter = {
fromBlock: BlockParamLiteral.Pending,
toBlock: BlockParamLiteral.Pending,
};
const events = await this._web3Wrapper.getLogsAsync(eventFilter);
return events;
}
private async _emitDifferencesAsync(
logs: Web3.LogEntry[],
logEventState: LogEventState,
callback: EventWatcherCallback,
): Promise<void> {
for (const log of logs) {
const logEvent = {
removed: logEventState === LogEventState.Removed,
...log,
};
if (!_.isUndefined(this._intervalIdIfExists)) {
callback(null, logEvent);
}
}
}
}

View File

@@ -13,63 +13,63 @@ const DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS = 50;
* It stores them in a min heap by expiration time and checks for expired ones every `orderExpirationCheckingIntervalMs`
*/
export class ExpirationWatcher {
private _orderHashByExpirationRBTree: RBTree<string>;
private _expiration: { [orderHash: string]: BigNumber } = {};
private _orderExpirationCheckingIntervalMs: number;
private _expirationMarginMs: number;
private _orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer;
constructor(expirationMarginIfExistsMs?: number, orderExpirationCheckingIntervalIfExistsMs?: number) {
this._expirationMarginMs = expirationMarginIfExistsMs || DEFAULT_EXPIRATION_MARGIN_MS;
this._orderExpirationCheckingIntervalMs =
expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS;
const scoreFunction = (orderHash: string) => this._expiration[orderHash].toNumber();
const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs);
this._orderHashByExpirationRBTree = new RBTree(comparator);
}
public subscribe(callback: (orderHash: string) => void): void {
if (!_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
}
this._orderExpirationCheckingIntervalIdIfExists = intervalUtils.setInterval(
this._pruneExpiredOrders.bind(this, callback),
this._orderExpirationCheckingIntervalMs,
_.noop, // _pruneExpiredOrders never throws
);
}
public unsubscribe(): void {
if (_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) {
throw new Error(ZeroExError.SubscriptionNotFound);
}
intervalUtils.clearInterval(this._orderExpirationCheckingIntervalIdIfExists);
delete this._orderExpirationCheckingIntervalIdIfExists;
}
public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void {
this._expiration[orderHash] = expirationUnixTimestampMs;
this._orderHashByExpirationRBTree.insert(orderHash);
}
public removeOrder(orderHash: string): void {
this._orderHashByExpirationRBTree.remove(orderHash);
delete this._expiration[orderHash];
}
private _pruneExpiredOrders(callback: (orderHash: string) => void): void {
const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs();
while (true) {
const hasTrakedOrders = this._orderHashByExpirationRBTree.size === 0;
if (hasTrakedOrders) {
break;
}
const nextOrderHashToExpire = this._orderHashByExpirationRBTree.min();
const hasNoExpiredOrders = this._expiration[nextOrderHashToExpire].greaterThan(
currentUnixTimestampMs.plus(this._expirationMarginMs),
);
const isSubscriptionActive = _.isUndefined(this._orderExpirationCheckingIntervalIdIfExists);
if (hasNoExpiredOrders || isSubscriptionActive) {
break;
}
const orderHash = this._orderHashByExpirationRBTree.min();
this._orderHashByExpirationRBTree.remove(orderHash);
delete this._expiration[orderHash];
callback(orderHash);
}
}
private _orderHashByExpirationRBTree: RBTree<string>;
private _expiration: { [orderHash: string]: BigNumber } = {};
private _orderExpirationCheckingIntervalMs: number;
private _expirationMarginMs: number;
private _orderExpirationCheckingIntervalIdIfExists?: NodeJS.Timer;
constructor(expirationMarginIfExistsMs?: number, orderExpirationCheckingIntervalIfExistsMs?: number) {
this._expirationMarginMs = expirationMarginIfExistsMs || DEFAULT_EXPIRATION_MARGIN_MS;
this._orderExpirationCheckingIntervalMs =
expirationMarginIfExistsMs || DEFAULT_ORDER_EXPIRATION_CHECKING_INTERVAL_MS;
const scoreFunction = (orderHash: string) => this._expiration[orderHash].toNumber();
const comparator = (lhs: string, rhs: string) => scoreFunction(lhs) - scoreFunction(rhs);
this._orderHashByExpirationRBTree = new RBTree(comparator);
}
public subscribe(callback: (orderHash: string) => void): void {
if (!_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
}
this._orderExpirationCheckingIntervalIdIfExists = intervalUtils.setInterval(
this._pruneExpiredOrders.bind(this, callback),
this._orderExpirationCheckingIntervalMs,
_.noop, // _pruneExpiredOrders never throws
);
}
public unsubscribe(): void {
if (_.isUndefined(this._orderExpirationCheckingIntervalIdIfExists)) {
throw new Error(ZeroExError.SubscriptionNotFound);
}
intervalUtils.clearInterval(this._orderExpirationCheckingIntervalIdIfExists);
delete this._orderExpirationCheckingIntervalIdIfExists;
}
public addOrder(orderHash: string, expirationUnixTimestampMs: BigNumber): void {
this._expiration[orderHash] = expirationUnixTimestampMs;
this._orderHashByExpirationRBTree.insert(orderHash);
}
public removeOrder(orderHash: string): void {
this._orderHashByExpirationRBTree.remove(orderHash);
delete this._expiration[orderHash];
}
private _pruneExpiredOrders(callback: (orderHash: string) => void): void {
const currentUnixTimestampMs = utils.getCurrentUnixTimestampMs();
while (true) {
const hasTrakedOrders = this._orderHashByExpirationRBTree.size === 0;
if (hasTrakedOrders) {
break;
}
const nextOrderHashToExpire = this._orderHashByExpirationRBTree.min();
const hasNoExpiredOrders = this._expiration[nextOrderHashToExpire].greaterThan(
currentUnixTimestampMs.plus(this._expirationMarginMs),
);
const isSubscriptionActive = _.isUndefined(this._orderExpirationCheckingIntervalIdIfExists);
if (hasNoExpiredOrders || isSubscriptionActive) {
break;
}
const orderHash = this._orderHashByExpirationRBTree.min();
this._orderHashByExpirationRBTree.remove(orderHash);
delete this._expiration[orderHash];
callback(orderHash);
}
}
}

View File

@@ -9,25 +9,25 @@ import { TokenWrapper } from '../contract_wrappers/token_wrapper';
import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store';
import { OrderFilledCancelledLazyStore } from '../stores/order_filled_cancelled_lazy_store';
import {
ApprovalContractEventArgs,
BlockParamLiteral,
ContractEventArgs,
DepositContractEventArgs,
EtherTokenEvents,
ExchangeContractErrs,
ExchangeEvents,
LogCancelContractEventArgs,
LogEvent,
LogFillContractEventArgs,
LogWithDecodedArgs,
OnOrderStateChangeCallback,
OrderState,
OrderStateWatcherConfig,
SignedOrder,
TokenEvents,
TransferContractEventArgs,
WithdrawalContractEventArgs,
ZeroExError,
ApprovalContractEventArgs,
BlockParamLiteral,
ContractEventArgs,
DepositContractEventArgs,
EtherTokenEvents,
ExchangeContractErrs,
ExchangeEvents,
LogCancelContractEventArgs,
LogEvent,
LogFillContractEventArgs,
LogWithDecodedArgs,
OnOrderStateChangeCallback,
OrderState,
OrderStateWatcherConfig,
SignedOrder,
TokenEvents,
TransferContractEventArgs,
WithdrawalContractEventArgs,
ZeroExError,
} from '../types';
import { AbiDecoder } from '../utils/abi_decoder';
import { assert } from '../utils/assert';
@@ -38,17 +38,17 @@ import { EventWatcher } from './event_watcher';
import { ExpirationWatcher } from './expiration_watcher';
interface DependentOrderHashes {
[makerAddress: string]: {
[makerToken: string]: Set<string>;
};
[makerAddress: string]: {
[makerToken: string]: Set<string>;
};
}
interface OrderByOrderHash {
[orderHash: string]: SignedOrder;
[orderHash: string]: SignedOrder;
}
interface OrderStateByOrderHash {
[orderHash: string]: OrderState;
[orderHash: string]: OrderState;
}
const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h
@@ -60,319 +60,319 @@ const DEFAULT_CLEANUP_JOB_INTERVAL_MS = 1000 * 60 * 60; // 1h
* the order should be deemed invalid.
*/
export class OrderStateWatcher {
private _orderStateByOrderHashCache: OrderStateByOrderHash = {};
private _orderByOrderHash: OrderByOrderHash = {};
private _dependentOrderHashes: DependentOrderHashes = {};
private _callbackIfExists?: OnOrderStateChangeCallback;
private _eventWatcher: EventWatcher;
private _web3Wrapper: Web3Wrapper;
private _abiDecoder: AbiDecoder;
private _expirationWatcher: ExpirationWatcher;
private _orderStateUtils: OrderStateUtils;
private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
private _cleanupJobInterval: number;
private _cleanupJobIntervalIdIfExists?: NodeJS.Timer;
constructor(
web3Wrapper: Web3Wrapper,
abiDecoder: AbiDecoder,
token: TokenWrapper,
exchange: ExchangeWrapper,
config?: OrderStateWatcherConfig,
) {
this._abiDecoder = abiDecoder;
this._web3Wrapper = web3Wrapper;
const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs;
this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs);
this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(
token,
BlockParamLiteral.Pending,
);
this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange);
this._orderStateUtils = new OrderStateUtils(
this._balanceAndProxyAllowanceLazyStore,
this._orderFilledCancelledLazyStore,
);
const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config)
? undefined
: config.orderExpirationCheckingIntervalMs;
const expirationMarginIfExistsMs = _.isUndefined(config) ? undefined : config.expirationMarginMs;
this._expirationWatcher = new ExpirationWatcher(
expirationMarginIfExistsMs,
orderExpirationCheckingIntervalMsIfExists,
);
this._cleanupJobInterval =
_.isUndefined(config) || _.isUndefined(config.cleanupJobIntervalMs)
? DEFAULT_CLEANUP_JOB_INTERVAL_MS
: config.cleanupJobIntervalMs;
}
/**
* Add an order to the orderStateWatcher. Before the order is added, it's
* signature is verified.
* @param signedOrder The order you wish to start watching.
*/
public addOrder(signedOrder: SignedOrder): void {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker);
this._orderByOrderHash[orderHash] = signedOrder;
this._addToDependentOrderHashes(signedOrder, orderHash);
const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000);
this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs);
}
/**
* Removes an order from the orderStateWatcher
* @param orderHash The orderHash of the order you wish to stop watching.
*/
public removeOrder(orderHash: string): void {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const signedOrder = this._orderByOrderHash[orderHash];
if (_.isUndefined(signedOrder)) {
return; // noop
}
delete this._orderByOrderHash[orderHash];
delete this._orderStateByOrderHashCache[orderHash];
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
const zrxTokenAddress = exchange.getZRXTokenAddress();
this._removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash);
this._removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash);
this._expirationWatcher.removeOrder(orderHash);
}
/**
* Starts an orderStateWatcher subscription. The callback will be called every time a watched order's
* backing blockchain state has changed. This is a call-to-action for the caller to re-validate the order.
* @param callback Receives the orderHash of the order that should be re-validated, together
* with all the order-relevant blockchain state needed to re-validate the order.
*/
public subscribe(callback: OnOrderStateChangeCallback): void {
assert.isFunction('callback', callback);
if (!_.isUndefined(this._callbackIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
}
this._callbackIfExists = callback;
this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this));
this._expirationWatcher.subscribe(this._onOrderExpired.bind(this));
this._cleanupJobIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
this._cleanupAsync.bind(this),
this._cleanupJobInterval,
(err: Error) => {
this.unsubscribe();
callback(err);
},
);
}
/**
* Ends an orderStateWatcher subscription.
*/
public unsubscribe(): void {
if (_.isUndefined(this._callbackIfExists) || _.isUndefined(this._cleanupJobIntervalIdIfExists)) {
throw new Error(ZeroExError.SubscriptionNotFound);
}
this._balanceAndProxyAllowanceLazyStore.deleteAll();
this._orderFilledCancelledLazyStore.deleteAll();
delete this._callbackIfExists;
this._eventWatcher.unsubscribe();
this._expirationWatcher.unsubscribe();
intervalUtils.clearAsyncExcludingInterval(this._cleanupJobIntervalIdIfExists);
}
private async _cleanupAsync(): Promise<void> {
for (const orderHash of _.keys(this._orderByOrderHash)) {
this._cleanupOrderRelatedState(orderHash);
await this._emitRevalidateOrdersAsync([orderHash]);
}
}
private _cleanupOrderRelatedState(orderHash: string): void {
const signedOrder = this._orderByOrderHash[orderHash];
private _orderStateByOrderHashCache: OrderStateByOrderHash = {};
private _orderByOrderHash: OrderByOrderHash = {};
private _dependentOrderHashes: DependentOrderHashes = {};
private _callbackIfExists?: OnOrderStateChangeCallback;
private _eventWatcher: EventWatcher;
private _web3Wrapper: Web3Wrapper;
private _abiDecoder: AbiDecoder;
private _expirationWatcher: ExpirationWatcher;
private _orderStateUtils: OrderStateUtils;
private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
private _cleanupJobInterval: number;
private _cleanupJobIntervalIdIfExists?: NodeJS.Timer;
constructor(
web3Wrapper: Web3Wrapper,
abiDecoder: AbiDecoder,
token: TokenWrapper,
exchange: ExchangeWrapper,
config?: OrderStateWatcherConfig,
) {
this._abiDecoder = abiDecoder;
this._web3Wrapper = web3Wrapper;
const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs;
this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs);
this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(
token,
BlockParamLiteral.Pending,
);
this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange);
this._orderStateUtils = new OrderStateUtils(
this._balanceAndProxyAllowanceLazyStore,
this._orderFilledCancelledLazyStore,
);
const orderExpirationCheckingIntervalMsIfExists = _.isUndefined(config)
? undefined
: config.orderExpirationCheckingIntervalMs;
const expirationMarginIfExistsMs = _.isUndefined(config) ? undefined : config.expirationMarginMs;
this._expirationWatcher = new ExpirationWatcher(
expirationMarginIfExistsMs,
orderExpirationCheckingIntervalMsIfExists,
);
this._cleanupJobInterval =
_.isUndefined(config) || _.isUndefined(config.cleanupJobIntervalMs)
? DEFAULT_CLEANUP_JOB_INTERVAL_MS
: config.cleanupJobIntervalMs;
}
/**
* Add an order to the orderStateWatcher. Before the order is added, it's
* signature is verified.
* @param signedOrder The order you wish to start watching.
*/
public addOrder(signedOrder: SignedOrder): void {
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker);
this._orderByOrderHash[orderHash] = signedOrder;
this._addToDependentOrderHashes(signedOrder, orderHash);
const expirationUnixTimestampMs = signedOrder.expirationUnixTimestampSec.times(1000);
this._expirationWatcher.addOrder(orderHash, expirationUnixTimestampMs);
}
/**
* Removes an order from the orderStateWatcher
* @param orderHash The orderHash of the order you wish to stop watching.
*/
public removeOrder(orderHash: string): void {
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
const signedOrder = this._orderByOrderHash[orderHash];
if (_.isUndefined(signedOrder)) {
return; // noop
}
delete this._orderByOrderHash[orderHash];
delete this._orderStateByOrderHashCache[orderHash];
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
const zrxTokenAddress = exchange.getZRXTokenAddress();
this._removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash);
this._removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash);
this._expirationWatcher.removeOrder(orderHash);
}
/**
* Starts an orderStateWatcher subscription. The callback will be called every time a watched order's
* backing blockchain state has changed. This is a call-to-action for the caller to re-validate the order.
* @param callback Receives the orderHash of the order that should be re-validated, together
* with all the order-relevant blockchain state needed to re-validate the order.
*/
public subscribe(callback: OnOrderStateChangeCallback): void {
assert.isFunction('callback', callback);
if (!_.isUndefined(this._callbackIfExists)) {
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
}
this._callbackIfExists = callback;
this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this));
this._expirationWatcher.subscribe(this._onOrderExpired.bind(this));
this._cleanupJobIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
this._cleanupAsync.bind(this),
this._cleanupJobInterval,
(err: Error) => {
this.unsubscribe();
callback(err);
},
);
}
/**
* Ends an orderStateWatcher subscription.
*/
public unsubscribe(): void {
if (_.isUndefined(this._callbackIfExists) || _.isUndefined(this._cleanupJobIntervalIdIfExists)) {
throw new Error(ZeroExError.SubscriptionNotFound);
}
this._balanceAndProxyAllowanceLazyStore.deleteAll();
this._orderFilledCancelledLazyStore.deleteAll();
delete this._callbackIfExists;
this._eventWatcher.unsubscribe();
this._expirationWatcher.unsubscribe();
intervalUtils.clearAsyncExcludingInterval(this._cleanupJobIntervalIdIfExists);
}
private async _cleanupAsync(): Promise<void> {
for (const orderHash of _.keys(this._orderByOrderHash)) {
this._cleanupOrderRelatedState(orderHash);
await this._emitRevalidateOrdersAsync([orderHash]);
}
}
private _cleanupOrderRelatedState(orderHash: string): void {
const signedOrder = this._orderByOrderHash[orderHash];
this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash);
this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(orderHash);
this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(orderHash);
this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(orderHash);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.makerTokenAddress, signedOrder.maker);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.makerTokenAddress, signedOrder.maker);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.takerTokenAddress, signedOrder.taker);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.takerTokenAddress, signedOrder.taker);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.makerTokenAddress, signedOrder.maker);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.makerTokenAddress, signedOrder.maker);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(signedOrder.takerTokenAddress, signedOrder.taker);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(signedOrder.takerTokenAddress, signedOrder.taker);
const zrxTokenAddress = this._getZRXTokenAddress();
if (!signedOrder.makerFee.isZero()) {
this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.maker);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.maker);
}
if (!signedOrder.takerFee.isZero()) {
this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.taker);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.taker);
}
}
private _onOrderExpired(orderHash: string): void {
const orderState: OrderState = {
isValid: false,
orderHash,
error: ExchangeContractErrs.OrderFillExpired,
};
if (!_.isUndefined(this._orderByOrderHash[orderHash])) {
this.removeOrder(orderHash);
if (!_.isUndefined(this._callbackIfExists)) {
this._callbackIfExists(null, orderState);
}
}
}
private async _onEventWatcherCallbackAsync(err: Error | null, logIfExists?: LogEvent): Promise<void> {
if (!_.isNull(err)) {
if (!_.isUndefined(this._callbackIfExists)) {
this._callbackIfExists(err);
this.unsubscribe();
}
return;
}
const log = logIfExists as LogEvent; // At this moment we are sure that no error occured and log is defined.
const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log);
const isLogDecoded = !_.isUndefined((maybeDecodedLog as LogWithDecodedArgs<any>).event);
if (!isLogDecoded) {
return; // noop
}
const decodedLog = maybeDecodedLog as LogWithDecodedArgs<ContractEventArgs>;
let makerToken: string;
let makerAddress: string;
switch (decodedLog.event) {
case TokenEvents.Approval: {
// Invalidate cache
const args = decodedLog.args as ApprovalContractEventArgs;
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(decodedLog.address, args._owner);
// Revalidate orders
makerToken = decodedLog.address;
makerAddress = args._owner;
if (
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
) {
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
await this._emitRevalidateOrdersAsync(orderHashes);
}
break;
}
case TokenEvents.Transfer: {
// Invalidate cache
const args = decodedLog.args as TransferContractEventArgs;
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._from);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._to);
// Revalidate orders
makerToken = decodedLog.address;
makerAddress = args._from;
if (
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
) {
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
await this._emitRevalidateOrdersAsync(orderHashes);
}
break;
}
case EtherTokenEvents.Deposit: {
// Invalidate cache
const args = decodedLog.args as DepositContractEventArgs;
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner);
// Revalidate orders
makerToken = decodedLog.address;
makerAddress = args._owner;
if (
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
) {
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
await this._emitRevalidateOrdersAsync(orderHashes);
}
break;
}
case EtherTokenEvents.Withdrawal: {
// Invalidate cache
const args = decodedLog.args as WithdrawalContractEventArgs;
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner);
// Revalidate orders
makerToken = decodedLog.address;
makerAddress = args._owner;
if (
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
) {
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
await this._emitRevalidateOrdersAsync(orderHashes);
}
break;
}
case ExchangeEvents.LogFill: {
// Invalidate cache
const args = decodedLog.args as LogFillContractEventArgs;
this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(args.orderHash);
// Revalidate orders
const orderHash = args.orderHash;
const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]);
if (isOrderWatched) {
await this._emitRevalidateOrdersAsync([orderHash]);
}
break;
}
case ExchangeEvents.LogCancel: {
// Invalidate cache
const args = decodedLog.args as LogCancelContractEventArgs;
this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(args.orderHash);
// Revalidate orders
const orderHash = args.orderHash;
const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]);
if (isOrderWatched) {
await this._emitRevalidateOrdersAsync([orderHash]);
}
break;
}
case ExchangeEvents.LogError:
return; // noop
const zrxTokenAddress = this._getZRXTokenAddress();
if (!signedOrder.makerFee.isZero()) {
this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.maker);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.maker);
}
if (!signedOrder.takerFee.isZero()) {
this._balanceAndProxyAllowanceLazyStore.deleteBalance(zrxTokenAddress, signedOrder.taker);
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(zrxTokenAddress, signedOrder.taker);
}
}
private _onOrderExpired(orderHash: string): void {
const orderState: OrderState = {
isValid: false,
orderHash,
error: ExchangeContractErrs.OrderFillExpired,
};
if (!_.isUndefined(this._orderByOrderHash[orderHash])) {
this.removeOrder(orderHash);
if (!_.isUndefined(this._callbackIfExists)) {
this._callbackIfExists(null, orderState);
}
}
}
private async _onEventWatcherCallbackAsync(err: Error | null, logIfExists?: LogEvent): Promise<void> {
if (!_.isNull(err)) {
if (!_.isUndefined(this._callbackIfExists)) {
this._callbackIfExists(err);
this.unsubscribe();
}
return;
}
const log = logIfExists as LogEvent; // At this moment we are sure that no error occured and log is defined.
const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log);
const isLogDecoded = !_.isUndefined((maybeDecodedLog as LogWithDecodedArgs<any>).event);
if (!isLogDecoded) {
return; // noop
}
const decodedLog = maybeDecodedLog as LogWithDecodedArgs<ContractEventArgs>;
let makerToken: string;
let makerAddress: string;
switch (decodedLog.event) {
case TokenEvents.Approval: {
// Invalidate cache
const args = decodedLog.args as ApprovalContractEventArgs;
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(decodedLog.address, args._owner);
// Revalidate orders
makerToken = decodedLog.address;
makerAddress = args._owner;
if (
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
) {
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
await this._emitRevalidateOrdersAsync(orderHashes);
}
break;
}
case TokenEvents.Transfer: {
// Invalidate cache
const args = decodedLog.args as TransferContractEventArgs;
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._from);
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._to);
// Revalidate orders
makerToken = decodedLog.address;
makerAddress = args._from;
if (
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
) {
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
await this._emitRevalidateOrdersAsync(orderHashes);
}
break;
}
case EtherTokenEvents.Deposit: {
// Invalidate cache
const args = decodedLog.args as DepositContractEventArgs;
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner);
// Revalidate orders
makerToken = decodedLog.address;
makerAddress = args._owner;
if (
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
) {
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
await this._emitRevalidateOrdersAsync(orderHashes);
}
break;
}
case EtherTokenEvents.Withdrawal: {
// Invalidate cache
const args = decodedLog.args as WithdrawalContractEventArgs;
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._owner);
// Revalidate orders
makerToken = decodedLog.address;
makerAddress = args._owner;
if (
!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])
) {
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
await this._emitRevalidateOrdersAsync(orderHashes);
}
break;
}
case ExchangeEvents.LogFill: {
// Invalidate cache
const args = decodedLog.args as LogFillContractEventArgs;
this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(args.orderHash);
// Revalidate orders
const orderHash = args.orderHash;
const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]);
if (isOrderWatched) {
await this._emitRevalidateOrdersAsync([orderHash]);
}
break;
}
case ExchangeEvents.LogCancel: {
// Invalidate cache
const args = decodedLog.args as LogCancelContractEventArgs;
this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(args.orderHash);
// Revalidate orders
const orderHash = args.orderHash;
const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]);
if (isOrderWatched) {
await this._emitRevalidateOrdersAsync([orderHash]);
}
break;
}
case ExchangeEvents.LogError:
return; // noop
default:
throw utils.spawnSwitchErr('decodedLog.event', decodedLog.event);
}
}
private async _emitRevalidateOrdersAsync(orderHashes: string[]): Promise<void> {
for (const orderHash of orderHashes) {
const signedOrder = this._orderByOrderHash[orderHash];
// Most of these calls will never reach the network because the data is fetched from stores
// and only updated when cache is invalidated
const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder);
if (_.isUndefined(this._callbackIfExists)) {
break; // Unsubscribe was called
}
if (_.isEqual(orderState, this._orderStateByOrderHashCache[orderHash])) {
// Actual order state didn't change
continue;
} else {
this._orderStateByOrderHashCache[orderHash] = orderState;
}
this._callbackIfExists(null, orderState);
}
}
private _addToDependentOrderHashes(signedOrder: SignedOrder, orderHash: string): void {
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) {
this._dependentOrderHashes[signedOrder.maker] = {};
}
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress])) {
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set();
}
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash);
const zrxTokenAddress = this._getZRXTokenAddress();
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress])) {
this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress] = new Set();
}
this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash);
}
private _removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string) {
this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash);
if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) {
delete this._dependentOrderHashes[makerAddress][tokenAddress];
}
if (_.isEmpty(this._dependentOrderHashes[makerAddress])) {
delete this._dependentOrderHashes[makerAddress];
}
}
private _getZRXTokenAddress(): string {
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
const zrxTokenAddress = exchange.getZRXTokenAddress();
return zrxTokenAddress;
}
default:
throw utils.spawnSwitchErr('decodedLog.event', decodedLog.event);
}
}
private async _emitRevalidateOrdersAsync(orderHashes: string[]): Promise<void> {
for (const orderHash of orderHashes) {
const signedOrder = this._orderByOrderHash[orderHash];
// Most of these calls will never reach the network because the data is fetched from stores
// and only updated when cache is invalidated
const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder);
if (_.isUndefined(this._callbackIfExists)) {
break; // Unsubscribe was called
}
if (_.isEqual(orderState, this._orderStateByOrderHashCache[orderHash])) {
// Actual order state didn't change
continue;
} else {
this._orderStateByOrderHashCache[orderHash] = orderState;
}
this._callbackIfExists(null, orderState);
}
}
private _addToDependentOrderHashes(signedOrder: SignedOrder, orderHash: string): void {
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) {
this._dependentOrderHashes[signedOrder.maker] = {};
}
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress])) {
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set();
}
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash);
const zrxTokenAddress = this._getZRXTokenAddress();
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress])) {
this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress] = new Set();
}
this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash);
}
private _removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string) {
this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash);
if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) {
delete this._dependentOrderHashes[makerAddress][tokenAddress];
}
if (_.isEmpty(this._dependentOrderHashes[makerAddress])) {
delete this._dependentOrderHashes[makerAddress];
}
}
private _getZRXTokenAddress(): string {
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
const zrxTokenAddress = exchange.getZRXTokenAddress();
return zrxTokenAddress;
}
}

View File

@@ -3,94 +3,94 @@ import { BigNumber } from '@0xproject/utils';
import { SignedOrder } from '../types';
export class RemainingFillableCalculator {
private _signedOrder: SignedOrder;
private _isMakerTokenZRX: boolean;
// Transferrable Amount is the minimum of Approval and Balance
private _transferrableMakerTokenAmount: BigNumber;
private _transferrableMakerFeeTokenAmount: BigNumber;
private _remainingMakerTokenAmount: BigNumber;
private _remainingMakerFeeAmount: BigNumber;
constructor(
signedOrder: SignedOrder,
isMakerTokenZRX: boolean,
transferrableMakerTokenAmount: BigNumber,
transferrableMakerFeeTokenAmount: BigNumber,
remainingMakerTokenAmount: BigNumber,
) {
this._signedOrder = signedOrder;
this._isMakerTokenZRX = isMakerTokenZRX;
this._transferrableMakerTokenAmount = transferrableMakerTokenAmount;
this._transferrableMakerFeeTokenAmount = transferrableMakerFeeTokenAmount;
this._remainingMakerTokenAmount = remainingMakerTokenAmount;
this._remainingMakerFeeAmount = remainingMakerTokenAmount
.times(signedOrder.makerFee)
.dividedToIntegerBy(signedOrder.makerTokenAmount);
}
public computeRemainingMakerFillable(): BigNumber {
if (this._hasSufficientFundsForFeeAndTransferAmount()) {
return this._remainingMakerTokenAmount;
}
if (this._signedOrder.makerFee.isZero()) {
return BigNumber.min(this._remainingMakerTokenAmount, this._transferrableMakerTokenAmount);
}
return this._calculatePartiallyFillableMakerTokenAmount();
}
public computeRemainingTakerFillable(): BigNumber {
return this.computeRemainingMakerFillable()
.times(this._signedOrder.takerTokenAmount)
.dividedToIntegerBy(this._signedOrder.makerTokenAmount);
}
private _hasSufficientFundsForFeeAndTransferAmount(): boolean {
if (this._isMakerTokenZRX) {
const totalZRXTransferAmountRequired = this._remainingMakerTokenAmount.plus(this._remainingMakerFeeAmount);
const hasSufficientFunds = this._transferrableMakerTokenAmount.greaterThanOrEqualTo(
totalZRXTransferAmountRequired,
);
return hasSufficientFunds;
} else {
const hasSufficientFundsForTransferAmount = this._transferrableMakerTokenAmount.greaterThanOrEqualTo(
this._remainingMakerTokenAmount,
);
const hasSufficientFundsForFeeAmount = this._transferrableMakerFeeTokenAmount.greaterThanOrEqualTo(
this._remainingMakerFeeAmount,
);
const hasSufficientFunds = hasSufficientFundsForTransferAmount && hasSufficientFundsForFeeAmount;
return hasSufficientFunds;
}
}
private _calculatePartiallyFillableMakerTokenAmount(): BigNumber {
// Given an order for 200 wei for 2 ZRXwei fee, find 100 wei for 1 ZRXwei. Order ratio is then 100:1
const orderToFeeRatio = this._signedOrder.makerTokenAmount.dividedBy(this._signedOrder.makerFee);
// The number of times the maker can fill the order, if each fill only required the transfer of a single
// baseUnit of fee tokens.
// Given 2 ZRXwei, the maximum amount of times Maker can fill this order, in terms of fees, is 2
const fillableTimesInFeeTokenBaseUnits = BigNumber.min(
this._transferrableMakerFeeTokenAmount,
this._remainingMakerFeeAmount,
);
// The number of times the Maker can fill the order, given the Maker Token Balance
// Assuming a balance of 150 wei, and an orderToFeeRatio of 100:1, maker can fill this order 1 time.
let fillableTimesInMakerTokenUnits = this._transferrableMakerTokenAmount.dividedBy(orderToFeeRatio);
if (this._isMakerTokenZRX) {
// If ZRX is the maker token, the Fee and the Maker amount need to be removed from the same pool;
// 200 ZRXwei for 2ZRXwei fee can only be filled once (need 202 ZRXwei)
const totalZRXTokenPooled = this._transferrableMakerTokenAmount;
// The purchasing power here is less as the tokens are taken from the same Pool
// For every one number of fills, we have to take an extra ZRX out of the pool
fillableTimesInMakerTokenUnits = totalZRXTokenPooled.dividedBy(orderToFeeRatio.plus(new BigNumber(1)));
}
// When Ratio is not fully divisible there can be remainders which cannot be represented, so they are floored.
// This can result in a RoundingError being thrown by the Exchange Contract.
const partiallyFillableMakerTokenAmount = fillableTimesInMakerTokenUnits
.times(this._signedOrder.makerTokenAmount)
.dividedToIntegerBy(this._signedOrder.makerFee);
const partiallyFillableFeeTokenAmount = fillableTimesInFeeTokenBaseUnits
.times(this._signedOrder.makerTokenAmount)
.dividedToIntegerBy(this._signedOrder.makerFee);
const partiallyFillableAmount = BigNumber.min(
partiallyFillableMakerTokenAmount,
partiallyFillableFeeTokenAmount,
);
return partiallyFillableAmount;
}
private _signedOrder: SignedOrder;
private _isMakerTokenZRX: boolean;
// Transferrable Amount is the minimum of Approval and Balance
private _transferrableMakerTokenAmount: BigNumber;
private _transferrableMakerFeeTokenAmount: BigNumber;
private _remainingMakerTokenAmount: BigNumber;
private _remainingMakerFeeAmount: BigNumber;
constructor(
signedOrder: SignedOrder,
isMakerTokenZRX: boolean,
transferrableMakerTokenAmount: BigNumber,
transferrableMakerFeeTokenAmount: BigNumber,
remainingMakerTokenAmount: BigNumber,
) {
this._signedOrder = signedOrder;
this._isMakerTokenZRX = isMakerTokenZRX;
this._transferrableMakerTokenAmount = transferrableMakerTokenAmount;
this._transferrableMakerFeeTokenAmount = transferrableMakerFeeTokenAmount;
this._remainingMakerTokenAmount = remainingMakerTokenAmount;
this._remainingMakerFeeAmount = remainingMakerTokenAmount
.times(signedOrder.makerFee)
.dividedToIntegerBy(signedOrder.makerTokenAmount);
}
public computeRemainingMakerFillable(): BigNumber {
if (this._hasSufficientFundsForFeeAndTransferAmount()) {
return this._remainingMakerTokenAmount;
}
if (this._signedOrder.makerFee.isZero()) {
return BigNumber.min(this._remainingMakerTokenAmount, this._transferrableMakerTokenAmount);
}
return this._calculatePartiallyFillableMakerTokenAmount();
}
public computeRemainingTakerFillable(): BigNumber {
return this.computeRemainingMakerFillable()
.times(this._signedOrder.takerTokenAmount)
.dividedToIntegerBy(this._signedOrder.makerTokenAmount);
}
private _hasSufficientFundsForFeeAndTransferAmount(): boolean {
if (this._isMakerTokenZRX) {
const totalZRXTransferAmountRequired = this._remainingMakerTokenAmount.plus(this._remainingMakerFeeAmount);
const hasSufficientFunds = this._transferrableMakerTokenAmount.greaterThanOrEqualTo(
totalZRXTransferAmountRequired,
);
return hasSufficientFunds;
} else {
const hasSufficientFundsForTransferAmount = this._transferrableMakerTokenAmount.greaterThanOrEqualTo(
this._remainingMakerTokenAmount,
);
const hasSufficientFundsForFeeAmount = this._transferrableMakerFeeTokenAmount.greaterThanOrEqualTo(
this._remainingMakerFeeAmount,
);
const hasSufficientFunds = hasSufficientFundsForTransferAmount && hasSufficientFundsForFeeAmount;
return hasSufficientFunds;
}
}
private _calculatePartiallyFillableMakerTokenAmount(): BigNumber {
// Given an order for 200 wei for 2 ZRXwei fee, find 100 wei for 1 ZRXwei. Order ratio is then 100:1
const orderToFeeRatio = this._signedOrder.makerTokenAmount.dividedBy(this._signedOrder.makerFee);
// The number of times the maker can fill the order, if each fill only required the transfer of a single
// baseUnit of fee tokens.
// Given 2 ZRXwei, the maximum amount of times Maker can fill this order, in terms of fees, is 2
const fillableTimesInFeeTokenBaseUnits = BigNumber.min(
this._transferrableMakerFeeTokenAmount,
this._remainingMakerFeeAmount,
);
// The number of times the Maker can fill the order, given the Maker Token Balance
// Assuming a balance of 150 wei, and an orderToFeeRatio of 100:1, maker can fill this order 1 time.
let fillableTimesInMakerTokenUnits = this._transferrableMakerTokenAmount.dividedBy(orderToFeeRatio);
if (this._isMakerTokenZRX) {
// If ZRX is the maker token, the Fee and the Maker amount need to be removed from the same pool;
// 200 ZRXwei for 2ZRXwei fee can only be filled once (need 202 ZRXwei)
const totalZRXTokenPooled = this._transferrableMakerTokenAmount;
// The purchasing power here is less as the tokens are taken from the same Pool
// For every one number of fills, we have to take an extra ZRX out of the pool
fillableTimesInMakerTokenUnits = totalZRXTokenPooled.dividedBy(orderToFeeRatio.plus(new BigNumber(1)));
}
// When Ratio is not fully divisible there can be remainders which cannot be represented, so they are floored.
// This can result in a RoundingError being thrown by the Exchange Contract.
const partiallyFillableMakerTokenAmount = fillableTimesInMakerTokenUnits
.times(this._signedOrder.makerTokenAmount)
.dividedToIntegerBy(this._signedOrder.makerFee);
const partiallyFillableFeeTokenAmount = fillableTimesInFeeTokenBaseUnits
.times(this._signedOrder.makerTokenAmount)
.dividedToIntegerBy(this._signedOrder.makerFee);
const partiallyFillableAmount = BigNumber.min(
partiallyFillableMakerTokenAmount,
partiallyFillableFeeTokenAmount,
);
return partiallyFillableAmount;
}
}

View File

@@ -1,27 +1,27 @@
export const zeroExConfigSchema = {
id: '/ZeroExConfig',
properties: {
networkId: {
type: 'number',
minimum: 0,
},
gasPrice: { $ref: '/Number' },
exchangeContractAddress: { $ref: '/Address' },
tokenRegistryContractAddress: { $ref: '/Address' },
orderWatcherConfig: {
type: 'object',
properties: {
pollingIntervalMs: {
type: 'number',
minimum: 0,
},
numConfirmations: {
type: 'number',
minimum: 0,
},
},
},
},
type: 'object',
required: ['networkId'],
id: '/ZeroExConfig',
properties: {
networkId: {
type: 'number',
minimum: 0,
},
gasPrice: { $ref: '/Number' },
exchangeContractAddress: { $ref: '/Address' },
tokenRegistryContractAddress: { $ref: '/Address' },
orderWatcherConfig: {
type: 'object',
properties: {
pollingIntervalMs: {
type: 'number',
minimum: 0,
},
numConfirmations: {
type: 'number',
minimum: 0,
},
},
},
},
type: 'object',
required: ['networkId'],
};

View File

@@ -8,79 +8,79 @@ import { BlockParamLiteral } from '../types';
* Copy on read store for balances/proxyAllowances of tokens/accounts
*/
export class BalanceAndProxyAllowanceLazyStore {
private _token: TokenWrapper;
private _defaultBlock: BlockParamLiteral;
private _balance: {
[tokenAddress: string]: {
[userAddress: string]: BigNumber;
};
};
private _proxyAllowance: {
[tokenAddress: string]: {
[userAddress: string]: BigNumber;
};
};
constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) {
this._token = token;
this._defaultBlock = defaultBlock;
this._balance = {};
this._proxyAllowance = {};
}
public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
if (_.isUndefined(this._balance[tokenAddress]) || _.isUndefined(this._balance[tokenAddress][userAddress])) {
const methodOpts = {
defaultBlock: this._defaultBlock,
};
const balance = await this._token.getBalanceAsync(tokenAddress, userAddress, methodOpts);
this.setBalance(tokenAddress, userAddress, balance);
}
const cachedBalance = this._balance[tokenAddress][userAddress];
return cachedBalance;
}
public setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void {
if (_.isUndefined(this._balance[tokenAddress])) {
this._balance[tokenAddress] = {};
}
this._balance[tokenAddress][userAddress] = balance;
}
public deleteBalance(tokenAddress: string, userAddress: string): void {
if (!_.isUndefined(this._balance[tokenAddress])) {
delete this._balance[tokenAddress][userAddress];
if (_.isEmpty(this._balance[tokenAddress])) {
delete this._balance[tokenAddress];
}
}
}
public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
if (
_.isUndefined(this._proxyAllowance[tokenAddress]) ||
_.isUndefined(this._proxyAllowance[tokenAddress][userAddress])
) {
const methodOpts = {
defaultBlock: this._defaultBlock,
};
const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts);
this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance);
}
const cachedProxyAllowance = this._proxyAllowance[tokenAddress][userAddress];
return cachedProxyAllowance;
}
public setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void {
if (_.isUndefined(this._proxyAllowance[tokenAddress])) {
this._proxyAllowance[tokenAddress] = {};
}
this._proxyAllowance[tokenAddress][userAddress] = proxyAllowance;
}
public deleteProxyAllowance(tokenAddress: string, userAddress: string): void {
if (!_.isUndefined(this._proxyAllowance[tokenAddress])) {
delete this._proxyAllowance[tokenAddress][userAddress];
if (_.isEmpty(this._proxyAllowance[tokenAddress])) {
delete this._proxyAllowance[tokenAddress];
}
}
}
public deleteAll(): void {
this._balance = {};
this._proxyAllowance = {};
}
private _token: TokenWrapper;
private _defaultBlock: BlockParamLiteral;
private _balance: {
[tokenAddress: string]: {
[userAddress: string]: BigNumber;
};
};
private _proxyAllowance: {
[tokenAddress: string]: {
[userAddress: string]: BigNumber;
};
};
constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) {
this._token = token;
this._defaultBlock = defaultBlock;
this._balance = {};
this._proxyAllowance = {};
}
public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
if (_.isUndefined(this._balance[tokenAddress]) || _.isUndefined(this._balance[tokenAddress][userAddress])) {
const methodOpts = {
defaultBlock: this._defaultBlock,
};
const balance = await this._token.getBalanceAsync(tokenAddress, userAddress, methodOpts);
this.setBalance(tokenAddress, userAddress, balance);
}
const cachedBalance = this._balance[tokenAddress][userAddress];
return cachedBalance;
}
public setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void {
if (_.isUndefined(this._balance[tokenAddress])) {
this._balance[tokenAddress] = {};
}
this._balance[tokenAddress][userAddress] = balance;
}
public deleteBalance(tokenAddress: string, userAddress: string): void {
if (!_.isUndefined(this._balance[tokenAddress])) {
delete this._balance[tokenAddress][userAddress];
if (_.isEmpty(this._balance[tokenAddress])) {
delete this._balance[tokenAddress];
}
}
}
public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
if (
_.isUndefined(this._proxyAllowance[tokenAddress]) ||
_.isUndefined(this._proxyAllowance[tokenAddress][userAddress])
) {
const methodOpts = {
defaultBlock: this._defaultBlock,
};
const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts);
this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance);
}
const cachedProxyAllowance = this._proxyAllowance[tokenAddress][userAddress];
return cachedProxyAllowance;
}
public setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void {
if (_.isUndefined(this._proxyAllowance[tokenAddress])) {
this._proxyAllowance[tokenAddress] = {};
}
this._proxyAllowance[tokenAddress][userAddress] = proxyAllowance;
}
public deleteProxyAllowance(tokenAddress: string, userAddress: string): void {
if (!_.isUndefined(this._proxyAllowance[tokenAddress])) {
delete this._proxyAllowance[tokenAddress][userAddress];
if (_.isEmpty(this._proxyAllowance[tokenAddress])) {
delete this._proxyAllowance[tokenAddress];
}
}
}
public deleteAll(): void {
this._balance = {};
this._proxyAllowance = {};
}
}

View File

@@ -8,54 +8,54 @@ import { BlockParamLiteral } from '../types';
* Copy on read store for filled/cancelled taker amounts
*/
export class OrderFilledCancelledLazyStore {
private _exchange: ExchangeWrapper;
private _filledTakerAmount: {
[orderHash: string]: BigNumber;
};
private _cancelledTakerAmount: {
[orderHash: string]: BigNumber;
};
constructor(exchange: ExchangeWrapper) {
this._exchange = exchange;
this._filledTakerAmount = {};
this._cancelledTakerAmount = {};
}
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
if (_.isUndefined(this._filledTakerAmount[orderHash])) {
const methodOpts = {
defaultBlock: BlockParamLiteral.Pending,
};
const filledTakerAmount = await this._exchange.getFilledTakerAmountAsync(orderHash, methodOpts);
this.setFilledTakerAmount(orderHash, filledTakerAmount);
}
const cachedFilled = this._filledTakerAmount[orderHash];
return cachedFilled;
}
public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void {
this._filledTakerAmount[orderHash] = filledTakerAmount;
}
public deleteFilledTakerAmount(orderHash: string): void {
delete this._filledTakerAmount[orderHash];
}
public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
if (_.isUndefined(this._cancelledTakerAmount[orderHash])) {
const methodOpts = {
defaultBlock: BlockParamLiteral.Pending,
};
const cancelledTakerAmount = await this._exchange.getCancelledTakerAmountAsync(orderHash, methodOpts);
this.setCancelledTakerAmount(orderHash, cancelledTakerAmount);
}
const cachedCancelled = this._cancelledTakerAmount[orderHash];
return cachedCancelled;
}
public setCancelledTakerAmount(orderHash: string, cancelledTakerAmount: BigNumber): void {
this._cancelledTakerAmount[orderHash] = cancelledTakerAmount;
}
public deleteCancelledTakerAmount(orderHash: string): void {
delete this._cancelledTakerAmount[orderHash];
}
public deleteAll(): void {
this._filledTakerAmount = {};
this._cancelledTakerAmount = {};
}
private _exchange: ExchangeWrapper;
private _filledTakerAmount: {
[orderHash: string]: BigNumber;
};
private _cancelledTakerAmount: {
[orderHash: string]: BigNumber;
};
constructor(exchange: ExchangeWrapper) {
this._exchange = exchange;
this._filledTakerAmount = {};
this._cancelledTakerAmount = {};
}
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
if (_.isUndefined(this._filledTakerAmount[orderHash])) {
const methodOpts = {
defaultBlock: BlockParamLiteral.Pending,
};
const filledTakerAmount = await this._exchange.getFilledTakerAmountAsync(orderHash, methodOpts);
this.setFilledTakerAmount(orderHash, filledTakerAmount);
}
const cachedFilled = this._filledTakerAmount[orderHash];
return cachedFilled;
}
public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void {
this._filledTakerAmount[orderHash] = filledTakerAmount;
}
public deleteFilledTakerAmount(orderHash: string): void {
delete this._filledTakerAmount[orderHash];
}
public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
if (_.isUndefined(this._cancelledTakerAmount[orderHash])) {
const methodOpts = {
defaultBlock: BlockParamLiteral.Pending,
};
const cancelledTakerAmount = await this._exchange.getCancelledTakerAmountAsync(orderHash, methodOpts);
this.setCancelledTakerAmount(orderHash, cancelledTakerAmount);
}
const cachedCancelled = this._cancelledTakerAmount[orderHash];
return cachedCancelled;
}
public setCancelledTakerAmount(orderHash: string, cancelledTakerAmount: BigNumber): void {
this._cancelledTakerAmount[orderHash] = cancelledTakerAmount;
}
public deleteCancelledTakerAmount(orderHash: string): void {
delete this._cancelledTakerAmount[orderHash];
}
public deleteAll(): void {
this._filledTakerAmount = {};
this._cancelledTakerAmount = {};
}
}

View File

@@ -3,41 +3,41 @@ import { BigNumber } from '@0xproject/utils';
import * as Web3 from 'web3';
export enum ZeroExError {
ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST',
ZRXContractDoesNotExist = 'ZRX_CONTRACT_DOES_NOT_EXIST',
EtherTokenContractDoesNotExist = 'ETHER_TOKEN_CONTRACT_DOES_NOT_EXIST',
TokenTransferProxyContractDoesNotExist = 'TOKEN_TRANSFER_PROXY_CONTRACT_DOES_NOT_EXIST',
TokenRegistryContractDoesNotExist = 'TOKEN_REGISTRY_CONTRACT_DOES_NOT_EXIST',
TokenContractDoesNotExist = 'TOKEN_CONTRACT_DOES_NOT_EXIST',
UnhandledError = 'UNHANDLED_ERROR',
UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES',
InvalidSignature = 'INVALID_SIGNATURE',
ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER',
InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT',
InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL',
InvalidJump = 'INVALID_JUMP',
OutOfGas = 'OUT_OF_GAS',
NoNetworkId = 'NO_NETWORK_ID',
SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND',
SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT',
TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT',
ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST',
ZRXContractDoesNotExist = 'ZRX_CONTRACT_DOES_NOT_EXIST',
EtherTokenContractDoesNotExist = 'ETHER_TOKEN_CONTRACT_DOES_NOT_EXIST',
TokenTransferProxyContractDoesNotExist = 'TOKEN_TRANSFER_PROXY_CONTRACT_DOES_NOT_EXIST',
TokenRegistryContractDoesNotExist = 'TOKEN_REGISTRY_CONTRACT_DOES_NOT_EXIST',
TokenContractDoesNotExist = 'TOKEN_CONTRACT_DOES_NOT_EXIST',
UnhandledError = 'UNHANDLED_ERROR',
UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES',
InvalidSignature = 'INVALID_SIGNATURE',
ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER',
InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT',
InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL',
InvalidJump = 'INVALID_JUMP',
OutOfGas = 'OUT_OF_GAS',
NoNetworkId = 'NO_NETWORK_ID',
SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND',
SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT',
TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT',
}
export enum InternalZeroExError {
NoAbiDecoder = 'NO_ABI_DECODER',
ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY',
WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY',
NoAbiDecoder = 'NO_ABI_DECODER',
ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY',
WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY',
}
/**
* Elliptic Curve signature
*/
export interface ECSignature {
v: number;
r: string;
s: string;
v: number;
r: string;
s: string;
}
export type OrderAddresses = [string, string, string, string, string];
@@ -46,214 +46,214 @@ export type OrderValues = [BigNumber, BigNumber, BigNumber, BigNumber, BigNumber
export type LogEvent = Web3.LogEntryEvent;
export interface DecodedLogEvent<ArgsType> {
isRemoved: boolean;
log: LogWithDecodedArgs<ArgsType>;
isRemoved: boolean;
log: LogWithDecodedArgs<ArgsType>;
}
export type EventCallback<ArgsType> = (err: null | Error, log?: DecodedLogEvent<ArgsType>) => void;
export type EventWatcherCallback = (err: null | Error, log?: LogEvent) => void;
export enum SolidityTypes {
Address = 'address',
Uint256 = 'uint256',
Uint8 = 'uint8',
Uint = 'uint',
Address = 'address',
Uint256 = 'uint256',
Uint8 = 'uint8',
Uint = 'uint',
}
export enum ExchangeContractErrCodes {
ERROR_FILL_EXPIRED, // Order has already expired
ERROR_FILL_NO_VALUE, // Order has already been fully filled or cancelled
ERROR_FILL_TRUNCATION, // Rounding error too large
ERROR_FILL_BALANCE_ALLOWANCE, // Insufficient balance or allowance for token transfer
ERROR_CANCEL_EXPIRED, // Order has already expired
ERROR_CANCEL_NO_VALUE, // Order has already been fully filled or cancelled
ERROR_FILL_EXPIRED, // Order has already expired
ERROR_FILL_NO_VALUE, // Order has already been fully filled or cancelled
ERROR_FILL_TRUNCATION, // Rounding error too large
ERROR_FILL_BALANCE_ALLOWANCE, // Insufficient balance or allowance for token transfer
ERROR_CANCEL_EXPIRED, // Order has already expired
ERROR_CANCEL_NO_VALUE, // Order has already been fully filled or cancelled
}
export enum ExchangeContractErrs {
OrderFillExpired = 'ORDER_FILL_EXPIRED',
OrderCancelExpired = 'ORDER_CANCEL_EXPIRED',
OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO',
OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED',
OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO',
OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO',
OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR',
FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR',
InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE',
InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE',
InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE',
InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE',
InsufficientTakerFeeBalance = 'INSUFFICIENT_TAKER_FEE_BALANCE',
InsufficientTakerFeeAllowance = 'INSUFFICIENT_TAKER_FEE_ALLOWANCE',
InsufficientMakerFeeBalance = 'INSUFFICIENT_MAKER_FEE_BALANCE',
InsufficientMakerFeeAllowance = 'INSUFFICIENT_MAKER_FEE_ALLOWANCE',
TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER',
MultipleMakersInSingleCancelBatchDisallowed = 'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED',
InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT',
MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED',
BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS',
BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM',
OrderFillExpired = 'ORDER_FILL_EXPIRED',
OrderCancelExpired = 'ORDER_CANCEL_EXPIRED',
OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO',
OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED',
OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO',
OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO',
OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR',
FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR',
InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE',
InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE',
InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE',
InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE',
InsufficientTakerFeeBalance = 'INSUFFICIENT_TAKER_FEE_BALANCE',
InsufficientTakerFeeAllowance = 'INSUFFICIENT_TAKER_FEE_ALLOWANCE',
InsufficientMakerFeeBalance = 'INSUFFICIENT_MAKER_FEE_BALANCE',
InsufficientMakerFeeAllowance = 'INSUFFICIENT_MAKER_FEE_ALLOWANCE',
TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER',
MultipleMakersInSingleCancelBatchDisallowed = 'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED',
InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT',
MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED',
BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS',
BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM',
}
export type RawLog = Web3.LogEntry;
export interface ContractEvent {
logIndex: number;
transactionIndex: number;
transactionHash: string;
blockHash: string;
blockNumber: number;
address: string;
type: string;
event: string;
args: ContractEventArgs;
logIndex: number;
transactionIndex: number;
transactionHash: string;
blockHash: string;
blockNumber: number;
address: string;
type: string;
event: string;
args: ContractEventArgs;
}
export interface LogFillContractEventArgs {
maker: string;
taker: string;
feeRecipient: string;
makerToken: string;
takerToken: string;
filledMakerTokenAmount: BigNumber;
filledTakerTokenAmount: BigNumber;
paidMakerFee: BigNumber;
paidTakerFee: BigNumber;
tokens: string;
orderHash: string;
maker: string;
taker: string;
feeRecipient: string;
makerToken: string;
takerToken: string;
filledMakerTokenAmount: BigNumber;
filledTakerTokenAmount: BigNumber;
paidMakerFee: BigNumber;
paidTakerFee: BigNumber;
tokens: string;
orderHash: string;
}
export interface LogCancelContractEventArgs {
maker: string;
feeRecipient: string;
makerToken: string;
takerToken: string;
cancelledMakerTokenAmount: BigNumber;
cancelledTakerTokenAmount: BigNumber;
tokens: string;
orderHash: string;
maker: string;
feeRecipient: string;
makerToken: string;
takerToken: string;
cancelledMakerTokenAmount: BigNumber;
cancelledTakerTokenAmount: BigNumber;
tokens: string;
orderHash: string;
}
export interface LogErrorContractEventArgs {
errorId: BigNumber;
orderHash: string;
errorId: BigNumber;
orderHash: string;
}
export type ExchangeContractEventArgs =
| LogFillContractEventArgs
| LogCancelContractEventArgs
| LogErrorContractEventArgs;
| LogFillContractEventArgs
| LogCancelContractEventArgs
| LogErrorContractEventArgs;
export interface TransferContractEventArgs {
_from: string;
_to: string;
_value: BigNumber;
_from: string;
_to: string;
_value: BigNumber;
}
export interface ApprovalContractEventArgs {
_owner: string;
_spender: string;
_value: BigNumber;
_owner: string;
_spender: string;
_value: BigNumber;
}
export interface DepositContractEventArgs {
_owner: string;
_value: BigNumber;
_owner: string;
_value: BigNumber;
}
export interface WithdrawalContractEventArgs {
_owner: string;
_value: BigNumber;
_owner: string;
_value: BigNumber;
}
export type TokenContractEventArgs = TransferContractEventArgs | ApprovalContractEventArgs;
export type EtherTokenContractEventArgs =
| TokenContractEventArgs
| DepositContractEventArgs
| WithdrawalContractEventArgs;
| TokenContractEventArgs
| DepositContractEventArgs
| WithdrawalContractEventArgs;
export type ContractEventArgs = ExchangeContractEventArgs | TokenContractEventArgs | EtherTokenContractEventArgs;
export type ContractEventArg = string | BigNumber;
export interface Order {
maker: string;
taker: string;
makerFee: BigNumber;
takerFee: BigNumber;
makerTokenAmount: BigNumber;
takerTokenAmount: BigNumber;
makerTokenAddress: string;
takerTokenAddress: string;
salt: BigNumber;
exchangeContractAddress: string;
feeRecipient: string;
expirationUnixTimestampSec: BigNumber;
maker: string;
taker: string;
makerFee: BigNumber;
takerFee: BigNumber;
makerTokenAmount: BigNumber;
takerTokenAmount: BigNumber;
makerTokenAddress: string;
takerTokenAddress: string;
salt: BigNumber;
exchangeContractAddress: string;
feeRecipient: string;
expirationUnixTimestampSec: BigNumber;
}
export interface SignedOrder extends Order {
ecSignature: ECSignature;
ecSignature: ECSignature;
}
// [address, name, symbol, decimals, ipfsHash, swarmHash]
export type TokenMetadata = [string, string, string, BigNumber, string, string];
export interface Token {
name: string;
address: string;
symbol: string;
decimals: number;
name: string;
address: string;
symbol: string;
decimals: number;
}
export interface TxOpts {
from: string;
gas?: number;
value?: BigNumber;
gasPrice?: BigNumber;
from: string;
gas?: number;
value?: BigNumber;
gasPrice?: BigNumber;
}
export interface TokenAddressBySymbol {
[symbol: string]: string;
[symbol: string]: string;
}
export enum ExchangeEvents {
LogFill = 'LogFill',
LogCancel = 'LogCancel',
LogError = 'LogError',
LogFill = 'LogFill',
LogCancel = 'LogCancel',
LogError = 'LogError',
}
export enum TokenEvents {
Transfer = 'Transfer',
Approval = 'Approval',
Transfer = 'Transfer',
Approval = 'Approval',
}
export enum EtherTokenEvents {
Transfer = 'Transfer',
Approval = 'Approval',
Deposit = 'Deposit',
Withdrawal = 'Withdrawal',
Transfer = 'Transfer',
Approval = 'Approval',
Deposit = 'Deposit',
Withdrawal = 'Withdrawal',
}
export type ContractEvents = TokenEvents | ExchangeEvents | EtherTokenEvents;
export interface IndexedFilterValues {
[index: string]: ContractEventArg;
[index: string]: ContractEventArg;
}
// Earliest is omitted by design. It is simply an alias for the `0` constant and
// is thus not very helpful. Moreover, this type is used in places that only accept
// `latest` or `pending`.
export enum BlockParamLiteral {
Latest = 'latest',
Pending = 'pending',
Latest = 'latest',
Pending = 'pending',
}
export type BlockParam = BlockParamLiteral | number;
export interface BlockRange {
fromBlock: BlockParam;
toBlock: BlockParam;
fromBlock: BlockParam;
toBlock: BlockParam;
}
export type DoneCallback = (err?: Error) => void;
export interface OrderCancellationRequest {
order: Order | SignedOrder;
takerTokenCancelAmount: BigNumber;
order: Order | SignedOrder;
takerTokenCancelAmount: BigNumber;
}
export interface OrderFillRequest {
signedOrder: SignedOrder;
takerTokenFillAmount: BigNumber;
signedOrder: SignedOrder;
takerTokenFillAmount: BigNumber;
}
export type AsyncMethod = (...args: any[]) => Promise<any>;
@@ -268,8 +268,8 @@ export type SyncMethod = (...args: any[]) => any;
export type Web3Provider = Web3.Provider;
export interface JSONRPCPayload {
params: any[];
method: string;
params: any[];
method: string;
}
/*
@@ -280,10 +280,10 @@ export interface JSONRPCPayload {
* cleanupJobIntervalMs: How often to run a cleanup job which revalidates all the orders. Defaults: 1h
*/
export interface OrderStateWatcherConfig {
orderExpirationCheckingIntervalMs?: number;
eventPollingIntervalMs?: number;
expirationMarginMs?: number;
cleanupJobIntervalMs?: number;
orderExpirationCheckingIntervalMs?: number;
eventPollingIntervalMs?: number;
expirationMarginMs?: number;
cleanupJobIntervalMs?: number;
}
/*
@@ -296,42 +296,42 @@ export interface OrderStateWatcherConfig {
* orderWatcherConfig: All the configs related to the orderWatcher
*/
export interface ZeroExConfig {
networkId: number;
gasPrice?: BigNumber;
exchangeContractAddress?: string;
zrxContractAddress?: string;
tokenRegistryContractAddress?: string;
tokenTransferProxyContractAddress?: string;
orderWatcherConfig?: OrderStateWatcherConfig;
networkId: number;
gasPrice?: BigNumber;
exchangeContractAddress?: string;
zrxContractAddress?: string;
tokenRegistryContractAddress?: string;
tokenTransferProxyContractAddress?: string;
orderWatcherConfig?: OrderStateWatcherConfig;
}
export enum AbiType {
Function = 'function',
Constructor = 'constructor',
Event = 'event',
Fallback = 'fallback',
Function = 'function',
Constructor = 'constructor',
Event = 'event',
Fallback = 'fallback',
}
export interface DecodedLogArgs {
[argName: string]: ContractEventArg;
[argName: string]: ContractEventArg;
}
export interface LogWithDecodedArgs<ArgsType> extends Web3.DecodedLogEntry<ArgsType> {}
export interface TransactionReceiptWithDecodedLogs extends TransactionReceipt {
logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>;
logs: Array<LogWithDecodedArgs<DecodedLogArgs> | Web3.LogEntry>;
}
export type ArtifactContractName = 'ZRX' | 'TokenTransferProxy' | 'TokenRegistry' | 'Token' | 'Exchange' | 'EtherToken';
export interface Artifact {
contract_name: ArtifactContractName;
abi: Web3.ContractAbi;
networks: {
[networkId: number]: {
address: string;
};
};
contract_name: ArtifactContractName;
abi: Web3.ContractAbi;
networks: {
[networkId: number]: {
address: string;
};
};
}
/*
@@ -341,7 +341,7 @@ export interface Artifact {
* allowance/balance to fill the entire remaining order amount.
*/
export interface ValidateOrderFillableOpts {
expectedFillTakerTokenAmount?: BigNumber;
expectedFillTakerTokenAmount?: BigNumber;
}
/*
@@ -351,7 +351,7 @@ export interface ValidateOrderFillableOpts {
* flag when running Parity).
*/
export interface MethodOpts {
defaultBlock?: Web3.BlockParam;
defaultBlock?: Web3.BlockParam;
}
/*
@@ -359,8 +359,8 @@ export interface MethodOpts {
* gasLimit: The amount of gas to send with a transaction
*/
export interface TransactionOpts {
gasPrice?: BigNumber;
gasLimit?: number;
gasPrice?: BigNumber;
gasLimit?: number;
}
/*
@@ -368,42 +368,42 @@ export interface TransactionOpts {
* broadcasting it. For example, order has a valid signature, maker has sufficient funds, etc. Default: true
*/
export interface OrderTransactionOpts extends TransactionOpts {
shouldValidate?: boolean;
shouldValidate?: boolean;
}
export type FilterObject = Web3.FilterObject;
export enum TradeSide {
Maker = 'maker',
Taker = 'taker',
Maker = 'maker',
Taker = 'taker',
}
export enum TransferType {
Trade = 'trade',
Fee = 'fee',
Trade = 'trade',
Fee = 'fee',
}
export interface OrderRelevantState {
makerBalance: BigNumber;
makerProxyAllowance: BigNumber;
makerFeeBalance: BigNumber;
makerFeeProxyAllowance: BigNumber;
filledTakerTokenAmount: BigNumber;
cancelledTakerTokenAmount: BigNumber;
remainingFillableMakerTokenAmount: BigNumber;
remainingFillableTakerTokenAmount: BigNumber;
makerBalance: BigNumber;
makerProxyAllowance: BigNumber;
makerFeeBalance: BigNumber;
makerFeeProxyAllowance: BigNumber;
filledTakerTokenAmount: BigNumber;
cancelledTakerTokenAmount: BigNumber;
remainingFillableMakerTokenAmount: BigNumber;
remainingFillableTakerTokenAmount: BigNumber;
}
export interface OrderStateValid {
isValid: true;
orderHash: string;
orderRelevantState: OrderRelevantState;
isValid: true;
orderHash: string;
orderRelevantState: OrderRelevantState;
}
export interface OrderStateInvalid {
isValid: false;
orderHash: string;
error: ExchangeContractErrs;
isValid: false;
orderHash: string;
error: ExchangeContractErrs;
}
export type OrderState = OrderStateValid | OrderStateInvalid;

View File

@@ -6,67 +6,67 @@ import * as SolidityCoder from 'web3/lib/solidity/coder';
import { AbiType, ContractEventArgs, DecodedLogArgs, LogWithDecodedArgs, RawLog, SolidityTypes } from '../types';
export class AbiDecoder {
private _savedABIs: Web3.AbiDefinition[] = [];
private _methodIds: { [signatureHash: string]: Web3.EventAbi } = {};
private static _padZeros(address: string) {
let formatted = address;
if (_.startsWith(formatted, '0x')) {
formatted = formatted.slice(2);
}
private _savedABIs: Web3.AbiDefinition[] = [];
private _methodIds: { [signatureHash: string]: Web3.EventAbi } = {};
private static _padZeros(address: string) {
let formatted = address;
if (_.startsWith(formatted, '0x')) {
formatted = formatted.slice(2);
}
formatted = _.padStart(formatted, 40, '0');
return `0x${formatted}`;
}
constructor(abiArrays: Web3.AbiDefinition[][]) {
_.map(abiArrays, this._addABI.bind(this));
}
// This method can only decode logs from the 0x & ERC20 smart contracts
public tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>(
log: Web3.LogEntry,
): LogWithDecodedArgs<ArgsType> | RawLog {
const methodId = log.topics[0];
const event = this._methodIds[methodId];
if (_.isUndefined(event)) {
return log;
}
const logData = log.data;
const decodedParams: DecodedLogArgs = {};
let dataIndex = 0;
let topicsIndex = 1;
formatted = _.padStart(formatted, 40, '0');
return `0x${formatted}`;
}
constructor(abiArrays: Web3.AbiDefinition[][]) {
_.map(abiArrays, this._addABI.bind(this));
}
// This method can only decode logs from the 0x & ERC20 smart contracts
public tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>(
log: Web3.LogEntry,
): LogWithDecodedArgs<ArgsType> | RawLog {
const methodId = log.topics[0];
const event = this._methodIds[methodId];
if (_.isUndefined(event)) {
return log;
}
const logData = log.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('0x'.length));
const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed);
const dataTypes = _.map(nonIndexedInputs, input => input.type);
const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice('0x'.length));
_.map(event.inputs, (param: Web3.EventParameter) => {
// Indexed parameters are stored in topics. Non-indexed ones in decodedData
let value = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++];
if (param.type === SolidityTypes.Address) {
value = AbiDecoder._padZeros(new BigNumber(value).toString(16));
} else if (
param.type === SolidityTypes.Uint256 ||
param.type === SolidityTypes.Uint8 ||
param.type === SolidityTypes.Uint
) {
value = new BigNumber(value);
}
decodedParams[param.name] = value;
});
_.map(event.inputs, (param: Web3.EventParameter) => {
// Indexed parameters are stored in topics. Non-indexed ones in decodedData
let value = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++];
if (param.type === SolidityTypes.Address) {
value = AbiDecoder._padZeros(new BigNumber(value).toString(16));
} else if (
param.type === SolidityTypes.Uint256 ||
param.type === SolidityTypes.Uint8 ||
param.type === SolidityTypes.Uint
) {
value = new BigNumber(value);
}
decodedParams[param.name] = value;
});
return {
...log,
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);
}
return {
...log,
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);
}
}

View File

@@ -11,25 +11,25 @@ import { ECSignature } from '../types';
import { signatureUtils } from '../utils/signature_utils';
export const assert = {
...sharedAssert,
isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) {
const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress);
this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`);
},
async isSenderAddressAsync(
variableName: string,
senderAddressHex: string,
web3Wrapper: Web3Wrapper,
): Promise<void> {
sharedAssert.isETHAddressHex(variableName, senderAddressHex);
const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex);
sharedAssert.assert(
isSenderAddressAvailable,
`Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`,
);
},
async isUserAddressAvailableAsync(web3Wrapper: Web3Wrapper): Promise<void> {
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider');
},
...sharedAssert,
isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) {
const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress);
this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`);
},
async isSenderAddressAsync(
variableName: string,
senderAddressHex: string,
web3Wrapper: Web3Wrapper,
): Promise<void> {
sharedAssert.isETHAddressHex(variableName, senderAddressHex);
const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex);
sharedAssert.assert(
isSenderAddressAvailable,
`Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`,
);
},
async isUserAddressAvailableAsync(web3Wrapper: Web3Wrapper): Promise<void> {
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider');
},
};

View File

@@ -1,12 +1,12 @@
import { BigNumber } from '@0xproject/utils';
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
TESTRPC_NETWORK_ID: 50,
MAX_DIGITS_IN_UNSIGNED_256_INT: 78,
INVALID_JUMP_PATTERN: 'invalid JUMP at',
OUT_OF_GAS_PATTERN: 'out of gas',
INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string',
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
DEFAULT_BLOCK_POLLING_INTERVAL: 1000,
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
TESTRPC_NETWORK_ID: 50,
MAX_DIGITS_IN_UNSIGNED_256_INT: 78,
INVALID_JUMP_PATTERN: 'invalid JUMP at',
OUT_OF_GAS_PATTERN: 'out of gas',
INVALID_TAKER_FORMAT: 'instance.taker is not of a type(s) string',
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
DEFAULT_BLOCK_POLLING_INTERVAL: 1000,
};

View File

@@ -7,85 +7,85 @@ import { constants } from './constants';
type ErrorTransformer = (err: Error) => Error;
const contractCallErrorTransformer = (error: Error) => {
if (_.includes(error.message, constants.INVALID_JUMP_PATTERN)) {
return new Error(ZeroExError.InvalidJump);
}
if (_.includes(error.message, constants.OUT_OF_GAS_PATTERN)) {
return new Error(ZeroExError.OutOfGas);
}
return error;
if (_.includes(error.message, constants.INVALID_JUMP_PATTERN)) {
return new Error(ZeroExError.InvalidJump);
}
if (_.includes(error.message, constants.OUT_OF_GAS_PATTERN)) {
return new Error(ZeroExError.OutOfGas);
}
return error;
};
const schemaErrorTransformer = (error: Error) => {
if (_.includes(error.message, constants.INVALID_TAKER_FORMAT)) {
const errMsg =
'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
return new Error(errMsg);
}
return error;
if (_.includes(error.message, constants.INVALID_TAKER_FORMAT)) {
const errMsg =
'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
return new Error(errMsg);
}
return error;
};
/**
* Source: https://stackoverflow.com/a/29837695/3546986
*/
const asyncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => {
const asyncErrorHandlingDecorator = (
target: object,
key: string | symbol,
descriptor: TypedPropertyDescriptor<AsyncMethod>,
) => {
const originalMethod = descriptor.value as AsyncMethod;
const asyncErrorHandlingDecorator = (
target: object,
key: string | symbol,
descriptor: TypedPropertyDescriptor<AsyncMethod>,
) => {
const originalMethod = descriptor.value as AsyncMethod;
// Do not use arrow syntax here. Use a function expression in
// order to use the correct value of `this` in this method
// tslint:disable-next-line:only-arrow-functions
descriptor.value = async function(...args: any[]) {
try {
const result = await originalMethod.apply(this, args);
return result;
} catch (error) {
const transformedError = errorTransformer(error);
throw transformedError;
}
};
// Do not use arrow syntax here. Use a function expression in
// order to use the correct value of `this` in this method
// tslint:disable-next-line:only-arrow-functions
descriptor.value = async function(...args: any[]) {
try {
const result = await originalMethod.apply(this, args);
return result;
} catch (error) {
const transformedError = errorTransformer(error);
throw transformedError;
}
};
return descriptor;
};
return descriptor;
};
return asyncErrorHandlingDecorator;
return asyncErrorHandlingDecorator;
};
const syncErrorHandlerFactory = (errorTransformer: ErrorTransformer) => {
const syncErrorHandlingDecorator = (
target: object,
key: string | symbol,
descriptor: TypedPropertyDescriptor<SyncMethod>,
) => {
const originalMethod = descriptor.value as SyncMethod;
const syncErrorHandlingDecorator = (
target: object,
key: string | symbol,
descriptor: TypedPropertyDescriptor<SyncMethod>,
) => {
const originalMethod = descriptor.value as SyncMethod;
// Do not use arrow syntax here. Use a function expression in
// order to use the correct value of `this` in this method
// tslint:disable-next-line:only-arrow-functions
descriptor.value = function(...args: any[]) {
try {
const result = originalMethod.apply(this, args);
return result;
} catch (error) {
const transformedError = errorTransformer(error);
throw transformedError;
}
};
// Do not use arrow syntax here. Use a function expression in
// order to use the correct value of `this` in this method
// tslint:disable-next-line:only-arrow-functions
descriptor.value = function(...args: any[]) {
try {
const result = originalMethod.apply(this, args);
return result;
} catch (error) {
const transformedError = errorTransformer(error);
throw transformedError;
}
};
return descriptor;
};
return descriptor;
};
return syncErrorHandlingDecorator;
return syncErrorHandlingDecorator;
};
// _.flow(f, g) = f ∘ g
const zeroExErrorTransformer = _.flow(schemaErrorTransformer, contractCallErrorTransformer);
export const decorators = {
asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer),
syncZeroExErrorHandler: syncErrorHandlerFactory(zeroExErrorTransformer),
asyncZeroExErrorHandler: asyncErrorHandlerFactory(zeroExErrorTransformer),
syncZeroExErrorHandler: syncErrorHandlerFactory(zeroExErrorTransformer),
};

View File

@@ -6,101 +6,101 @@ import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allow
import { BlockParamLiteral, ExchangeContractErrs, TradeSide, TransferType } from '../types';
enum FailureReason {
Balance = 'balance',
ProxyAllowance = 'proxyAllowance',
Balance = 'balance',
ProxyAllowance = 'proxyAllowance',
}
const ERR_MSG_MAPPING = {
[FailureReason.Balance]: {
[TradeSide.Maker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance,
},
[TradeSide.Taker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance,
},
},
[FailureReason.ProxyAllowance]: {
[TradeSide.Maker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance,
},
[TradeSide.Taker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance,
},
},
[FailureReason.Balance]: {
[TradeSide.Maker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance,
},
[TradeSide.Taker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance,
},
},
[FailureReason.ProxyAllowance]: {
[TradeSide.Maker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance,
},
[TradeSide.Taker]: {
[TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance,
[TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance,
},
},
};
export class ExchangeTransferSimulator {
private _store: BalanceAndProxyAllowanceLazyStore;
private _UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber;
private static _throwValidationError(
failureReason: FailureReason,
tradeSide: TradeSide,
transferType: TransferType,
): never {
const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType];
throw new Error(errMsg);
}
constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) {
this._store = new BalanceAndProxyAllowanceLazyStore(token, defaultBlock);
this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
}
/**
* Simulates transferFrom call performed by a proxy
* @param tokenAddress Address of the token to be transferred
* @param from Owner of the transferred tokens
* @param to Recipient of the transferred tokens
* @param amountInBaseUnits The amount of tokens being transferred
* @param tradeSide Is Maker/Taker transferring
* @param transferType Is it a fee payment or a value transfer
*/
public async transferFromAsync(
tokenAddress: string,
from: string,
to: string,
amountInBaseUnits: BigNumber,
tradeSide: TradeSide,
transferType: TransferType,
): Promise<void> {
const balance = await this._store.getBalanceAsync(tokenAddress, from);
const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, from);
if (proxyAllowance.lessThan(amountInBaseUnits)) {
ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
}
if (balance.lessThan(amountInBaseUnits)) {
ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
}
await this._decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits);
await this._decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits);
await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
}
private async _decreaseProxyAllowanceAsync(
tokenAddress: string,
userAddress: string,
amountInBaseUnits: BigNumber,
): Promise<void> {
const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, userAddress);
if (!proxyAllowance.eq(this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
this._store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits));
}
}
private async _increaseBalanceAsync(
tokenAddress: string,
userAddress: string,
amountInBaseUnits: BigNumber,
): Promise<void> {
const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
this._store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits));
}
private async _decreaseBalanceAsync(
tokenAddress: string,
userAddress: string,
amountInBaseUnits: BigNumber,
): Promise<void> {
const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
this._store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits));
}
private _store: BalanceAndProxyAllowanceLazyStore;
private _UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber;
private static _throwValidationError(
failureReason: FailureReason,
tradeSide: TradeSide,
transferType: TransferType,
): never {
const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType];
throw new Error(errMsg);
}
constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) {
this._store = new BalanceAndProxyAllowanceLazyStore(token, defaultBlock);
this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
}
/**
* Simulates transferFrom call performed by a proxy
* @param tokenAddress Address of the token to be transferred
* @param from Owner of the transferred tokens
* @param to Recipient of the transferred tokens
* @param amountInBaseUnits The amount of tokens being transferred
* @param tradeSide Is Maker/Taker transferring
* @param transferType Is it a fee payment or a value transfer
*/
public async transferFromAsync(
tokenAddress: string,
from: string,
to: string,
amountInBaseUnits: BigNumber,
tradeSide: TradeSide,
transferType: TransferType,
): Promise<void> {
const balance = await this._store.getBalanceAsync(tokenAddress, from);
const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, from);
if (proxyAllowance.lessThan(amountInBaseUnits)) {
ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
}
if (balance.lessThan(amountInBaseUnits)) {
ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
}
await this._decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits);
await this._decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits);
await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
}
private async _decreaseProxyAllowanceAsync(
tokenAddress: string,
userAddress: string,
amountInBaseUnits: BigNumber,
): Promise<void> {
const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, userAddress);
if (!proxyAllowance.eq(this._UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
this._store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits));
}
}
private async _increaseBalanceAsync(
tokenAddress: string,
userAddress: string,
amountInBaseUnits: BigNumber,
): Promise<void> {
const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
this._store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits));
}
private async _decreaseBalanceAsync(
tokenAddress: string,
userAddress: string,
amountInBaseUnits: BigNumber,
): Promise<void> {
const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
this._store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits));
}
}

View File

@@ -9,79 +9,79 @@ import { BlockRange, ContractEvents, IndexedFilterValues } from '../types';
const TOPIC_LENGTH = 32;
export const filterUtils = {
generateUUID(): string {
return uuid();
},
getFilter(
address: string,
eventName: ContractEvents,
indexFilterValues: IndexedFilterValues,
abi: Web3.ContractAbi,
blockRange?: BlockRange,
): Web3.FilterObject {
const eventAbi = _.find(abi, { name: eventName }) as Web3.EventAbi;
const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName);
const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature));
const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues);
const topics = [topicForEventSignature, ...topicsForIndexedArgs];
let filter: Web3.FilterObject = {
address,
topics,
};
if (!_.isUndefined(blockRange)) {
filter = {
...blockRange,
...filter,
};
}
return filter;
},
getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string {
const types = _.map(eventAbi.inputs, 'type');
const signature = `${eventAbi.name}(${types.join(',')})`;
return signature;
},
getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string | null> {
const topics: Array<string | null> = [];
for (const eventInput of abi.inputs) {
if (!eventInput.indexed) {
continue;
}
if (_.isUndefined(indexFilterValues[eventInput.name])) {
// Null is a wildcard topic in a JSON-RPC call
topics.push(null);
} else {
const value = indexFilterValues[eventInput.name] as string;
const buffer = ethUtil.toBuffer(value);
const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH);
const topic = ethUtil.bufferToHex(paddedBuffer);
topics.push(topic);
}
}
return topics;
},
matchesFilter(log: Web3.LogEntry, filter: Web3.FilterObject): boolean {
if (!_.isUndefined(filter.address) && log.address !== filter.address) {
return false;
}
if (!_.isUndefined(filter.topics)) {
return filterUtils.matchesTopics(log.topics, filter.topics);
}
return true;
},
matchesTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean {
const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils));
const matchesTopics = _.every(matchesTopic);
return matchesTopics;
},
matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean {
if (_.isArray(filterTopic)) {
return _.includes(filterTopic, logTopic);
}
if (_.isString(filterTopic)) {
return filterTopic === logTopic;
}
// null topic is a wildcard
return true;
},
generateUUID(): string {
return uuid();
},
getFilter(
address: string,
eventName: ContractEvents,
indexFilterValues: IndexedFilterValues,
abi: Web3.ContractAbi,
blockRange?: BlockRange,
): Web3.FilterObject {
const eventAbi = _.find(abi, { name: eventName }) as Web3.EventAbi;
const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName);
const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature));
const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues);
const topics = [topicForEventSignature, ...topicsForIndexedArgs];
let filter: Web3.FilterObject = {
address,
topics,
};
if (!_.isUndefined(blockRange)) {
filter = {
...blockRange,
...filter,
};
}
return filter;
},
getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string {
const types = _.map(eventAbi.inputs, 'type');
const signature = `${eventAbi.name}(${types.join(',')})`;
return signature;
},
getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string | null> {
const topics: Array<string | null> = [];
for (const eventInput of abi.inputs) {
if (!eventInput.indexed) {
continue;
}
if (_.isUndefined(indexFilterValues[eventInput.name])) {
// Null is a wildcard topic in a JSON-RPC call
topics.push(null);
} else {
const value = indexFilterValues[eventInput.name] as string;
const buffer = ethUtil.toBuffer(value);
const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH);
const topic = ethUtil.bufferToHex(paddedBuffer);
topics.push(topic);
}
}
return topics;
},
matchesFilter(log: Web3.LogEntry, filter: Web3.FilterObject): boolean {
if (!_.isUndefined(filter.address) && log.address !== filter.address) {
return false;
}
if (!_.isUndefined(filter.topics)) {
return filterUtils.matchesTopics(log.topics, filter.topics);
}
return true;
},
matchesTopics(logTopics: string[], filterTopics: Array<string[] | string | null>): boolean {
const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils));
const matchesTopics = _.every(matchesTopic);
return matchesTopics;
},
matchesTopic(logTopic: string, filterTopic: string[] | string | null): boolean {
if (_.isArray(filterTopic)) {
return _.includes(filterTopic, logTopic);
}
if (_.isString(filterTopic)) {
return filterTopic === logTopic;
}
// null topic is a wildcard
return true;
},
};

View File

@@ -7,138 +7,138 @@ import { RemainingFillableCalculator } from '../order_watcher/remaining_fillable
import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store';
import { OrderFilledCancelledLazyStore } from '../stores/order_filled_cancelled_lazy_store';
import {
ExchangeContractErrs,
OrderRelevantState,
OrderState,
OrderStateInvalid,
OrderStateValid,
SignedOrder,
ExchangeContractErrs,
OrderRelevantState,
OrderState,
OrderStateInvalid,
OrderStateValid,
SignedOrder,
} from '../types';
const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
export class OrderStateUtils {
private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void {
const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add(
orderRelevantState.filledTakerTokenAmount,
);
const availableTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
if (availableTakerTokenAmount.eq(0)) {
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
}
private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void {
const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add(
orderRelevantState.filledTakerTokenAmount,
);
const availableTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
if (availableTakerTokenAmount.eq(0)) {
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
}
if (orderRelevantState.makerBalance.eq(0)) {
throw new Error(ExchangeContractErrs.InsufficientMakerBalance);
}
if (orderRelevantState.makerProxyAllowance.eq(0)) {
throw new Error(ExchangeContractErrs.InsufficientMakerAllowance);
}
if (!signedOrder.makerFee.eq(0)) {
if (orderRelevantState.makerFeeBalance.eq(0)) {
throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance);
}
if (orderRelevantState.makerFeeProxyAllowance.eq(0)) {
throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance);
}
}
const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount
.dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
.dividedBy(signedOrder.makerTokenAmount);
if (
orderRelevantState.remainingFillableTakerTokenAmount.lessThan(
minFillableTakerTokenAmountWithinNoRoundingErrorRange,
)
) {
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
}
}
constructor(
balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore,
orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore,
) {
this._balanceAndProxyAllowanceLazyStore = balanceAndProxyAllowanceLazyStore;
this._orderFilledCancelledLazyStore = orderFilledCancelledLazyStore;
}
public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
try {
OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState);
const orderState: OrderStateValid = {
isValid: true,
orderHash,
orderRelevantState,
};
return orderState;
} catch (err) {
const orderState: OrderStateInvalid = {
isValid: false,
orderHash,
error: err.message,
};
return orderState;
}
}
public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
// HACK: We access the private property here but otherwise the interface will be less nice.
// If we pass it from the instantiator - there is no opportunity to get it there
// because JS doesn't support async constructors.
// Moreover - it's cached under the hood so it's equivalent to an async constructor.
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
const zrxTokenAddress = exchange.getZRXTokenAddress();
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
const makerBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync(
signedOrder.makerTokenAddress,
signedOrder.maker,
);
const makerProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync(
signedOrder.makerTokenAddress,
signedOrder.maker,
);
const makerFeeBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync(
zrxTokenAddress,
signedOrder.maker,
);
const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync(
zrxTokenAddress,
signedOrder.maker,
);
const filledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getFilledTakerAmountAsync(orderHash);
const cancelledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getCancelledTakerAmountAsync(
orderHash,
);
const unavailableTakerTokenAmount = await exchange.getUnavailableTakerAmountAsync(orderHash);
const totalMakerTokenAmount = signedOrder.makerTokenAmount;
const totalTakerTokenAmount = signedOrder.takerTokenAmount;
const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount);
const remainingMakerTokenAmount = remainingTakerTokenAmount
.times(totalMakerTokenAmount)
.dividedToIntegerBy(totalTakerTokenAmount);
const transferrableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
const transferrableFeeTokenAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]);
if (orderRelevantState.makerBalance.eq(0)) {
throw new Error(ExchangeContractErrs.InsufficientMakerBalance);
}
if (orderRelevantState.makerProxyAllowance.eq(0)) {
throw new Error(ExchangeContractErrs.InsufficientMakerAllowance);
}
if (!signedOrder.makerFee.eq(0)) {
if (orderRelevantState.makerFeeBalance.eq(0)) {
throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance);
}
if (orderRelevantState.makerFeeProxyAllowance.eq(0)) {
throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance);
}
}
const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount
.dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
.dividedBy(signedOrder.makerTokenAmount);
if (
orderRelevantState.remainingFillableTakerTokenAmount.lessThan(
minFillableTakerTokenAmountWithinNoRoundingErrorRange,
)
) {
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
}
}
constructor(
balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore,
orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore,
) {
this._balanceAndProxyAllowanceLazyStore = balanceAndProxyAllowanceLazyStore;
this._orderFilledCancelledLazyStore = orderFilledCancelledLazyStore;
}
public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
try {
OrderStateUtils._validateIfOrderIsValid(signedOrder, orderRelevantState);
const orderState: OrderStateValid = {
isValid: true,
orderHash,
orderRelevantState,
};
return orderState;
} catch (err) {
const orderState: OrderStateInvalid = {
isValid: false,
orderHash,
error: err.message,
};
return orderState;
}
}
public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
// HACK: We access the private property here but otherwise the interface will be less nice.
// If we pass it from the instantiator - there is no opportunity to get it there
// because JS doesn't support async constructors.
// Moreover - it's cached under the hood so it's equivalent to an async constructor.
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
const zrxTokenAddress = exchange.getZRXTokenAddress();
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
const makerBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync(
signedOrder.makerTokenAddress,
signedOrder.maker,
);
const makerProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync(
signedOrder.makerTokenAddress,
signedOrder.maker,
);
const makerFeeBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync(
zrxTokenAddress,
signedOrder.maker,
);
const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync(
zrxTokenAddress,
signedOrder.maker,
);
const filledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getFilledTakerAmountAsync(orderHash);
const cancelledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getCancelledTakerAmountAsync(
orderHash,
);
const unavailableTakerTokenAmount = await exchange.getUnavailableTakerAmountAsync(orderHash);
const totalMakerTokenAmount = signedOrder.makerTokenAmount;
const totalTakerTokenAmount = signedOrder.takerTokenAmount;
const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount);
const remainingMakerTokenAmount = remainingTakerTokenAmount
.times(totalMakerTokenAmount)
.dividedToIntegerBy(totalTakerTokenAmount);
const transferrableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
const transferrableFeeTokenAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]);
const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress;
const remainingFillableCalculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableFeeTokenAmount,
remainingMakerTokenAmount,
);
const remainingFillableMakerTokenAmount = remainingFillableCalculator.computeRemainingMakerFillable();
const remainingFillableTakerTokenAmount = remainingFillableCalculator.computeRemainingTakerFillable();
const orderRelevantState = {
makerBalance,
makerProxyAllowance,
makerFeeBalance,
makerFeeProxyAllowance,
filledTakerTokenAmount,
cancelledTakerTokenAmount,
remainingFillableMakerTokenAmount,
remainingFillableTakerTokenAmount,
};
return orderRelevantState;
}
const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress;
const remainingFillableCalculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableFeeTokenAmount,
remainingMakerTokenAmount,
);
const remainingFillableMakerTokenAmount = remainingFillableCalculator.computeRemainingMakerFillable();
const remainingFillableTakerTokenAmount = remainingFillableCalculator.computeRemainingTakerFillable();
const orderRelevantState = {
makerBalance,
makerProxyAllowance,
makerFeeBalance,
makerFeeProxyAllowance,
filledTakerTokenAmount,
cancelledTakerTokenAmount,
remainingFillableMakerTokenAmount,
remainingFillableTakerTokenAmount,
};
return orderRelevantState;
}
}

View File

@@ -10,207 +10,207 @@ import { utils } from '../utils/utils';
import { ExchangeTransferSimulator } from './exchange_transfer_simulator';
export class OrderValidationUtils {
private _exchangeWrapper: ExchangeWrapper;
public static validateCancelOrderThrowIfInvalid(
order: Order,
cancelTakerTokenAmount: BigNumber,
unavailableTakerTokenAmount: BigNumber,
): void {
if (cancelTakerTokenAmount.eq(0)) {
throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
}
if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) {
throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
}
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
throw new Error(ExchangeContractErrs.OrderCancelExpired);
}
}
public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber,
senderAddress: string,
zrxTokenAddress: string,
): Promise<void> {
const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount(
fillTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.makerTokenAmount,
);
await exchangeTradeEmulator.transferFromAsync(
signedOrder.makerTokenAddress,
signedOrder.maker,
senderAddress,
fillMakerTokenAmount,
TradeSide.Maker,
TransferType.Trade,
);
await exchangeTradeEmulator.transferFromAsync(
signedOrder.takerTokenAddress,
senderAddress,
signedOrder.maker,
fillTakerTokenAmount,
TradeSide.Taker,
TransferType.Trade,
);
const makerFeeAmount = OrderValidationUtils._getPartialAmount(
fillTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.makerFee,
);
await exchangeTradeEmulator.transferFromAsync(
zrxTokenAddress,
signedOrder.maker,
signedOrder.feeRecipient,
makerFeeAmount,
TradeSide.Maker,
TransferType.Fee,
);
const takerFeeAmount = OrderValidationUtils._getPartialAmount(
fillTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.takerFee,
);
await exchangeTradeEmulator.transferFromAsync(
zrxTokenAddress,
senderAddress,
signedOrder.feeRecipient,
takerFeeAmount,
TradeSide.Taker,
TransferType.Fee,
);
}
private static _validateRemainingFillAmountNotZeroOrThrow(
takerTokenAmount: BigNumber,
unavailableTakerTokenAmount: BigNumber,
) {
if (takerTokenAmount.eq(unavailableTakerTokenAmount)) {
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
}
}
private static _validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) {
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
throw new Error(ExchangeContractErrs.OrderFillExpired);
}
}
private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
const fillMakerTokenAmount = numerator
.mul(target)
.div(denominator)
.round(0);
return fillMakerTokenAmount;
}
constructor(exchangeWrapper: ExchangeWrapper) {
this._exchangeWrapper = exchangeWrapper;
}
public async validateOrderFillableOrThrowAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
zrxTokenAddress: string,
expectedFillTakerTokenAmount?: BigNumber,
): Promise<void> {
const orderHash = utils.getOrderHashHex(signedOrder);
const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerTokenAmount,
unavailableTakerTokenAmount,
);
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
fillTakerTokenAmount = expectedFillTakerTokenAmount;
}
const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount(
fillTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.makerTokenAmount,
);
await exchangeTradeEmulator.transferFromAsync(
signedOrder.makerTokenAddress,
signedOrder.maker,
signedOrder.taker,
fillMakerTokenAmount,
TradeSide.Maker,
TransferType.Trade,
);
const makerFeeAmount = OrderValidationUtils._getPartialAmount(
fillTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.makerFee,
);
await exchangeTradeEmulator.transferFromAsync(
zrxTokenAddress,
signedOrder.maker,
signedOrder.feeRecipient,
makerFeeAmount,
TradeSide.Maker,
TransferType.Fee,
);
}
public async validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber,
takerAddress: string,
zrxTokenAddress: string,
): Promise<BigNumber> {
if (fillTakerTokenAmount.eq(0)) {
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);
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerTokenAmount,
unavailableTakerTokenAmount,
);
if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) {
throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
}
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount)
? remainingTakerTokenAmount
: fillTakerTokenAmount;
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTradeEmulator,
signedOrder,
filledTakerTokenAmount,
takerAddress,
zrxTokenAddress,
);
private _exchangeWrapper: ExchangeWrapper;
public static validateCancelOrderThrowIfInvalid(
order: Order,
cancelTakerTokenAmount: BigNumber,
unavailableTakerTokenAmount: BigNumber,
): void {
if (cancelTakerTokenAmount.eq(0)) {
throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
}
if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) {
throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
}
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
throw new Error(ExchangeContractErrs.OrderCancelExpired);
}
}
public static async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber,
senderAddress: string,
zrxTokenAddress: string,
): Promise<void> {
const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount(
fillTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.makerTokenAmount,
);
await exchangeTradeEmulator.transferFromAsync(
signedOrder.makerTokenAddress,
signedOrder.maker,
senderAddress,
fillMakerTokenAmount,
TradeSide.Maker,
TransferType.Trade,
);
await exchangeTradeEmulator.transferFromAsync(
signedOrder.takerTokenAddress,
senderAddress,
signedOrder.maker,
fillTakerTokenAmount,
TradeSide.Taker,
TransferType.Trade,
);
const makerFeeAmount = OrderValidationUtils._getPartialAmount(
fillTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.makerFee,
);
await exchangeTradeEmulator.transferFromAsync(
zrxTokenAddress,
signedOrder.maker,
signedOrder.feeRecipient,
makerFeeAmount,
TradeSide.Maker,
TransferType.Fee,
);
const takerFeeAmount = OrderValidationUtils._getPartialAmount(
fillTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.takerFee,
);
await exchangeTradeEmulator.transferFromAsync(
zrxTokenAddress,
senderAddress,
signedOrder.feeRecipient,
takerFeeAmount,
TradeSide.Taker,
TransferType.Fee,
);
}
private static _validateRemainingFillAmountNotZeroOrThrow(
takerTokenAmount: BigNumber,
unavailableTakerTokenAmount: BigNumber,
) {
if (takerTokenAmount.eq(unavailableTakerTokenAmount)) {
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
}
}
private static _validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) {
const currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
throw new Error(ExchangeContractErrs.OrderFillExpired);
}
}
private static _getPartialAmount(numerator: BigNumber, denominator: BigNumber, target: BigNumber): BigNumber {
const fillMakerTokenAmount = numerator
.mul(target)
.div(denominator)
.round(0);
return fillMakerTokenAmount;
}
constructor(exchangeWrapper: ExchangeWrapper) {
this._exchangeWrapper = exchangeWrapper;
}
public async validateOrderFillableOrThrowAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
zrxTokenAddress: string,
expectedFillTakerTokenAmount?: BigNumber,
): Promise<void> {
const orderHash = utils.getOrderHashHex(signedOrder);
const unavailableTakerTokenAmount = await this._exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerTokenAmount,
unavailableTakerTokenAmount,
);
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
fillTakerTokenAmount = expectedFillTakerTokenAmount;
}
const fillMakerTokenAmount = OrderValidationUtils._getPartialAmount(
fillTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.makerTokenAmount,
);
await exchangeTradeEmulator.transferFromAsync(
signedOrder.makerTokenAddress,
signedOrder.maker,
signedOrder.taker,
fillMakerTokenAmount,
TradeSide.Maker,
TransferType.Trade,
);
const makerFeeAmount = OrderValidationUtils._getPartialAmount(
fillTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.makerFee,
);
await exchangeTradeEmulator.transferFromAsync(
zrxTokenAddress,
signedOrder.maker,
signedOrder.feeRecipient,
makerFeeAmount,
TradeSide.Maker,
TransferType.Fee,
);
}
public async validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber,
takerAddress: string,
zrxTokenAddress: string,
): Promise<BigNumber> {
if (fillTakerTokenAmount.eq(0)) {
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);
OrderValidationUtils._validateRemainingFillAmountNotZeroOrThrow(
signedOrder.takerTokenAmount,
unavailableTakerTokenAmount,
);
if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) {
throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
}
OrderValidationUtils._validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount)
? remainingTakerTokenAmount
: fillTakerTokenAmount;
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTradeEmulator,
signedOrder,
filledTakerTokenAmount,
takerAddress,
zrxTokenAddress,
);
const wouldRoundingErrorOccur = await this._exchangeWrapper.isRoundingErrorAsync(
filledTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.makerTokenAmount,
);
if (wouldRoundingErrorOccur) {
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
}
return filledTakerTokenAmount;
}
public async validateFillOrKillOrderThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber,
takerAddress: string,
zrxTokenAddress: string,
): Promise<void> {
const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator,
signedOrder,
fillTakerTokenAmount,
takerAddress,
zrxTokenAddress,
);
if (filledTakerTokenAmount !== fillTakerTokenAmount) {
throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
}
}
const wouldRoundingErrorOccur = await this._exchangeWrapper.isRoundingErrorAsync(
filledTakerTokenAmount,
signedOrder.takerTokenAmount,
signedOrder.makerTokenAmount,
);
if (wouldRoundingErrorOccur) {
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
}
return filledTakerTokenAmount;
}
public async validateFillOrKillOrderThrowIfInvalidAsync(
exchangeTradeEmulator: ExchangeTransferSimulator,
signedOrder: SignedOrder,
fillTakerTokenAmount: BigNumber,
takerAddress: string,
zrxTokenAddress: string,
): Promise<void> {
const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
exchangeTradeEmulator,
signedOrder,
fillTakerTokenAmount,
takerAddress,
zrxTokenAddress,
);
if (filledTakerTokenAmount !== fillTakerTokenAmount) {
throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
}
}
}

View File

@@ -3,44 +3,44 @@ import * as ethUtil from 'ethereumjs-util';
import { ECSignature } from '../types';
export const signatureUtils = {
isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
const dataBuff = ethUtil.toBuffer(data);
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
try {
const pubKey = ethUtil.ecrecover(
msgHashBuff,
signature.v,
ethUtil.toBuffer(signature.r),
ethUtil.toBuffer(signature.s),
);
const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey));
return retrievedAddress === signerAddress;
} catch (err) {
return false;
}
},
parseSignatureHexAsVRS(signatureHex: string): ECSignature {
const signatureBuffer = ethUtil.toBuffer(signatureHex);
let v = signatureBuffer[0];
if (v < 27) {
v += 27;
}
const r = signatureBuffer.slice(1, 33);
const s = signatureBuffer.slice(33, 65);
const ecSignature: ECSignature = {
v,
r: ethUtil.bufferToHex(r),
s: ethUtil.bufferToHex(s),
};
return ecSignature;
},
parseSignatureHexAsRSV(signatureHex: string): ECSignature {
const { v, r, s } = ethUtil.fromRpcSig(signatureHex);
const ecSignature: ECSignature = {
v,
r: ethUtil.bufferToHex(r),
s: ethUtil.bufferToHex(s),
};
return ecSignature;
},
isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
const dataBuff = ethUtil.toBuffer(data);
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
try {
const pubKey = ethUtil.ecrecover(
msgHashBuff,
signature.v,
ethUtil.toBuffer(signature.r),
ethUtil.toBuffer(signature.s),
);
const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey));
return retrievedAddress === signerAddress;
} catch (err) {
return false;
}
},
parseSignatureHexAsVRS(signatureHex: string): ECSignature {
const signatureBuffer = ethUtil.toBuffer(signatureHex);
let v = signatureBuffer[0];
if (v < 27) {
v += 27;
}
const r = signatureBuffer.slice(1, 33);
const s = signatureBuffer.slice(33, 65);
const ecSignature: ECSignature = {
v,
r: ethUtil.bufferToHex(r),
s: ethUtil.bufferToHex(s),
};
return ecSignature;
},
parseSignatureHexAsRSV(signatureHex: string): ECSignature {
const { v, r, s } = ethUtil.fromRpcSig(signatureHex);
const ecSignature: ECSignature = {
v,
r: ethUtil.bufferToHex(r),
s: ethUtil.bufferToHex(s),
};
return ecSignature;
},
};

View File

@@ -7,62 +7,62 @@ import * as _ from 'lodash';
import { Order, SignedOrder, SolidityTypes } from '../types';
export const utils = {
/**
* Converts BigNumber instance to BN
* The only reason we convert to BN is to remain compatible with `ethABI. soliditySHA3` that
* expects values of Solidity type `uint` to be passed as type `BN`.
* We do not use BN anywhere else in the codebase.
*/
bigNumberToBN(value: BigNumber) {
return new BN(value.toString(), 10);
},
consoleLog(message: string): void {
// tslint:disable-next-line: no-console
console.log(message);
},
spawnSwitchErr(name: string, value: any): Error {
return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
},
getOrderHashHex(order: Order | SignedOrder): string {
const orderParts = [
{ value: order.exchangeContractAddress, type: SolidityTypes.Address },
{ value: order.maker, type: SolidityTypes.Address },
{ value: order.taker, type: SolidityTypes.Address },
{ value: order.makerTokenAddress, type: SolidityTypes.Address },
{ value: order.takerTokenAddress, type: SolidityTypes.Address },
{ value: order.feeRecipient, type: SolidityTypes.Address },
{
value: utils.bigNumberToBN(order.makerTokenAmount),
type: SolidityTypes.Uint256,
},
{
value: utils.bigNumberToBN(order.takerTokenAmount),
type: SolidityTypes.Uint256,
},
{
value: utils.bigNumberToBN(order.makerFee),
type: SolidityTypes.Uint256,
},
{
value: utils.bigNumberToBN(order.takerFee),
type: SolidityTypes.Uint256,
},
{
value: utils.bigNumberToBN(order.expirationUnixTimestampSec),
type: SolidityTypes.Uint256,
},
{ value: utils.bigNumberToBN(order.salt), type: SolidityTypes.Uint256 },
];
const types = _.map(orderParts, o => o.type);
const values = _.map(orderParts, o => o.value);
const hashBuff = ethABI.soliditySHA3(types, values);
const hashHex = ethUtil.bufferToHex(hashBuff);
return hashHex;
},
getCurrentUnixTimestampSec(): BigNumber {
return new BigNumber(Date.now() / 1000).round();
},
getCurrentUnixTimestampMs(): BigNumber {
return new BigNumber(Date.now());
},
/**
* Converts BigNumber instance to BN
* The only reason we convert to BN is to remain compatible with `ethABI. soliditySHA3` that
* expects values of Solidity type `uint` to be passed as type `BN`.
* We do not use BN anywhere else in the codebase.
*/
bigNumberToBN(value: BigNumber) {
return new BN(value.toString(), 10);
},
consoleLog(message: string): void {
// tslint:disable-next-line: no-console
console.log(message);
},
spawnSwitchErr(name: string, value: any): Error {
return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
},
getOrderHashHex(order: Order | SignedOrder): string {
const orderParts = [
{ value: order.exchangeContractAddress, type: SolidityTypes.Address },
{ value: order.maker, type: SolidityTypes.Address },
{ value: order.taker, type: SolidityTypes.Address },
{ value: order.makerTokenAddress, type: SolidityTypes.Address },
{ value: order.takerTokenAddress, type: SolidityTypes.Address },
{ value: order.feeRecipient, type: SolidityTypes.Address },
{
value: utils.bigNumberToBN(order.makerTokenAmount),
type: SolidityTypes.Uint256,
},
{
value: utils.bigNumberToBN(order.takerTokenAmount),
type: SolidityTypes.Uint256,
},
{
value: utils.bigNumberToBN(order.makerFee),
type: SolidityTypes.Uint256,
},
{
value: utils.bigNumberToBN(order.takerFee),
type: SolidityTypes.Uint256,
},
{
value: utils.bigNumberToBN(order.expirationUnixTimestampSec),
type: SolidityTypes.Uint256,
},
{ value: utils.bigNumberToBN(order.salt), type: SolidityTypes.Uint256 },
];
const types = _.map(orderParts, o => o.type);
const values = _.map(orderParts, o => o.value);
const hashBuff = ethABI.soliditySHA3(types, values);
const hashHex = ethUtil.bufferToHex(hashBuff);
return hashHex;
},
getCurrentUnixTimestampSec(): BigNumber {
return new BigNumber(Date.now() / 1000).round();
},
getCurrentUnixTimestampMs(): BigNumber {
return new BigNumber(Date.now());
},
};

View File

@@ -19,278 +19,278 @@ const expect = chai.expect;
const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false;
describe('ZeroEx library', () => {
const web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroEx = new ZeroEx(web3.currentProvider, config);
describe('#setProvider', () => {
it('overrides provider in nested web3s and invalidates contractInstances', async () => {
// Instantiate the contract instances with the current provider
await (zeroEx.exchange as any)._getExchangeContractAsync();
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
expect((zeroEx.exchange as any)._exchangeContractIfExists).to.not.be.undefined();
expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.not.be.undefined();
const web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroEx = new ZeroEx(web3.currentProvider, config);
describe('#setProvider', () => {
it('overrides provider in nested web3s and invalidates contractInstances', async () => {
// Instantiate the contract instances with the current provider
await (zeroEx.exchange as any)._getExchangeContractAsync();
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
expect((zeroEx.exchange as any)._exchangeContractIfExists).to.not.be.undefined();
expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.not.be.undefined();
const newProvider = web3Factory.getRpcProvider();
// Add property to newProvider so that we can differentiate it from old provider
(newProvider as any).zeroExTestId = 1;
zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID);
const newProvider = web3Factory.getRpcProvider();
// Add property to newProvider so that we can differentiate it from old provider
(newProvider as any).zeroExTestId = 1;
zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID);
// Check that contractInstances with old provider are removed after provider update
expect((zeroEx.exchange as any)._exchangeContractIfExists).to.be.undefined();
expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.be.undefined();
// Check that contractInstances with old provider are removed after provider update
expect((zeroEx.exchange as any)._exchangeContractIfExists).to.be.undefined();
expect((zeroEx.tokenRegistry as any)._tokenRegistryContractIfExists).to.be.undefined();
// Check that all nested web3 wrapper instances return the updated provider
const nestedWeb3WrapperProvider = (zeroEx as any)._web3Wrapper.getCurrentProvider();
expect(nestedWeb3WrapperProvider.zeroExTestId).to.be.a('number');
const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getCurrentProvider();
expect(exchangeWeb3WrapperProvider.zeroExTestId).to.be.a('number');
const tokenRegistryWeb3WrapperProvider = (zeroEx.tokenRegistry as any)._web3Wrapper.getCurrentProvider();
expect(tokenRegistryWeb3WrapperProvider.zeroExTestId).to.be.a('number');
});
});
describe('#isValidSignature', () => {
// The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes
// the length of the data is exactly 32 bytes. Thus for these tests, we use data of this size.
const dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
const signature = {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
};
const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
it("should return false if the data doesn't pertain to the signature & address", async () => {
expect(ZeroEx.isValidSignature('0x0', signature, address)).to.be.false();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync('0x0', signature, address),
).to.become(false);
});
it("should return false if the address doesn't pertain to the signature & data", async () => {
const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42';
expect(ZeroEx.isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(
dataHex,
signature,
validUnrelatedAddress,
),
).to.become(false);
});
it("should return false if the signature doesn't pertain to the dataHex & address", async () => {
const wrongSignature = _.assign({}, signature, { v: 28 });
expect(ZeroEx.isValidSignature(dataHex, wrongSignature, address)).to.be.false();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, wrongSignature, address),
).to.become(false);
});
it('should return true if the signature does pertain to the dataHex & address', async () => {
const isValidSignatureLocal = ZeroEx.isValidSignature(dataHex, signature, address);
expect(isValidSignatureLocal).to.be.true();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature, address),
).to.become(true);
});
});
describe('#generateSalt', () => {
it('generates different salts', () => {
const equal = ZeroEx.generatePseudoRandomSalt().eq(ZeroEx.generatePseudoRandomSalt());
expect(equal).to.be.false();
});
it('generates salt in range [0..2^256)', () => {
const salt = ZeroEx.generatePseudoRandomSalt();
expect(salt.greaterThanOrEqualTo(0)).to.be.true();
const twoPow256 = new BigNumber(2).pow(256);
expect(salt.lessThan(twoPow256)).to.be.true();
});
});
describe('#isValidOrderHash', () => {
it('returns false if the value is not a hex string', () => {
const isValid = ZeroEx.isValidOrderHash('not a hex');
expect(isValid).to.be.false();
});
it('returns false if the length is wrong', () => {
const isValid = ZeroEx.isValidOrderHash('0xdeadbeef');
expect(isValid).to.be.false();
});
it('returns true if order hash is correct', () => {
const isValid = ZeroEx.isValidOrderHash('0x' + Array(65).join('0'));
expect(isValid).to.be.true();
});
});
describe('#toUnitAmount', () => {
it('should throw if invalid baseUnit amount supplied as argument', () => {
const invalidBaseUnitAmount = new BigNumber(1000000000.4);
const decimals = 6;
expect(() => ZeroEx.toUnitAmount(invalidBaseUnitAmount, decimals)).to.throw(
'amount should be in baseUnits (no decimals), found value: 1000000000.4',
);
});
it('Should return the expected unit amount for the decimals passed in', () => {
const baseUnitAmount = new BigNumber(1000000000);
const decimals = 6;
const unitAmount = ZeroEx.toUnitAmount(baseUnitAmount, decimals);
const expectedUnitAmount = new BigNumber(1000);
expect(unitAmount).to.be.bignumber.equal(expectedUnitAmount);
});
});
describe('#toBaseUnitAmount', () => {
it('Should return the expected base unit amount for the decimals passed in', () => {
const unitAmount = new BigNumber(1000);
const decimals = 6;
const baseUnitAmount = ZeroEx.toBaseUnitAmount(unitAmount, decimals);
const expectedUnitAmount = new BigNumber(1000000000);
expect(baseUnitAmount).to.be.bignumber.equal(expectedUnitAmount);
});
it('should throw if unitAmount has more decimals then specified as the max decimal precision', () => {
const unitAmount = new BigNumber(0.823091);
const decimals = 5;
expect(() => ZeroEx.toBaseUnitAmount(unitAmount, decimals)).to.throw(
'Invalid unit amount: 0.823091 - Too many decimal places',
);
});
});
describe('#getOrderHashHex', () => {
const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83';
const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b';
const order: Order = {
maker: constants.NULL_ADDRESS,
taker: constants.NULL_ADDRESS,
feeRecipient: constants.NULL_ADDRESS,
makerTokenAddress: constants.NULL_ADDRESS,
takerTokenAddress: constants.NULL_ADDRESS,
exchangeContractAddress: fakeExchangeContractAddress,
salt: new BigNumber(0),
makerFee: new BigNumber(0),
takerFee: new BigNumber(0),
makerTokenAmount: new BigNumber(0),
takerTokenAmount: new BigNumber(0),
expirationUnixTimestampSec: new BigNumber(0),
};
it('calculates the order hash', async () => {
const orderHash = ZeroEx.getOrderHashHex(order);
expect(orderHash).to.be.equal(expectedOrderHash);
});
it('throws a readable error message if taker format is invalid', async () => {
const orderWithInvalidtakerFormat = {
...order,
taker: (null as any) as string,
};
const expectedErrorMessage =
'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
expect(() => ZeroEx.getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage);
});
});
describe('#signOrderHashAsync', () => {
let stubs: Sinon.SinonStub[] = [];
let makerAddress: string;
before(async () => {
const availableAddreses = await zeroEx.getAvailableAddressesAsync();
makerAddress = availableAddreses[0];
});
afterEach(() => {
// clean up any stubs after the test has completed
_.each(stubs, s => s.restore());
stubs = [];
});
it('Should return the correct ECSignature', async () => {
const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
const expectedECSignature = {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
};
const ecSignature = await zeroEx.signOrderHashAsync(
orderHash,
makerAddress,
SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
);
expect(ecSignature).to.deep.equal(expectedECSignature);
});
it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => {
const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004';
const signature =
'0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb021b';
const expectedECSignature = {
v: 27,
r: '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3',
s: '0x050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb02',
};
stubs = [
Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)),
Sinon.stub(ZeroEx, 'isValidSignature').returns(true),
];
// Check that all nested web3 wrapper instances return the updated provider
const nestedWeb3WrapperProvider = (zeroEx as any)._web3Wrapper.getCurrentProvider();
expect(nestedWeb3WrapperProvider.zeroExTestId).to.be.a('number');
const exchangeWeb3WrapperProvider = (zeroEx.exchange as any)._web3Wrapper.getCurrentProvider();
expect(exchangeWeb3WrapperProvider.zeroExTestId).to.be.a('number');
const tokenRegistryWeb3WrapperProvider = (zeroEx.tokenRegistry as any)._web3Wrapper.getCurrentProvider();
expect(tokenRegistryWeb3WrapperProvider.zeroExTestId).to.be.a('number');
});
});
describe('#isValidSignature', () => {
// The Exchange smart contract `isValidSignature` method only validates orderHashes and assumes
// the length of the data is exactly 32 bytes. Thus for these tests, we use data of this size.
const dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
const signature = {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
};
const address = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
it("should return false if the data doesn't pertain to the signature & address", async () => {
expect(ZeroEx.isValidSignature('0x0', signature, address)).to.be.false();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync('0x0', signature, address),
).to.become(false);
});
it("should return false if the address doesn't pertain to the signature & data", async () => {
const validUnrelatedAddress = '0x8b0292b11a196601ed2ce54b665cafeca0347d42';
expect(ZeroEx.isValidSignature(dataHex, signature, validUnrelatedAddress)).to.be.false();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(
dataHex,
signature,
validUnrelatedAddress,
),
).to.become(false);
});
it("should return false if the signature doesn't pertain to the dataHex & address", async () => {
const wrongSignature = _.assign({}, signature, { v: 28 });
expect(ZeroEx.isValidSignature(dataHex, wrongSignature, address)).to.be.false();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, wrongSignature, address),
).to.become(false);
});
it('should return true if the signature does pertain to the dataHex & address', async () => {
const isValidSignatureLocal = ZeroEx.isValidSignature(dataHex, signature, address);
expect(isValidSignatureLocal).to.be.true();
return expect(
(zeroEx.exchange as any)._isValidSignatureUsingContractCallAsync(dataHex, signature, address),
).to.become(true);
});
});
describe('#generateSalt', () => {
it('generates different salts', () => {
const equal = ZeroEx.generatePseudoRandomSalt().eq(ZeroEx.generatePseudoRandomSalt());
expect(equal).to.be.false();
});
it('generates salt in range [0..2^256)', () => {
const salt = ZeroEx.generatePseudoRandomSalt();
expect(salt.greaterThanOrEqualTo(0)).to.be.true();
const twoPow256 = new BigNumber(2).pow(256);
expect(salt.lessThan(twoPow256)).to.be.true();
});
});
describe('#isValidOrderHash', () => {
it('returns false if the value is not a hex string', () => {
const isValid = ZeroEx.isValidOrderHash('not a hex');
expect(isValid).to.be.false();
});
it('returns false if the length is wrong', () => {
const isValid = ZeroEx.isValidOrderHash('0xdeadbeef');
expect(isValid).to.be.false();
});
it('returns true if order hash is correct', () => {
const isValid = ZeroEx.isValidOrderHash('0x' + Array(65).join('0'));
expect(isValid).to.be.true();
});
});
describe('#toUnitAmount', () => {
it('should throw if invalid baseUnit amount supplied as argument', () => {
const invalidBaseUnitAmount = new BigNumber(1000000000.4);
const decimals = 6;
expect(() => ZeroEx.toUnitAmount(invalidBaseUnitAmount, decimals)).to.throw(
'amount should be in baseUnits (no decimals), found value: 1000000000.4',
);
});
it('Should return the expected unit amount for the decimals passed in', () => {
const baseUnitAmount = new BigNumber(1000000000);
const decimals = 6;
const unitAmount = ZeroEx.toUnitAmount(baseUnitAmount, decimals);
const expectedUnitAmount = new BigNumber(1000);
expect(unitAmount).to.be.bignumber.equal(expectedUnitAmount);
});
});
describe('#toBaseUnitAmount', () => {
it('Should return the expected base unit amount for the decimals passed in', () => {
const unitAmount = new BigNumber(1000);
const decimals = 6;
const baseUnitAmount = ZeroEx.toBaseUnitAmount(unitAmount, decimals);
const expectedUnitAmount = new BigNumber(1000000000);
expect(baseUnitAmount).to.be.bignumber.equal(expectedUnitAmount);
});
it('should throw if unitAmount has more decimals then specified as the max decimal precision', () => {
const unitAmount = new BigNumber(0.823091);
const decimals = 5;
expect(() => ZeroEx.toBaseUnitAmount(unitAmount, decimals)).to.throw(
'Invalid unit amount: 0.823091 - Too many decimal places',
);
});
});
describe('#getOrderHashHex', () => {
const expectedOrderHash = '0x39da987067a3c9e5f1617694f1301326ba8c8b0498ebef5df4863bed394e3c83';
const fakeExchangeContractAddress = '0xb69e673309512a9d726f87304c6984054f87a93b';
const order: Order = {
maker: constants.NULL_ADDRESS,
taker: constants.NULL_ADDRESS,
feeRecipient: constants.NULL_ADDRESS,
makerTokenAddress: constants.NULL_ADDRESS,
takerTokenAddress: constants.NULL_ADDRESS,
exchangeContractAddress: fakeExchangeContractAddress,
salt: new BigNumber(0),
makerFee: new BigNumber(0),
takerFee: new BigNumber(0),
makerTokenAmount: new BigNumber(0),
takerTokenAmount: new BigNumber(0),
expirationUnixTimestampSec: new BigNumber(0),
};
it('calculates the order hash', async () => {
const orderHash = ZeroEx.getOrderHashHex(order);
expect(orderHash).to.be.equal(expectedOrderHash);
});
it('throws a readable error message if taker format is invalid', async () => {
const orderWithInvalidtakerFormat = {
...order,
taker: (null as any) as string,
};
const expectedErrorMessage =
'Order taker must be of type string. If you want anyone to be able to fill an order - pass ZeroEx.NULL_ADDRESS';
expect(() => ZeroEx.getOrderHashHex(orderWithInvalidtakerFormat)).to.throw(expectedErrorMessage);
});
});
describe('#signOrderHashAsync', () => {
let stubs: Sinon.SinonStub[] = [];
let makerAddress: string;
before(async () => {
const availableAddreses = await zeroEx.getAvailableAddressesAsync();
makerAddress = availableAddreses[0];
});
afterEach(() => {
// clean up any stubs after the test has completed
_.each(stubs, s => s.restore());
stubs = [];
});
it('Should return the correct ECSignature', async () => {
const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
const expectedECSignature = {
v: 27,
r: '0x61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc33',
s: '0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
};
const ecSignature = await zeroEx.signOrderHashAsync(
orderHash,
makerAddress,
SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
);
expect(ecSignature).to.deep.equal(expectedECSignature);
});
it('should return the correct ECSignature for signatureHex concatenated as R + S + V', async () => {
const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004';
const signature =
'0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb021b';
const expectedECSignature = {
v: 27,
r: '0x22109d11d79cb8bf96ed88625e1cd9558800c4073332a9a02857499883ee5ce3',
s: '0x050aa3cc1f2c435e67e114cdce54b9527b4f50548342401bc5d2b77adbdacb02',
};
stubs = [
Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)),
Sinon.stub(ZeroEx, 'isValidSignature').returns(true),
];
const ecSignature = await zeroEx.signOrderHashAsync(
orderHash,
makerAddress,
SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
);
expect(ecSignature).to.deep.equal(expectedECSignature);
});
it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => {
const orderHash = '0xc793e33ffded933b76f2f48d9aa3339fc090399d5e7f5dec8d3660f5480793f7';
const signature =
'0x1bc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee02dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960';
const expectedECSignature = {
v: 27,
r: '0xc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee0',
s: '0x2dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960',
};
stubs = [
Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)),
Sinon.stub(ZeroEx, 'isValidSignature').returns(true),
];
const ecSignature = await zeroEx.signOrderHashAsync(
orderHash,
makerAddress,
SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
);
expect(ecSignature).to.deep.equal(expectedECSignature);
});
it('should return the correct ECSignature for signatureHex concatenated as V + R + S', async () => {
const orderHash = '0xc793e33ffded933b76f2f48d9aa3339fc090399d5e7f5dec8d3660f5480793f7';
const signature =
'0x1bc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee02dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960';
const expectedECSignature = {
v: 27,
r: '0xc80bedc6756722672753413efdd749b5adbd4fd552595f59c13427407ee9aee0',
s: '0x2dea66f25a608bbae457e020fb6decb763deb8b7192abab624997242da248960',
};
stubs = [
Sinon.stub((zeroEx as any)._web3Wrapper, 'signTransactionAsync').returns(Promise.resolve(signature)),
Sinon.stub(ZeroEx, 'isValidSignature').returns(true),
];
const ecSignature = await zeroEx.signOrderHashAsync(
orderHash,
makerAddress,
SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
);
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 = zeroEx.proxy.getContractAddress();
const txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(zrxTokenAddress, coinbase);
const txReceiptWithDecodedLogs = await zeroEx.awaitTransactionMinedAsync(txHash);
const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs<ApprovalContractEventArgs>;
expect(log.event).to.be.equal(TokenEvents.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);
});
});
describe('#config', () => {
it('allows to specify exchange contract address', async () => {
const zeroExConfig = {
exchangeContractAddress: ZeroEx.NULL_ADDRESS,
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, zeroExConfig);
expect(zeroExWithWrongExchangeAddress.exchange.getContractAddress()).to.be.equal(ZeroEx.NULL_ADDRESS);
});
it('allows to specify token registry token contract address', async () => {
const zeroExConfig = {
tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS,
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroExWithWrongTokenRegistryAddress = new ZeroEx(web3.currentProvider, zeroExConfig);
expect(zeroExWithWrongTokenRegistryAddress.tokenRegistry.getContractAddress()).to.be.equal(
ZeroEx.NULL_ADDRESS,
);
});
});
const ecSignature = await zeroEx.signOrderHashAsync(
orderHash,
makerAddress,
SHOULD_ADD_PERSONAL_MESSAGE_PREFIX,
);
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 = zeroEx.proxy.getContractAddress();
const txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(zrxTokenAddress, coinbase);
const txReceiptWithDecodedLogs = await zeroEx.awaitTransactionMinedAsync(txHash);
const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs<ApprovalContractEventArgs>;
expect(log.event).to.be.equal(TokenEvents.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);
});
});
describe('#config', () => {
it('allows to specify exchange contract address', async () => {
const zeroExConfig = {
exchangeContractAddress: ZeroEx.NULL_ADDRESS,
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, zeroExConfig);
expect(zeroExWithWrongExchangeAddress.exchange.getContractAddress()).to.be.equal(ZeroEx.NULL_ADDRESS);
});
it('allows to specify token registry token contract address', async () => {
const zeroExConfig = {
tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS,
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroExWithWrongTokenRegistryAddress = new ZeroEx(web3.currentProvider, zeroExConfig);
expect(zeroExWithWrongTokenRegistryAddress.tokenRegistry.getContractAddress()).to.be.equal(
ZeroEx.NULL_ADDRESS,
);
});
});
});

View File

@@ -12,44 +12,44 @@ chaiSetup.configure();
const TIMEOUT = 10000;
describe('Artifacts', () => {
describe('contracts are deployed on kovan', () => {
const kovanRpcUrl = constants.KOVAN_RPC_URL;
const packageJSONContent = fs.readFileSync('package.json', 'utf-8');
const packageJSON = JSON.parse(packageJSONContent);
const mnemonic = packageJSON.config.mnemonic;
const web3Provider = new HDWalletProvider(mnemonic, kovanRpcUrl);
const config = {
networkId: constants.KOVAN_NETWORK_ID,
};
const zeroEx = new ZeroEx(web3Provider, config);
it('token registry contract is deployed', async () => {
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
}).timeout(TIMEOUT);
it('proxy contract is deployed', async () => {
await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync();
}).timeout(TIMEOUT);
it('exchange contract is deployed', async () => {
await (zeroEx.exchange as any)._getExchangeContractAsync();
}).timeout(TIMEOUT);
});
describe('contracts are deployed on ropsten', () => {
const ropstenRpcUrl = constants.ROPSTEN_RPC_URL;
const packageJSONContent = fs.readFileSync('package.json', 'utf-8');
const packageJSON = JSON.parse(packageJSONContent);
const mnemonic = packageJSON.config.mnemonic;
const web3Provider = new HDWalletProvider(mnemonic, ropstenRpcUrl);
const config = {
networkId: constants.ROPSTEN_NETWORK_ID,
};
const zeroEx = new ZeroEx(web3Provider, config);
it('token registry contract is deployed', async () => {
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
}).timeout(TIMEOUT);
it('proxy contract is deployed', async () => {
await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync();
}).timeout(TIMEOUT);
it('exchange contract is deployed', async () => {
await (zeroEx.exchange as any)._getExchangeContractAsync();
}).timeout(TIMEOUT);
});
describe('contracts are deployed on kovan', () => {
const kovanRpcUrl = constants.KOVAN_RPC_URL;
const packageJSONContent = fs.readFileSync('package.json', 'utf-8');
const packageJSON = JSON.parse(packageJSONContent);
const mnemonic = packageJSON.config.mnemonic;
const web3Provider = new HDWalletProvider(mnemonic, kovanRpcUrl);
const config = {
networkId: constants.KOVAN_NETWORK_ID,
};
const zeroEx = new ZeroEx(web3Provider, config);
it('token registry contract is deployed', async () => {
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
}).timeout(TIMEOUT);
it('proxy contract is deployed', async () => {
await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync();
}).timeout(TIMEOUT);
it('exchange contract is deployed', async () => {
await (zeroEx.exchange as any)._getExchangeContractAsync();
}).timeout(TIMEOUT);
});
describe('contracts are deployed on ropsten', () => {
const ropstenRpcUrl = constants.ROPSTEN_RPC_URL;
const packageJSONContent = fs.readFileSync('package.json', 'utf-8');
const packageJSON = JSON.parse(packageJSONContent);
const mnemonic = packageJSON.config.mnemonic;
const web3Provider = new HDWalletProvider(mnemonic, ropstenRpcUrl);
const config = {
networkId: constants.ROPSTEN_NETWORK_ID,
};
const zeroEx = new ZeroEx(web3Provider, config);
it('token registry contract is deployed', async () => {
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
}).timeout(TIMEOUT);
it('proxy contract is deployed', async () => {
await (zeroEx.proxy as any)._getTokenTransferProxyContractAsync();
}).timeout(TIMEOUT);
it('exchange contract is deployed', async () => {
await (zeroEx.exchange as any)._getExchangeContractAsync();
}).timeout(TIMEOUT);
});
});

View File

@@ -10,34 +10,34 @@ import { web3Factory } from './utils/web3_factory';
const expect = chai.expect;
describe('Assertion library', () => {
const web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroEx = new ZeroEx(web3.currentProvider, config);
describe('#isSenderAddressHexAsync', () => {
it('throws when address is invalid', async () => {
const address = '0xdeadbeef';
const varName = 'address';
return expect(
assert.isSenderAddressAsync(varName, address, (zeroEx as any)._web3Wrapper),
).to.be.rejectedWith(`Expected ${varName} to be of type ETHAddressHex, encountered: ${address}`);
});
it('throws when address is unavailable', async () => {
const validUnrelatedAddress = '0x8b0292b11a196601eddce54b665cafeca0347d42';
const varName = 'address';
return expect(
assert.isSenderAddressAsync(varName, validUnrelatedAddress, (zeroEx as any)._web3Wrapper),
).to.be.rejectedWith(
`Specified ${varName} ${validUnrelatedAddress} isn't available through the supplied web3 provider`,
);
});
it("doesn't throw if address is available", async () => {
const availableAddress = (await zeroEx.getAvailableAddressesAsync())[0];
const varName = 'address';
return expect(
assert.isSenderAddressAsync(varName, availableAddress, (zeroEx as any)._web3Wrapper),
).to.become(undefined);
});
});
const web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroEx = new ZeroEx(web3.currentProvider, config);
describe('#isSenderAddressHexAsync', () => {
it('throws when address is invalid', async () => {
const address = '0xdeadbeef';
const varName = 'address';
return expect(
assert.isSenderAddressAsync(varName, address, (zeroEx as any)._web3Wrapper),
).to.be.rejectedWith(`Expected ${varName} to be of type ETHAddressHex, encountered: ${address}`);
});
it('throws when address is unavailable', async () => {
const validUnrelatedAddress = '0x8b0292b11a196601eddce54b665cafeca0347d42';
const varName = 'address';
return expect(
assert.isSenderAddressAsync(varName, validUnrelatedAddress, (zeroEx as any)._web3Wrapper),
).to.be.rejectedWith(
`Specified ${varName} ${validUnrelatedAddress} isn't available through the supplied web3 provider`,
);
});
it("doesn't throw if address is available", async () => {
const availableAddress = (await zeroEx.getAvailableAddressesAsync())[0];
const varName = 'address';
return expect(
assert.isSenderAddressAsync(varName, availableAddress, (zeroEx as any)._web3Wrapper),
).to.become(undefined);
});
});
});

View File

@@ -5,17 +5,17 @@ import 'mocha';
import * as Web3 from 'web3';
import {
ApprovalContractEventArgs,
BlockParamLiteral,
BlockRange,
DecodedLogEvent,
DepositContractEventArgs,
EtherTokenEvents,
Token,
TransferContractEventArgs,
WithdrawalContractEventArgs,
ZeroEx,
ZeroExError,
ApprovalContractEventArgs,
BlockParamLiteral,
BlockRange,
DecodedLogEvent,
DepositContractEventArgs,
EtherTokenEvents,
Token,
TransferContractEventArgs,
WithdrawalContractEventArgs,
ZeroEx,
ZeroExError,
} from '../src';
import { artifacts } from '../src/artifacts';
import { DoneCallback } from '../src/types';
@@ -37,339 +37,339 @@ const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL);
const MAX_REASONABLE_GAS_COST_IN_WEI = 62517;
describe('EtherTokenWrapper', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let tokens: Token[];
let userAddresses: string[];
let addressWithETH: string;
let wethContractAddress: string;
let depositWeiAmount: BigNumber;
let decimalPlaces: number;
let addressWithoutFunds: string;
const gasPrice = new BigNumber(1);
const zeroExConfig = {
gasPrice,
networkId: constants.TESTRPC_NETWORK_ID,
};
const transferAmount = new BigNumber(42);
const allowanceAmount = new BigNumber(42);
const depositAmount = new BigNumber(42);
const withdrawalAmount = new BigNumber(42);
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig);
tokens = await zeroEx.tokenRegistry.getTokensAsync();
userAddresses = await zeroEx.getAvailableAddressesAsync();
addressWithETH = userAddresses[0];
wethContractAddress = (zeroEx.etherToken as any)._getContractAddress(artifacts.EtherTokenArtifact);
depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5));
decimalPlaces = 7;
addressWithoutFunds = userAddresses[1];
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#depositAsync', () => {
it('should successfully deposit ETH and issue Wrapped ETH tokens', async () => {
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
expect(preETHBalance).to.be.bignumber.gt(0);
expect(preWETHBalance).to.be.bignumber.equal(0);
let web3: Web3;
let zeroEx: ZeroEx;
let tokens: Token[];
let userAddresses: string[];
let addressWithETH: string;
let wethContractAddress: string;
let depositWeiAmount: BigNumber;
let decimalPlaces: number;
let addressWithoutFunds: string;
const gasPrice = new BigNumber(1);
const zeroExConfig = {
gasPrice,
networkId: constants.TESTRPC_NETWORK_ID,
};
const transferAmount = new BigNumber(42);
const allowanceAmount = new BigNumber(42);
const depositAmount = new BigNumber(42);
const withdrawalAmount = new BigNumber(42);
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig);
tokens = await zeroEx.tokenRegistry.getTokensAsync();
userAddresses = await zeroEx.getAvailableAddressesAsync();
addressWithETH = userAddresses[0];
wethContractAddress = (zeroEx.etherToken as any)._getContractAddress(artifacts.EtherTokenArtifact);
depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5));
decimalPlaces = 7;
addressWithoutFunds = userAddresses[1];
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#depositAsync', () => {
it('should successfully deposit ETH and issue Wrapped ETH tokens', async () => {
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
expect(preETHBalance).to.be.bignumber.gt(0);
expect(preWETHBalance).to.be.bignumber.equal(0);
const txHash = await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const txHash = await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(depositWeiAmount);
const remainingETHInWei = preETHBalance.minus(depositWeiAmount);
const gasCost = remainingETHInWei.minus(postETHBalanceInWei);
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
});
it('should throw if user has insufficient ETH balance for deposit', async () => {
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(depositWeiAmount);
const remainingETHInWei = preETHBalance.minus(depositWeiAmount);
const gasCost = remainingETHInWei.minus(postETHBalanceInWei);
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
});
it('should throw if user has insufficient ETH balance for deposit', async () => {
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const extraETHBalance = (zeroEx as any)._web3Wrapper.toWei(5, 'ether');
const overETHBalanceinWei = preETHBalance.add(extraETHBalance);
const extraETHBalance = (zeroEx as any)._web3Wrapper.toWei(5, 'ether');
const overETHBalanceinWei = preETHBalance.add(extraETHBalance);
return expect(
zeroEx.etherToken.depositAsync(wethContractAddress, overETHBalanceinWei, addressWithETH),
).to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit);
});
});
describe('#withdrawAsync', () => {
it('should successfully withdraw ETH in return for Wrapped ETH tokens', async () => {
const ETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
return expect(
zeroEx.etherToken.depositAsync(wethContractAddress, overETHBalanceinWei, addressWithETH),
).to.be.rejectedWith(ZeroExError.InsufficientEthBalanceForDeposit);
});
});
describe('#withdrawAsync', () => {
it('should successfully withdraw ETH in return for Wrapped ETH tokens', async () => {
const ETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH);
await zeroEx.etherToken.depositAsync(wethContractAddress, depositWeiAmount, addressWithETH);
const expectedPreETHBalance = ETHBalanceInWei.minus(depositWeiAmount);
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
let gasCost = expectedPreETHBalance.minus(preETHBalance);
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount);
const expectedPreETHBalance = ETHBalanceInWei.minus(depositWeiAmount);
const preETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
let gasCost = expectedPreETHBalance.minus(preETHBalance);
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount);
const txHash = await zeroEx.etherToken.withdrawAsync(wethContractAddress, depositWeiAmount, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const txHash = await zeroEx.etherToken.withdrawAsync(wethContractAddress, depositWeiAmount, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(0);
const expectedETHBalance = preETHBalance.add(depositWeiAmount).round(decimalPlaces);
gasCost = expectedETHBalance.minus(postETHBalance);
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
});
it('should throw if user has insufficient WETH balance for withdrawl', async () => {
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
expect(preWETHBalance).to.be.bignumber.equal(0);
expect(postWETHBalanceInBaseUnits).to.be.bignumber.equal(0);
const expectedETHBalance = preETHBalance.add(depositWeiAmount).round(decimalPlaces);
gasCost = expectedETHBalance.minus(postETHBalance);
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
});
it('should throw if user has insufficient WETH balance for withdrawl', async () => {
const preWETHBalance = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
expect(preWETHBalance).to.be.bignumber.equal(0);
const overWETHBalance = preWETHBalance.add(999999999);
const overWETHBalance = preWETHBalance.add(999999999);
return expect(
zeroEx.etherToken.withdrawAsync(wethContractAddress, overWETHBalance, addressWithETH),
).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal);
});
});
describe('#subscribe', () => {
const indexFilterValues = {};
let etherTokenAddress: string;
before(() => {
const tokenUtils = new TokenUtils(tokens);
const etherToken = tokenUtils.getWethTokenOrThrow();
etherTokenAddress = etherToken.address;
});
afterEach(() => {
zeroEx.etherToken.unsubscribeAll();
});
// Hack: Mocha does not allow a test to be both async and have a `done` callback
// Since we need to await the receipt of the event in the `subscribe` callback,
// we do need both. A hack is to make the top-level async 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 tokens are transfered', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<TransferContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
expect(logEvent.isRemoved).to.be.false();
expect(logEvent.log.logIndex).to.be.equal(0);
expect(logEvent.log.transactionIndex).to.be.equal(0);
expect(logEvent.log.blockNumber).to.be.a('number');
const args = logEvent.log.args;
expect(args._from).to.be.equal(addressWithETH);
expect(args._to).to.be.equal(addressWithoutFunds);
expect(args._value).to.be.bignumber.equal(transferAmount);
},
);
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Transfer, indexFilterValues, callback);
await zeroEx.token.transferAsync(
etherTokenAddress,
addressWithETH,
addressWithoutFunds,
transferAmount,
);
})().catch(done);
});
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
expect(logEvent.isRemoved).to.be.false();
const args = logEvent.log.args;
expect(args._owner).to.be.equal(addressWithETH);
expect(args._spender).to.be.equal(addressWithoutFunds);
expect(args._value).to.be.bignumber.equal(allowanceAmount);
},
);
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Approval, indexFilterValues, callback);
await zeroEx.token.setAllowanceAsync(
etherTokenAddress,
addressWithETH,
addressWithoutFunds,
allowanceAmount,
);
})().catch(done);
});
it('Should receive the Deposit event when ether is being deposited', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<DepositContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
expect(logEvent.isRemoved).to.be.false();
const args = logEvent.log.args;
expect(args._owner).to.be.equal(addressWithETH);
expect(args._value).to.be.bignumber.equal(depositAmount);
},
);
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Deposit, indexFilterValues, callback);
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
})().catch(done);
});
it('Should receive the Withdrawal event when ether is being withdrawn', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<WithdrawalContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
expect(logEvent.isRemoved).to.be.false();
const args = logEvent.log.args;
expect(args._owner).to.be.equal(addressWithETH);
expect(args._value).to.be.bignumber.equal(depositAmount);
},
);
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
zeroEx.etherToken.subscribe(
etherTokenAddress,
EtherTokenEvents.Withdrawal,
indexFilterValues,
callback,
);
await zeroEx.etherToken.withdrawAsync(etherTokenAddress, withdrawalAmount, addressWithETH);
})().catch(done);
});
it('should cancel outstanding subscriptions when ZeroEx.setProvider is called', (done: DoneCallback) => {
(async () => {
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
},
);
zeroEx.etherToken.subscribe(
etherTokenAddress,
EtherTokenEvents.Transfer,
indexFilterValues,
callbackNeverToBeCalled,
);
const callbackToBeCalled = reportNodeCallbackErrors(done)();
const newProvider = web3Factory.getRpcProvider();
zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID);
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
zeroEx.etherToken.subscribe(
etherTokenAddress,
EtherTokenEvents.Transfer,
indexFilterValues,
callbackToBeCalled,
);
await zeroEx.token.transferAsync(
etherTokenAddress,
addressWithETH,
addressWithoutFunds,
transferAmount,
);
})().catch(done);
});
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
(async () => {
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
},
);
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
const subscriptionToken = zeroEx.etherToken.subscribe(
etherTokenAddress,
EtherTokenEvents.Transfer,
indexFilterValues,
callbackNeverToBeCalled,
);
zeroEx.etherToken.unsubscribe(subscriptionToken);
await zeroEx.token.transferAsync(
etherTokenAddress,
addressWithETH,
addressWithoutFunds,
transferAmount,
);
done();
})().catch(done);
});
});
describe('#getLogsAsync', () => {
let etherTokenAddress: string;
let tokenTransferProxyAddress: string;
const blockRange: BlockRange = {
fromBlock: 0,
toBlock: BlockParamLiteral.Latest,
};
let txHash: string;
before(() => {
addressWithETH = userAddresses[0];
const tokenUtils = new TokenUtils(tokens);
const etherToken = tokenUtils.getWethTokenOrThrow();
etherTokenAddress = etherToken.address;
tokenTransferProxyAddress = zeroEx.proxy.getContractAddress();
});
it('should get logs with decoded args emitted by Approval', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = EtherTokenEvents.Approval;
const indexFilterValues = {};
const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>(
etherTokenAddress,
eventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(logs[0].event).to.be.equal(eventName);
expect(args._owner).to.be.equal(addressWithETH);
expect(args._spender).to.be.equal(tokenTransferProxyAddress);
expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
it('should get logs with decoded args emitted by Deposit', async () => {
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
const eventName = EtherTokenEvents.Deposit;
const indexFilterValues = {};
const logs = await zeroEx.etherToken.getLogsAsync<DepositContractEventArgs>(
etherTokenAddress,
eventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(logs[0].event).to.be.equal(eventName);
expect(args._owner).to.be.equal(addressWithETH);
expect(args._value).to.be.bignumber.equal(depositAmount);
});
it('should only get the logs with the correct event name', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const differentEventName = EtherTokenEvents.Transfer;
const indexFilterValues = {};
const logs = await zeroEx.etherToken.getLogsAsync(
etherTokenAddress,
differentEventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(0);
});
it('should only get the logs with the correct indexed fields', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithoutFunds);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = EtherTokenEvents.Approval;
const indexFilterValues = {
_owner: addressWithETH,
};
const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>(
etherTokenAddress,
eventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(args._owner).to.be.equal(addressWithETH);
});
});
return expect(
zeroEx.etherToken.withdrawAsync(wethContractAddress, overWETHBalance, addressWithETH),
).to.be.rejectedWith(ZeroExError.InsufficientWEthBalanceForWithdrawal);
});
});
describe('#subscribe', () => {
const indexFilterValues = {};
let etherTokenAddress: string;
before(() => {
const tokenUtils = new TokenUtils(tokens);
const etherToken = tokenUtils.getWethTokenOrThrow();
etherTokenAddress = etherToken.address;
});
afterEach(() => {
zeroEx.etherToken.unsubscribeAll();
});
// Hack: Mocha does not allow a test to be both async and have a `done` callback
// Since we need to await the receipt of the event in the `subscribe` callback,
// we do need both. A hack is to make the top-level async 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 tokens are transfered', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<TransferContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
expect(logEvent.isRemoved).to.be.false();
expect(logEvent.log.logIndex).to.be.equal(0);
expect(logEvent.log.transactionIndex).to.be.equal(0);
expect(logEvent.log.blockNumber).to.be.a('number');
const args = logEvent.log.args;
expect(args._from).to.be.equal(addressWithETH);
expect(args._to).to.be.equal(addressWithoutFunds);
expect(args._value).to.be.bignumber.equal(transferAmount);
},
);
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Transfer, indexFilterValues, callback);
await zeroEx.token.transferAsync(
etherTokenAddress,
addressWithETH,
addressWithoutFunds,
transferAmount,
);
})().catch(done);
});
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
expect(logEvent.isRemoved).to.be.false();
const args = logEvent.log.args;
expect(args._owner).to.be.equal(addressWithETH);
expect(args._spender).to.be.equal(addressWithoutFunds);
expect(args._value).to.be.bignumber.equal(allowanceAmount);
},
);
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Approval, indexFilterValues, callback);
await zeroEx.token.setAllowanceAsync(
etherTokenAddress,
addressWithETH,
addressWithoutFunds,
allowanceAmount,
);
})().catch(done);
});
it('Should receive the Deposit event when ether is being deposited', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<DepositContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
expect(logEvent.isRemoved).to.be.false();
const args = logEvent.log.args;
expect(args._owner).to.be.equal(addressWithETH);
expect(args._value).to.be.bignumber.equal(depositAmount);
},
);
zeroEx.etherToken.subscribe(etherTokenAddress, EtherTokenEvents.Deposit, indexFilterValues, callback);
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
})().catch(done);
});
it('Should receive the Withdrawal event when ether is being withdrawn', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<WithdrawalContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
expect(logEvent.isRemoved).to.be.false();
const args = logEvent.log.args;
expect(args._owner).to.be.equal(addressWithETH);
expect(args._value).to.be.bignumber.equal(depositAmount);
},
);
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
zeroEx.etherToken.subscribe(
etherTokenAddress,
EtherTokenEvents.Withdrawal,
indexFilterValues,
callback,
);
await zeroEx.etherToken.withdrawAsync(etherTokenAddress, withdrawalAmount, addressWithETH);
})().catch(done);
});
it('should cancel outstanding subscriptions when ZeroEx.setProvider is called', (done: DoneCallback) => {
(async () => {
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
},
);
zeroEx.etherToken.subscribe(
etherTokenAddress,
EtherTokenEvents.Transfer,
indexFilterValues,
callbackNeverToBeCalled,
);
const callbackToBeCalled = reportNodeCallbackErrors(done)();
const newProvider = web3Factory.getRpcProvider();
zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID);
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
zeroEx.etherToken.subscribe(
etherTokenAddress,
EtherTokenEvents.Transfer,
indexFilterValues,
callbackToBeCalled,
);
await zeroEx.token.transferAsync(
etherTokenAddress,
addressWithETH,
addressWithoutFunds,
transferAmount,
);
})().catch(done);
});
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
(async () => {
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
},
);
await zeroEx.etherToken.depositAsync(etherTokenAddress, transferAmount, addressWithETH);
const subscriptionToken = zeroEx.etherToken.subscribe(
etherTokenAddress,
EtherTokenEvents.Transfer,
indexFilterValues,
callbackNeverToBeCalled,
);
zeroEx.etherToken.unsubscribe(subscriptionToken);
await zeroEx.token.transferAsync(
etherTokenAddress,
addressWithETH,
addressWithoutFunds,
transferAmount,
);
done();
})().catch(done);
});
});
describe('#getLogsAsync', () => {
let etherTokenAddress: string;
let tokenTransferProxyAddress: string;
const blockRange: BlockRange = {
fromBlock: 0,
toBlock: BlockParamLiteral.Latest,
};
let txHash: string;
before(() => {
addressWithETH = userAddresses[0];
const tokenUtils = new TokenUtils(tokens);
const etherToken = tokenUtils.getWethTokenOrThrow();
etherTokenAddress = etherToken.address;
tokenTransferProxyAddress = zeroEx.proxy.getContractAddress();
});
it('should get logs with decoded args emitted by Approval', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = EtherTokenEvents.Approval;
const indexFilterValues = {};
const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>(
etherTokenAddress,
eventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(logs[0].event).to.be.equal(eventName);
expect(args._owner).to.be.equal(addressWithETH);
expect(args._spender).to.be.equal(tokenTransferProxyAddress);
expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
it('should get logs with decoded args emitted by Deposit', async () => {
await zeroEx.etherToken.depositAsync(etherTokenAddress, depositAmount, addressWithETH);
const eventName = EtherTokenEvents.Deposit;
const indexFilterValues = {};
const logs = await zeroEx.etherToken.getLogsAsync<DepositContractEventArgs>(
etherTokenAddress,
eventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(logs[0].event).to.be.equal(eventName);
expect(args._owner).to.be.equal(addressWithETH);
expect(args._value).to.be.bignumber.equal(depositAmount);
});
it('should only get the logs with the correct event name', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
const differentEventName = EtherTokenEvents.Transfer;
const indexFilterValues = {};
const logs = await zeroEx.etherToken.getLogsAsync(
etherTokenAddress,
differentEventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(0);
});
it('should only get the logs with the correct indexed fields', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithETH);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(etherTokenAddress, addressWithoutFunds);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = EtherTokenEvents.Approval;
const indexFilterValues = {
_owner: addressWithETH,
};
const logs = await zeroEx.etherToken.getLogsAsync<ApprovalContractEventArgs>(
etherTokenAddress,
eventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(args._owner).to.be.equal(addressWithETH);
});
});
});

View File

@@ -17,110 +17,110 @@ chaiSetup.configure();
const expect = chai.expect;
describe('EventWatcher', () => {
let web3: Web3;
let stubs: Sinon.SinonStub[] = [];
let eventWatcher: EventWatcher;
let web3Wrapper: Web3Wrapper;
const logA: Web3.LogEntry = {
address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5',
blockHash: null,
blockNumber: null,
data: '',
logIndex: null,
topics: [],
transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17',
transactionIndex: 0,
};
const logB: Web3.LogEntry = {
address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819',
blockHash: null,
blockNumber: null,
data: '',
logIndex: null,
topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'],
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
transactionIndex: 0,
};
const logC: Web3.LogEntry = {
address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5',
blockHash: null,
blockNumber: null,
data: '',
logIndex: null,
topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'],
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
transactionIndex: 0,
};
before(async () => {
web3 = web3Factory.create();
const pollingIntervalMs = 10;
web3Wrapper = new Web3Wrapper(web3.currentProvider);
eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs);
});
afterEach(() => {
// clean up any stubs after the test has completed
_.each(stubs, s => s.restore());
stubs = [];
eventWatcher.unsubscribe();
});
it('correctly emits initial log events', (done: DoneCallback) => {
const logs: Web3.LogEntry[] = [logA, logB];
const expectedLogEvents = [
{
removed: false,
...logA,
},
{
removed: false,
...logB,
},
];
const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync');
getLogsStub.onCall(0).returns(logs);
stubs.push(getLogsStub);
const expectedToBeCalledOnce = false;
const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => {
const expectedLogEvent = expectedLogEvents.shift();
expect(event).to.be.deep.equal(expectedLogEvent);
if (_.isEmpty(expectedLogEvents)) {
done();
}
});
eventWatcher.subscribe(callback);
});
it('correctly computes the difference and emits only changes', (done: DoneCallback) => {
const initialLogs: Web3.LogEntry[] = [logA, logB];
const changedLogs: Web3.LogEntry[] = [logA, logC];
const expectedLogEvents = [
{
removed: false,
...logA,
},
{
removed: false,
...logB,
},
{
removed: true,
...logB,
},
{
removed: false,
...logC,
},
];
const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync');
getLogsStub.onCall(0).returns(initialLogs);
getLogsStub.onCall(1).returns(changedLogs);
stubs.push(getLogsStub);
const expectedToBeCalledOnce = false;
const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => {
const expectedLogEvent = expectedLogEvents.shift();
expect(event).to.be.deep.equal(expectedLogEvent);
if (_.isEmpty(expectedLogEvents)) {
done();
}
});
eventWatcher.subscribe(callback);
});
let web3: Web3;
let stubs: Sinon.SinonStub[] = [];
let eventWatcher: EventWatcher;
let web3Wrapper: Web3Wrapper;
const logA: Web3.LogEntry = {
address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5',
blockHash: null,
blockNumber: null,
data: '',
logIndex: null,
topics: [],
transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17',
transactionIndex: 0,
};
const logB: Web3.LogEntry = {
address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819',
blockHash: null,
blockNumber: null,
data: '',
logIndex: null,
topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'],
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
transactionIndex: 0,
};
const logC: Web3.LogEntry = {
address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5',
blockHash: null,
blockNumber: null,
data: '',
logIndex: null,
topics: ['0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567'],
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
transactionIndex: 0,
};
before(async () => {
web3 = web3Factory.create();
const pollingIntervalMs = 10;
web3Wrapper = new Web3Wrapper(web3.currentProvider);
eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs);
});
afterEach(() => {
// clean up any stubs after the test has completed
_.each(stubs, s => s.restore());
stubs = [];
eventWatcher.unsubscribe();
});
it('correctly emits initial log events', (done: DoneCallback) => {
const logs: Web3.LogEntry[] = [logA, logB];
const expectedLogEvents = [
{
removed: false,
...logA,
},
{
removed: false,
...logB,
},
];
const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync');
getLogsStub.onCall(0).returns(logs);
stubs.push(getLogsStub);
const expectedToBeCalledOnce = false;
const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => {
const expectedLogEvent = expectedLogEvents.shift();
expect(event).to.be.deep.equal(expectedLogEvent);
if (_.isEmpty(expectedLogEvents)) {
done();
}
});
eventWatcher.subscribe(callback);
});
it('correctly computes the difference and emits only changes', (done: DoneCallback) => {
const initialLogs: Web3.LogEntry[] = [logA, logB];
const changedLogs: Web3.LogEntry[] = [logA, logC];
const expectedLogEvents = [
{
removed: false,
...logA,
},
{
removed: false,
...logB,
},
{
removed: true,
...logB,
},
{
removed: false,
...logC,
},
];
const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync');
getLogsStub.onCall(0).returns(initialLogs);
getLogsStub.onCall(1).returns(changedLogs);
stubs.push(getLogsStub);
const expectedToBeCalledOnce = false;
const callback = reportNodeCallbackErrors(done, expectedToBeCalledOnce)((event: LogEvent) => {
const expectedLogEvent = expectedLogEvents.shift();
expect(event).to.be.deep.equal(expectedLogEvent);
if (_.isEmpty(expectedLogEvents)) {
done();
}
});
eventWatcher.subscribe(callback);
});
});

View File

@@ -15,103 +15,103 @@ const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL);
describe('ExchangeTransferSimulator', () => {
const web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroEx = new ZeroEx(web3.currentProvider, config);
const transferAmount = new BigNumber(5);
let userAddresses: string[];
let tokens: Token[];
let coinbase: string;
let sender: string;
let recipient: string;
let exampleTokenAddress: string;
let exchangeTransferSimulator: ExchangeTransferSimulator;
let txHash: string;
before(async () => {
userAddresses = await zeroEx.getAvailableAddressesAsync();
[coinbase, sender, recipient] = userAddresses;
tokens = await zeroEx.tokenRegistry.getTokensAsync();
exampleTokenAddress = tokens[0].address;
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#transferFromAsync', () => {
beforeEach(() => {
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest);
});
it("throws if the user doesn't have enough allowance", async () => {
return expect(
exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress,
sender,
recipient,
transferAmount,
TradeSide.Taker,
TransferType.Trade,
),
).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
});
it("throws if the user doesn't have enough balance", async () => {
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
return expect(
exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress,
sender,
recipient,
transferAmount,
TradeSide.Maker,
TransferType.Trade,
),
).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
});
it('updates balances and proxyAllowance after transfer', async () => {
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
await exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress,
sender,
recipient,
transferAmount,
TradeSide.Taker,
TransferType.Trade,
);
const store = (exchangeTransferSimulator as any)._store;
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
expect(senderBalance).to.be.bignumber.equal(0);
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
expect(senderProxyAllowance).to.be.bignumber.equal(0);
});
it("doesn't update proxyAllowance after transfer if unlimited", async () => {
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(exampleTokenAddress, sender);
await zeroEx.awaitTransactionMinedAsync(txHash);
await exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress,
sender,
recipient,
transferAmount,
TradeSide.Taker,
TransferType.Trade,
);
const store = (exchangeTransferSimulator as any)._store;
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
expect(senderBalance).to.be.bignumber.equal(0);
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
});
const web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
const zeroEx = new ZeroEx(web3.currentProvider, config);
const transferAmount = new BigNumber(5);
let userAddresses: string[];
let tokens: Token[];
let coinbase: string;
let sender: string;
let recipient: string;
let exampleTokenAddress: string;
let exchangeTransferSimulator: ExchangeTransferSimulator;
let txHash: string;
before(async () => {
userAddresses = await zeroEx.getAvailableAddressesAsync();
[coinbase, sender, recipient] = userAddresses;
tokens = await zeroEx.tokenRegistry.getTokensAsync();
exampleTokenAddress = tokens[0].address;
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#transferFromAsync', () => {
beforeEach(() => {
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest);
});
it("throws if the user doesn't have enough allowance", async () => {
return expect(
exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress,
sender,
recipient,
transferAmount,
TradeSide.Taker,
TransferType.Trade,
),
).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
});
it("throws if the user doesn't have enough balance", async () => {
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
return expect(
exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress,
sender,
recipient,
transferAmount,
TradeSide.Maker,
TransferType.Trade,
),
).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
});
it('updates balances and proxyAllowance after transfer', async () => {
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
await exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress,
sender,
recipient,
transferAmount,
TradeSide.Taker,
TransferType.Trade,
);
const store = (exchangeTransferSimulator as any)._store;
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
expect(senderBalance).to.be.bignumber.equal(0);
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
expect(senderProxyAllowance).to.be.bignumber.equal(0);
});
it("doesn't update proxyAllowance after transfer if unlimited", async () => {
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(exampleTokenAddress, sender);
await zeroEx.awaitTransactionMinedAsync(txHash);
await exchangeTransferSimulator.transferFromAsync(
exampleTokenAddress,
sender,
recipient,
transferAmount,
TradeSide.Taker,
TransferType.Trade,
);
const store = (exchangeTransferSimulator as any)._store;
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
expect(senderBalance).to.be.bignumber.equal(0);
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -24,137 +24,137 @@ const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(testConstants.RPC_URL);
describe('ExpirationWatcher', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let tokenUtils: TokenUtils;
let tokens: Token[];
let userAddresses: string[];
let zrxTokenAddress: string;
let fillScenarios: FillScenarios;
let exchangeContractAddress: string;
let makerTokenAddress: string;
let takerTokenAddress: string;
let coinbase: string;
let makerAddress: string;
let takerAddress: string;
let feeRecipient: string;
const fillableAmount = new BigNumber(5);
let currentUnixTimestampSec: BigNumber;
let timer: Sinon.SinonFakeTimers;
let expirationWatcher: ExpirationWatcher;
before(async () => {
web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
zeroEx = new ZeroEx(web3.currentProvider, config);
exchangeContractAddress = zeroEx.exchange.getContractAddress();
userAddresses = await zeroEx.getAvailableAddressesAsync();
tokens = await zeroEx.tokenRegistry.getTokensAsync();
tokenUtils = new TokenUtils(tokens);
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
tokens = await zeroEx.tokenRegistry.getTokensAsync();
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
makerTokenAddress = makerToken.address;
takerTokenAddress = takerToken.address;
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
const sinonTimerConfig = { shouldAdvanceTime: true } as any;
// This constructor has incorrect types
timer = Sinon.useFakeTimers(sinonTimerConfig);
currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
expirationWatcher = new ExpirationWatcher();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
timer.restore();
expirationWatcher.unsubscribe();
});
it('correctly emits events when order expires', (done: DoneCallback) => {
(async () => {
const orderLifetimeSec = 60;
const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
expirationUnixTimestampSec,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000));
const callbackAsync = reportNoErrorCallbackErrors(done)((hash: string) => {
expect(hash).to.be.equal(orderHash);
expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec);
});
expirationWatcher.subscribe(callbackAsync);
timer.tick(orderLifetimeSec * 1000);
})().catch(done);
});
it("doesn't emit events before order expires", (done: DoneCallback) => {
(async () => {
const orderLifetimeSec = 60;
const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
expirationUnixTimestampSec,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000));
const callbackAsync = reportNoErrorCallbackErrors(done)(async (hash: string) => {
done(new Error('Emitted expiration went before the order actually expired'));
});
expirationWatcher.subscribe(callbackAsync);
const notEnoughTime = orderLifetimeSec - 1;
timer.tick(notEnoughTime * 1000);
done();
})().catch(done);
});
it('emits events in correct order', (done: DoneCallback) => {
(async () => {
const order1Lifetime = 60;
const order2Lifetime = 120;
const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime);
const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime);
const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
order1ExpirationUnixTimestampSec,
);
const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
order2ExpirationUnixTimestampSec,
);
const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1);
const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2);
expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000));
expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000));
const expirationOrder = [orderHash1, orderHash2];
const expectToBeCalledOnce = false;
const callbackAsync = reportNoErrorCallbackErrors(done, expectToBeCalledOnce)((hash: string) => {
const orderHash = expirationOrder.shift();
expect(hash).to.be.equal(orderHash);
if (_.isEmpty(expirationOrder)) {
done();
}
});
expirationWatcher.subscribe(callbackAsync);
timer.tick(order2Lifetime * 1000);
})().catch(done);
});
let web3: Web3;
let zeroEx: ZeroEx;
let tokenUtils: TokenUtils;
let tokens: Token[];
let userAddresses: string[];
let zrxTokenAddress: string;
let fillScenarios: FillScenarios;
let exchangeContractAddress: string;
let makerTokenAddress: string;
let takerTokenAddress: string;
let coinbase: string;
let makerAddress: string;
let takerAddress: string;
let feeRecipient: string;
const fillableAmount = new BigNumber(5);
let currentUnixTimestampSec: BigNumber;
let timer: Sinon.SinonFakeTimers;
let expirationWatcher: ExpirationWatcher;
before(async () => {
web3 = web3Factory.create();
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
zeroEx = new ZeroEx(web3.currentProvider, config);
exchangeContractAddress = zeroEx.exchange.getContractAddress();
userAddresses = await zeroEx.getAvailableAddressesAsync();
tokens = await zeroEx.tokenRegistry.getTokensAsync();
tokenUtils = new TokenUtils(tokens);
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
tokens = await zeroEx.tokenRegistry.getTokensAsync();
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
makerTokenAddress = makerToken.address;
takerTokenAddress = takerToken.address;
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
const sinonTimerConfig = { shouldAdvanceTime: true } as any;
// This constructor has incorrect types
timer = Sinon.useFakeTimers(sinonTimerConfig);
currentUnixTimestampSec = utils.getCurrentUnixTimestampSec();
expirationWatcher = new ExpirationWatcher();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
timer.restore();
expirationWatcher.unsubscribe();
});
it('correctly emits events when order expires', (done: DoneCallback) => {
(async () => {
const orderLifetimeSec = 60;
const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
expirationUnixTimestampSec,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000));
const callbackAsync = reportNoErrorCallbackErrors(done)((hash: string) => {
expect(hash).to.be.equal(orderHash);
expect(utils.getCurrentUnixTimestampSec()).to.be.bignumber.gte(expirationUnixTimestampSec);
});
expirationWatcher.subscribe(callbackAsync);
timer.tick(orderLifetimeSec * 1000);
})().catch(done);
});
it("doesn't emit events before order expires", (done: DoneCallback) => {
(async () => {
const orderLifetimeSec = 60;
const expirationUnixTimestampSec = currentUnixTimestampSec.plus(orderLifetimeSec);
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
expirationUnixTimestampSec,
);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
expirationWatcher.addOrder(orderHash, signedOrder.expirationUnixTimestampSec.times(1000));
const callbackAsync = reportNoErrorCallbackErrors(done)(async (hash: string) => {
done(new Error('Emitted expiration went before the order actually expired'));
});
expirationWatcher.subscribe(callbackAsync);
const notEnoughTime = orderLifetimeSec - 1;
timer.tick(notEnoughTime * 1000);
done();
})().catch(done);
});
it('emits events in correct order', (done: DoneCallback) => {
(async () => {
const order1Lifetime = 60;
const order2Lifetime = 120;
const order1ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order1Lifetime);
const order2ExpirationUnixTimestampSec = currentUnixTimestampSec.plus(order2Lifetime);
const signedOrder1 = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
order1ExpirationUnixTimestampSec,
);
const signedOrder2 = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
order2ExpirationUnixTimestampSec,
);
const orderHash1 = ZeroEx.getOrderHashHex(signedOrder1);
const orderHash2 = ZeroEx.getOrderHashHex(signedOrder2);
expirationWatcher.addOrder(orderHash2, signedOrder2.expirationUnixTimestampSec.times(1000));
expirationWatcher.addOrder(orderHash1, signedOrder1.expirationUnixTimestampSec.times(1000));
const expirationOrder = [orderHash1, orderHash2];
const expectToBeCalledOnce = false;
const callbackAsync = reportNoErrorCallbackErrors(done, expectToBeCalledOnce)((hash: string) => {
const orderHash = expirationOrder.shift();
expect(hash).to.be.equal(orderHash);
if (_.isEmpty(expirationOrder)) {
done();
}
});
expirationWatcher.subscribe(callbackAsync);
timer.tick(order2Lifetime * 1000);
})().catch(done);
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -20,455 +20,455 @@ const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL);
describe('OrderValidation', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let userAddresses: string[];
let tokens: Token[];
let tokenUtils: TokenUtils;
let exchangeContractAddress: string;
let zrxTokenAddress: string;
let fillScenarios: FillScenarios;
let makerTokenAddress: string;
let takerTokenAddress: string;
let coinbase: string;
let makerAddress: string;
let takerAddress: string;
let feeRecipient: string;
const fillableAmount = new BigNumber(5);
const fillTakerAmount = new BigNumber(5);
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
exchangeContractAddress = zeroEx.exchange.getContractAddress();
userAddresses = await zeroEx.getAvailableAddressesAsync();
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
tokens = await zeroEx.tokenRegistry.getTokensAsync();
tokenUtils = new TokenUtils(tokens);
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
makerTokenAddress = makerToken.address;
takerTokenAddress = takerToken.address;
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('validateOrderFillableOrThrowAsync', () => {
it('should succeed if the order is fillable', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder);
});
it('should succeed if the order is asymmetric and fillable', async () => {
const makerFillableAmount = fillableAmount;
const takerFillableAmount = fillableAmount.minus(4);
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
makerFillableAmount,
takerFillableAmount,
);
await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder);
});
it('should throw when the order is fully filled or cancelled', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith(
ExchangeContractErrs.OrderRemainingFillAmountZero,
);
});
it('should throw when order is expired', async () => {
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
expirationInPast,
);
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith(
ExchangeContractErrs.OrderFillExpired,
);
});
});
describe('validateFillOrderAndThrowIfInvalidAsync', () => {
it('should throw when the fill amount is zero', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
const zeroFillAmount = new BigNumber(0);
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(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 = 28 - signedOrder.ecSignature.v + 27;
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,
);
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress),
).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
});
it('should throw when sender is not a taker', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
const nonTakerAddress = userAddresses[6];
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, nonTakerAddress),
).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
});
it('should throw when order is expired', async () => {
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
expirationInPast,
);
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, takerAddress),
).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
});
it('should throw when there a rounding error would have occurred', async () => {
const makerAmount = new BigNumber(3);
const takerAmount = new BigNumber(5);
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
makerAmount,
takerAmount,
);
const fillTakerAmountThatCausesRoundingError = new BigNumber(3);
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
signedOrder,
fillTakerAmountThatCausesRoundingError,
takerAddress,
),
).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError);
});
});
describe('#validateFillOrKillOrderAndThrowIfInvalidAsync', () => {
it('should throw if remaining fillAmount is less then the desired fillAmount', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
const tooLargeFillAmount = new BigNumber(7);
const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount);
await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference);
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount);
await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference);
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount);
let web3: Web3;
let zeroEx: ZeroEx;
let userAddresses: string[];
let tokens: Token[];
let tokenUtils: TokenUtils;
let exchangeContractAddress: string;
let zrxTokenAddress: string;
let fillScenarios: FillScenarios;
let makerTokenAddress: string;
let takerTokenAddress: string;
let coinbase: string;
let makerAddress: string;
let takerAddress: string;
let feeRecipient: string;
const fillableAmount = new BigNumber(5);
const fillTakerAmount = new BigNumber(5);
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
exchangeContractAddress = zeroEx.exchange.getContractAddress();
userAddresses = await zeroEx.getAvailableAddressesAsync();
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
tokens = await zeroEx.tokenRegistry.getTokensAsync();
tokenUtils = new TokenUtils(tokens);
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
makerTokenAddress = makerToken.address;
takerTokenAddress = takerToken.address;
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('validateOrderFillableOrThrowAsync', () => {
it('should succeed if the order is fillable', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder);
});
it('should succeed if the order is asymmetric and fillable', async () => {
const makerFillableAmount = fillableAmount;
const takerFillableAmount = fillableAmount.minus(4);
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
makerFillableAmount,
takerFillableAmount,
);
await zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder);
});
it('should throw when the order is fully filled or cancelled', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith(
ExchangeContractErrs.OrderRemainingFillAmountZero,
);
});
it('should throw when order is expired', async () => {
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
expirationInPast,
);
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(signedOrder)).to.be.rejectedWith(
ExchangeContractErrs.OrderFillExpired,
);
});
});
describe('validateFillOrderAndThrowIfInvalidAsync', () => {
it('should throw when the fill amount is zero', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
const zeroFillAmount = new BigNumber(0);
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(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 = 28 - signedOrder.ecSignature.v + 27;
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,
);
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillableAmount, takerAddress),
).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
});
it('should throw when sender is not a taker', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
const nonTakerAddress = userAddresses[6];
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, nonTakerAddress),
).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
});
it('should throw when order is expired', async () => {
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
expirationInPast,
);
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerAmount, takerAddress),
).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
});
it('should throw when there a rounding error would have occurred', async () => {
const makerAmount = new BigNumber(3);
const takerAmount = new BigNumber(5);
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
makerAmount,
takerAmount,
);
const fillTakerAmountThatCausesRoundingError = new BigNumber(3);
return expect(
zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
signedOrder,
fillTakerAmountThatCausesRoundingError,
takerAddress,
),
).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError);
});
});
describe('#validateFillOrKillOrderAndThrowIfInvalidAsync', () => {
it('should throw if remaining fillAmount is less then the desired fillAmount', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
const tooLargeFillAmount = new BigNumber(7);
const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount);
await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference);
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount);
await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference);
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount);
return expect(
zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync(
signedOrder,
tooLargeFillAmount,
takerAddress,
),
).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount);
});
});
describe('validateCancelOrderAndThrowIfInvalidAsync', () => {
let signedOrder: SignedOrder;
const cancelAmount = new BigNumber(3);
beforeEach(async () => {
[coinbase, makerAddress, takerAddress] = userAddresses;
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
makerTokenAddress = makerToken.address;
takerTokenAddress = takerToken.address;
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
});
it('should throw when cancel amount is zero', async () => {
const zeroCancelAmount = new BigNumber(0);
return expect(
zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, zeroCancelAmount),
).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
});
it('should throw when order is expired', async () => {
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
expirationInPast,
);
return expect(
zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(expiredSignedOrder, cancelAmount),
).to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired);
});
it('should throw when order is already cancelled or filled', async () => {
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
return expect(
zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, fillableAmount),
).to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
});
});
describe('#validateFillOrderBalancesAllowancesThrowIfInvalidAsync', () => {
let exchangeTransferSimulator: ExchangeTransferSimulator;
let transferFromAsync: Sinon.SinonSpy;
const bigNumberMatch = (expected: BigNumber) => {
return Sinon.match((value: BigNumber) => value.eq(expected));
};
beforeEach('create exchangeTransferSimulator', async () => {
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest);
transferFromAsync = Sinon.spy();
exchangeTransferSimulator.transferFromAsync = transferFromAsync as any;
});
it('should call exchangeTransferSimulator.transferFrom in a correct order', async () => {
const makerFee = new BigNumber(2);
const takerFee = new BigNumber(2);
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerTokenAddress,
takerTokenAddress,
makerFee,
takerFee,
makerAddress,
takerAddress,
fillableAmount,
feeRecipient,
);
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTransferSimulator,
signedOrder,
fillableAmount,
takerAddress,
zrxTokenAddress,
);
expect(transferFromAsync.callCount).to.be.equal(4);
expect(
transferFromAsync
.getCall(0)
.calledWith(
makerTokenAddress,
makerAddress,
takerAddress,
bigNumberMatch(fillableAmount),
TradeSide.Maker,
TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync
.getCall(1)
.calledWith(
takerTokenAddress,
takerAddress,
makerAddress,
bigNumberMatch(fillableAmount),
TradeSide.Taker,
TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync
.getCall(2)
.calledWith(
zrxTokenAddress,
makerAddress,
feeRecipient,
bigNumberMatch(makerFee),
TradeSide.Maker,
TransferType.Fee,
),
).to.be.true();
expect(
transferFromAsync
.getCall(3)
.calledWith(
zrxTokenAddress,
takerAddress,
feeRecipient,
bigNumberMatch(takerFee),
TradeSide.Taker,
TransferType.Fee,
),
).to.be.true();
});
it('should call exchangeTransferSimulator.transferFrom with correct values for an open order', async () => {
const makerFee = new BigNumber(2);
const takerFee = new BigNumber(2);
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerTokenAddress,
takerTokenAddress,
makerFee,
takerFee,
makerAddress,
ZeroEx.NULL_ADDRESS,
fillableAmount,
feeRecipient,
);
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTransferSimulator,
signedOrder,
fillableAmount,
takerAddress,
zrxTokenAddress,
);
expect(transferFromAsync.callCount).to.be.equal(4);
expect(
transferFromAsync
.getCall(0)
.calledWith(
makerTokenAddress,
makerAddress,
takerAddress,
bigNumberMatch(fillableAmount),
TradeSide.Maker,
TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync
.getCall(1)
.calledWith(
takerTokenAddress,
takerAddress,
makerAddress,
bigNumberMatch(fillableAmount),
TradeSide.Taker,
TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync
.getCall(2)
.calledWith(
zrxTokenAddress,
makerAddress,
feeRecipient,
bigNumberMatch(makerFee),
TradeSide.Maker,
TransferType.Fee,
),
).to.be.true();
expect(
transferFromAsync
.getCall(3)
.calledWith(
zrxTokenAddress,
takerAddress,
feeRecipient,
bigNumberMatch(takerFee),
TradeSide.Taker,
TransferType.Fee,
),
).to.be.true();
});
it('should correctly round the fillMakerTokenAmount', async () => {
const makerTokenAmount = new BigNumber(3);
const takerTokenAmount = new BigNumber(1);
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
makerTokenAmount,
takerTokenAmount,
);
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTransferSimulator,
signedOrder,
takerTokenAmount,
takerAddress,
zrxTokenAddress,
);
expect(transferFromAsync.callCount).to.be.equal(4);
const makerFillAmount = transferFromAsync.getCall(0).args[3];
expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount);
});
it('should correctly round the makerFeeAmount', async () => {
const makerFee = new BigNumber(2);
const takerFee = new BigNumber(4);
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerTokenAddress,
takerTokenAddress,
makerFee,
takerFee,
makerAddress,
takerAddress,
fillableAmount,
ZeroEx.NULL_ADDRESS,
);
const fillTakerTokenAmount = fillableAmount.div(2).round(0);
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTransferSimulator,
signedOrder,
fillTakerTokenAmount,
takerAddress,
zrxTokenAddress,
);
const makerPartialFee = makerFee.div(2);
const takerPartialFee = takerFee.div(2);
expect(transferFromAsync.callCount).to.be.equal(4);
const partialMakerFee = transferFromAsync.getCall(2).args[3];
expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee);
const partialTakerFee = transferFromAsync.getCall(3).args[3];
expect(partialTakerFee).to.be.bignumber.equal(takerPartialFee);
});
});
return expect(
zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync(
signedOrder,
tooLargeFillAmount,
takerAddress,
),
).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount);
});
});
describe('validateCancelOrderAndThrowIfInvalidAsync', () => {
let signedOrder: SignedOrder;
const cancelAmount = new BigNumber(3);
beforeEach(async () => {
[coinbase, makerAddress, takerAddress] = userAddresses;
const [makerToken, takerToken] = tokenUtils.getDummyTokens();
makerTokenAddress = makerToken.address;
takerTokenAddress = takerToken.address;
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
);
});
it('should throw when cancel amount is zero', async () => {
const zeroCancelAmount = new BigNumber(0);
return expect(
zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, zeroCancelAmount),
).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
});
it('should throw when order is expired', async () => {
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
expirationInPast,
);
return expect(
zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(expiredSignedOrder, cancelAmount),
).to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired);
});
it('should throw when order is already cancelled or filled', async () => {
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
return expect(
zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, fillableAmount),
).to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
});
});
describe('#validateFillOrderBalancesAllowancesThrowIfInvalidAsync', () => {
let exchangeTransferSimulator: ExchangeTransferSimulator;
let transferFromAsync: Sinon.SinonSpy;
const bigNumberMatch = (expected: BigNumber) => {
return Sinon.match((value: BigNumber) => value.eq(expected));
};
beforeEach('create exchangeTransferSimulator', async () => {
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token, BlockParamLiteral.Latest);
transferFromAsync = Sinon.spy();
exchangeTransferSimulator.transferFromAsync = transferFromAsync as any;
});
it('should call exchangeTransferSimulator.transferFrom in a correct order', async () => {
const makerFee = new BigNumber(2);
const takerFee = new BigNumber(2);
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerTokenAddress,
takerTokenAddress,
makerFee,
takerFee,
makerAddress,
takerAddress,
fillableAmount,
feeRecipient,
);
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTransferSimulator,
signedOrder,
fillableAmount,
takerAddress,
zrxTokenAddress,
);
expect(transferFromAsync.callCount).to.be.equal(4);
expect(
transferFromAsync
.getCall(0)
.calledWith(
makerTokenAddress,
makerAddress,
takerAddress,
bigNumberMatch(fillableAmount),
TradeSide.Maker,
TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync
.getCall(1)
.calledWith(
takerTokenAddress,
takerAddress,
makerAddress,
bigNumberMatch(fillableAmount),
TradeSide.Taker,
TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync
.getCall(2)
.calledWith(
zrxTokenAddress,
makerAddress,
feeRecipient,
bigNumberMatch(makerFee),
TradeSide.Maker,
TransferType.Fee,
),
).to.be.true();
expect(
transferFromAsync
.getCall(3)
.calledWith(
zrxTokenAddress,
takerAddress,
feeRecipient,
bigNumberMatch(takerFee),
TradeSide.Taker,
TransferType.Fee,
),
).to.be.true();
});
it('should call exchangeTransferSimulator.transferFrom with correct values for an open order', async () => {
const makerFee = new BigNumber(2);
const takerFee = new BigNumber(2);
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerTokenAddress,
takerTokenAddress,
makerFee,
takerFee,
makerAddress,
ZeroEx.NULL_ADDRESS,
fillableAmount,
feeRecipient,
);
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTransferSimulator,
signedOrder,
fillableAmount,
takerAddress,
zrxTokenAddress,
);
expect(transferFromAsync.callCount).to.be.equal(4);
expect(
transferFromAsync
.getCall(0)
.calledWith(
makerTokenAddress,
makerAddress,
takerAddress,
bigNumberMatch(fillableAmount),
TradeSide.Maker,
TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync
.getCall(1)
.calledWith(
takerTokenAddress,
takerAddress,
makerAddress,
bigNumberMatch(fillableAmount),
TradeSide.Taker,
TransferType.Trade,
),
).to.be.true();
expect(
transferFromAsync
.getCall(2)
.calledWith(
zrxTokenAddress,
makerAddress,
feeRecipient,
bigNumberMatch(makerFee),
TradeSide.Maker,
TransferType.Fee,
),
).to.be.true();
expect(
transferFromAsync
.getCall(3)
.calledWith(
zrxTokenAddress,
takerAddress,
feeRecipient,
bigNumberMatch(takerFee),
TradeSide.Taker,
TransferType.Fee,
),
).to.be.true();
});
it('should correctly round the fillMakerTokenAmount', async () => {
const makerTokenAmount = new BigNumber(3);
const takerTokenAmount = new BigNumber(1);
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
makerTokenAmount,
takerTokenAmount,
);
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTransferSimulator,
signedOrder,
takerTokenAmount,
takerAddress,
zrxTokenAddress,
);
expect(transferFromAsync.callCount).to.be.equal(4);
const makerFillAmount = transferFromAsync.getCall(0).args[3];
expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount);
});
it('should correctly round the makerFeeAmount', async () => {
const makerFee = new BigNumber(2);
const takerFee = new BigNumber(4);
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
makerTokenAddress,
takerTokenAddress,
makerFee,
takerFee,
makerAddress,
takerAddress,
fillableAmount,
ZeroEx.NULL_ADDRESS,
);
const fillTakerTokenAmount = fillableAmount.div(2).round(0);
await OrderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
exchangeTransferSimulator,
signedOrder,
fillTakerTokenAmount,
takerAddress,
zrxTokenAddress,
);
const makerPartialFee = makerFee.div(2);
const takerPartialFee = takerFee.div(2);
expect(transferFromAsync.callCount).to.be.equal(4);
const partialMakerFee = transferFromAsync.getCall(2).args[3];
expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee);
const partialTakerFee = transferFromAsync.getCall(3).args[3];
expect(partialTakerFee).to.be.bignumber.equal(takerPartialFee);
});
});
});

View File

@@ -12,223 +12,223 @@ chaiSetup.configure();
const expect = chai.expect;
describe('RemainingFillableCalculator', () => {
let calculator: RemainingFillableCalculator;
let signedOrder: SignedOrder;
let transferrableMakerTokenAmount: BigNumber;
let transferrableMakerFeeTokenAmount: BigNumber;
let remainingMakerTokenAmount: BigNumber;
let makerAmount: BigNumber;
let takerAmount: BigNumber;
let makerFeeAmount: BigNumber;
let isMakerTokenZRX: boolean;
const makerToken: string = '0x1';
const takerToken: string = '0x2';
const decimals: number = 4;
const zero: BigNumber = new BigNumber(0);
const zeroAddress = '0x0';
const signature: ECSignature = { v: 27, r: '', s: '' };
beforeEach(async () => {
[makerAmount, takerAmount, makerFeeAmount] = [
ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals),
];
[transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount] = [
ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals),
];
});
function buildSignedOrder(): SignedOrder {
return {
ecSignature: signature,
exchangeContractAddress: zeroAddress,
feeRecipient: zeroAddress,
maker: zeroAddress,
taker: zeroAddress,
makerFee: makerFeeAmount,
takerFee: zero,
makerTokenAmount: makerAmount,
takerTokenAmount: takerAmount,
makerTokenAddress: makerToken,
takerTokenAddress: takerToken,
salt: zero,
expirationUnixTimestampSec: zero,
};
}
describe('Maker token is NOT ZRX', () => {
before(async () => {
isMakerTokenZRX = false;
});
it('calculates the correct amount when unfilled and funds available', () => {
signedOrder = buildSignedOrder();
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
});
it('calculates the correct amount when partially filled and funds available', () => {
signedOrder = buildSignedOrder();
remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
});
it('calculates the amount to be 0 when all fee funds are transferred', () => {
signedOrder = buildSignedOrder();
transferrableMakerFeeTokenAmount = zero;
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero);
});
it('calculates the correct amount when balance is less than remaining fillable', () => {
signedOrder = buildSignedOrder();
const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount);
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount);
});
describe('Order to Fee Ratio is < 1', () => {
beforeEach(async () => {
[makerAmount, takerAmount, makerFeeAmount] = [
ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals),
];
});
it('calculates the correct amount when funds unavailable', () => {
signedOrder = buildSignedOrder();
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount);
});
});
describe('Ratio is not evenly divisble', () => {
beforeEach(async () => {
[makerAmount, takerAmount, makerFeeAmount] = [
ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals),
];
});
it('calculates the correct amount when funds unavailable', () => {
signedOrder = buildSignedOrder();
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
const calculatedFillableAmount = calculator.computeRemainingMakerFillable();
expect(calculatedFillableAmount.lessThanOrEqualTo(transferrableMakerTokenAmount)).to.be.true();
expect(calculatedFillableAmount).to.be.bignumber.greaterThan(new BigNumber(0));
const orderToFeeRatio = signedOrder.makerTokenAmount.dividedBy(signedOrder.makerFee);
const calculatedFeeAmount = calculatedFillableAmount.dividedBy(orderToFeeRatio);
expect(calculatedFeeAmount).to.be.bignumber.lessThan(transferrableMakerFeeTokenAmount);
});
});
});
describe('Maker Token is ZRX', () => {
before(async () => {
isMakerTokenZRX = true;
});
it('calculates the correct amount when unfilled and funds available', () => {
signedOrder = buildSignedOrder();
transferrableMakerTokenAmount = makerAmount.plus(makerFeeAmount);
transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount;
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
});
it('calculates the correct amount when partially filled and funds available', () => {
signedOrder = buildSignedOrder();
remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
});
it('calculates the amount to be 0 when all fee funds are transferred', () => {
signedOrder = buildSignedOrder();
transferrableMakerTokenAmount = zero;
transferrableMakerFeeTokenAmount = zero;
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero);
});
it('calculates the correct amount when balance is less than remaining fillable', () => {
signedOrder = buildSignedOrder();
const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount);
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount);
transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount;
let calculator: RemainingFillableCalculator;
let signedOrder: SignedOrder;
let transferrableMakerTokenAmount: BigNumber;
let transferrableMakerFeeTokenAmount: BigNumber;
let remainingMakerTokenAmount: BigNumber;
let makerAmount: BigNumber;
let takerAmount: BigNumber;
let makerFeeAmount: BigNumber;
let isMakerTokenZRX: boolean;
const makerToken: string = '0x1';
const takerToken: string = '0x2';
const decimals: number = 4;
const zero: BigNumber = new BigNumber(0);
const zeroAddress = '0x0';
const signature: ECSignature = { v: 27, r: '', s: '' };
beforeEach(async () => {
[makerAmount, takerAmount, makerFeeAmount] = [
ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals),
];
[transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount] = [
ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals),
];
});
function buildSignedOrder(): SignedOrder {
return {
ecSignature: signature,
exchangeContractAddress: zeroAddress,
feeRecipient: zeroAddress,
maker: zeroAddress,
taker: zeroAddress,
makerFee: makerFeeAmount,
takerFee: zero,
makerTokenAmount: makerAmount,
takerTokenAmount: takerAmount,
makerTokenAddress: makerToken,
takerTokenAddress: takerToken,
salt: zero,
expirationUnixTimestampSec: zero,
};
}
describe('Maker token is NOT ZRX', () => {
before(async () => {
isMakerTokenZRX = false;
});
it('calculates the correct amount when unfilled and funds available', () => {
signedOrder = buildSignedOrder();
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
});
it('calculates the correct amount when partially filled and funds available', () => {
signedOrder = buildSignedOrder();
remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
});
it('calculates the amount to be 0 when all fee funds are transferred', () => {
signedOrder = buildSignedOrder();
transferrableMakerFeeTokenAmount = zero;
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero);
});
it('calculates the correct amount when balance is less than remaining fillable', () => {
signedOrder = buildSignedOrder();
const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount);
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount);
});
describe('Order to Fee Ratio is < 1', () => {
beforeEach(async () => {
[makerAmount, takerAmount, makerFeeAmount] = [
ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals),
];
});
it('calculates the correct amount when funds unavailable', () => {
signedOrder = buildSignedOrder();
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount);
});
});
describe('Ratio is not evenly divisble', () => {
beforeEach(async () => {
[makerAmount, takerAmount, makerFeeAmount] = [
ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals),
ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals),
];
});
it('calculates the correct amount when funds unavailable', () => {
signedOrder = buildSignedOrder();
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
const calculatedFillableAmount = calculator.computeRemainingMakerFillable();
expect(calculatedFillableAmount.lessThanOrEqualTo(transferrableMakerTokenAmount)).to.be.true();
expect(calculatedFillableAmount).to.be.bignumber.greaterThan(new BigNumber(0));
const orderToFeeRatio = signedOrder.makerTokenAmount.dividedBy(signedOrder.makerFee);
const calculatedFeeAmount = calculatedFillableAmount.dividedBy(orderToFeeRatio);
expect(calculatedFeeAmount).to.be.bignumber.lessThan(transferrableMakerFeeTokenAmount);
});
});
});
describe('Maker Token is ZRX', () => {
before(async () => {
isMakerTokenZRX = true;
});
it('calculates the correct amount when unfilled and funds available', () => {
signedOrder = buildSignedOrder();
transferrableMakerTokenAmount = makerAmount.plus(makerFeeAmount);
transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount;
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
});
it('calculates the correct amount when partially filled and funds available', () => {
signedOrder = buildSignedOrder();
remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
});
it('calculates the amount to be 0 when all fee funds are transferred', () => {
signedOrder = buildSignedOrder();
transferrableMakerTokenAmount = zero;
transferrableMakerFeeTokenAmount = zero;
remainingMakerTokenAmount = signedOrder.makerTokenAmount;
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero);
});
it('calculates the correct amount when balance is less than remaining fillable', () => {
signedOrder = buildSignedOrder();
const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount);
transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount);
transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount;
const orderToFeeRatio = signedOrder.makerTokenAmount.dividedToIntegerBy(signedOrder.makerFee);
const expectedFillableAmount = new BigNumber(450980);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
const calculatedFillableAmount = calculator.computeRemainingMakerFillable();
const numberOfFillsInRatio = calculatedFillableAmount.dividedToIntegerBy(orderToFeeRatio);
const calculatedFillableAmountPlusFees = calculatedFillableAmount.plus(numberOfFillsInRatio);
expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(transferrableMakerTokenAmount);
expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(remainingMakerTokenAmount);
expect(calculatedFillableAmount).to.be.bignumber.equal(expectedFillableAmount);
expect(numberOfFillsInRatio.decimalPlaces()).to.be.equal(0);
});
});
const orderToFeeRatio = signedOrder.makerTokenAmount.dividedToIntegerBy(signedOrder.makerFee);
const expectedFillableAmount = new BigNumber(450980);
calculator = new RemainingFillableCalculator(
signedOrder,
isMakerTokenZRX,
transferrableMakerTokenAmount,
transferrableMakerFeeTokenAmount,
remainingMakerTokenAmount,
);
const calculatedFillableAmount = calculator.computeRemainingMakerFillable();
const numberOfFillsInRatio = calculatedFillableAmount.dividedToIntegerBy(orderToFeeRatio);
const calculatedFillableAmountPlusFees = calculatedFillableAmount.plus(numberOfFillsInRatio);
expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(transferrableMakerTokenAmount);
expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(remainingMakerTokenAmount);
expect(calculatedFillableAmount).to.be.bignumber.equal(expectedFillableAmount);
expect(numberOfFillsInRatio.decimalPlaces()).to.be.equal(0);
});
});
});

View File

@@ -17,69 +17,69 @@ chaiSetup.configure();
const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL);
describe('SubscriptionTest', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let userAddresses: string[];
let tokens: Token[];
let coinbase: string;
let addressWithoutFunds: string;
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
userAddresses = await zeroEx.getAvailableAddressesAsync();
tokens = await zeroEx.tokenRegistry.getTokensAsync();
coinbase = userAddresses[0];
addressWithoutFunds = userAddresses[1];
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#subscribe', () => {
const indexFilterValues = {};
let tokenAddress: string;
const allowanceAmount = new BigNumber(42);
let stubs: Sinon.SinonStub[] = [];
before(() => {
const token = tokens[0];
tokenAddress = token.address;
});
afterEach(() => {
zeroEx.token.unsubscribeAll();
_.each(stubs, s => s.restore());
stubs = [];
});
it('Should receive the Error when an error occurs while fetching the block', (done: DoneCallback) => {
(async () => {
const errMsg = 'Error fetching block';
const callback = assertNodeCallbackError(done, errMsg);
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error(errMsg))];
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
})().catch(done);
});
it('Should receive the Error when an error occurs while reconciling the new block', (done: DoneCallback) => {
(async () => {
const errMsg = 'Error fetching logs';
const callback = assertNodeCallbackError(done, errMsg);
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getLogsAsync').throws(new Error(errMsg))];
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
})().catch(done);
});
it('Should allow unsubscribeAll to be called successfully after an error', (done: DoneCallback) => {
(async () => {
const callback = (err: Error | null, logEvent?: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop;
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error('JSON RPC error'))];
zeroEx.token.unsubscribeAll();
done();
})().catch(done);
});
});
let web3: Web3;
let zeroEx: ZeroEx;
let userAddresses: string[];
let tokens: Token[];
let coinbase: string;
let addressWithoutFunds: string;
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
userAddresses = await zeroEx.getAvailableAddressesAsync();
tokens = await zeroEx.tokenRegistry.getTokensAsync();
coinbase = userAddresses[0];
addressWithoutFunds = userAddresses[1];
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#subscribe', () => {
const indexFilterValues = {};
let tokenAddress: string;
const allowanceAmount = new BigNumber(42);
let stubs: Sinon.SinonStub[] = [];
before(() => {
const token = tokens[0];
tokenAddress = token.address;
});
afterEach(() => {
zeroEx.token.unsubscribeAll();
_.each(stubs, s => s.restore());
stubs = [];
});
it('Should receive the Error when an error occurs while fetching the block', (done: DoneCallback) => {
(async () => {
const errMsg = 'Error fetching block';
const callback = assertNodeCallbackError(done, errMsg);
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error(errMsg))];
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
})().catch(done);
});
it('Should receive the Error when an error occurs while reconciling the new block', (done: DoneCallback) => {
(async () => {
const errMsg = 'Error fetching logs';
const callback = assertNodeCallbackError(done, errMsg);
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getLogsAsync').throws(new Error(errMsg))];
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
})().catch(done);
});
it('Should allow unsubscribeAll to be called successfully after an error', (done: DoneCallback) => {
(async () => {
const callback = (err: Error | null, logEvent?: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop;
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
stubs = [Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync').throws(new Error('JSON RPC error'))];
zeroEx.token.unsubscribeAll();
done();
})().catch(done);
});
});
});

View File

@@ -17,113 +17,113 @@ const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL);
const TOKEN_REGISTRY_SIZE_AFTER_MIGRATION = 7;
describe('TokenRegistryWrapper', () => {
let zeroEx: ZeroEx;
let tokens: Token[];
const tokenAddressBySymbol: { [symbol: string]: string } = {};
const tokenAddressByName: { [symbol: string]: string } = {};
const tokenBySymbol: { [symbol: string]: Token } = {};
const tokenByName: { [symbol: string]: Token } = {};
const registeredSymbol = 'ZRX';
const registeredName = '0x Protocol Token';
const unregisteredSymbol = 'MAL';
const unregisteredName = 'Malicious Token';
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
const web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
tokens = await zeroEx.tokenRegistry.getTokensAsync();
_.map(tokens, token => {
tokenAddressBySymbol[token.symbol] = token.address;
tokenAddressByName[token.name] = token.address;
tokenBySymbol[token.symbol] = token;
tokenByName[token.name] = token;
});
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#getTokensAsync', () => {
it('should return all the tokens added to the tokenRegistry during the migration', async () => {
expect(tokens).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION);
let zeroEx: ZeroEx;
let tokens: Token[];
const tokenAddressBySymbol: { [symbol: string]: string } = {};
const tokenAddressByName: { [symbol: string]: string } = {};
const tokenBySymbol: { [symbol: string]: Token } = {};
const tokenByName: { [symbol: string]: Token } = {};
const registeredSymbol = 'ZRX';
const registeredName = '0x Protocol Token';
const unregisteredSymbol = 'MAL';
const unregisteredName = 'Malicious Token';
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
const web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
tokens = await zeroEx.tokenRegistry.getTokensAsync();
_.map(tokens, token => {
tokenAddressBySymbol[token.symbol] = token.address;
tokenAddressByName[token.name] = token.address;
tokenBySymbol[token.symbol] = token;
tokenByName[token.name] = token;
});
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#getTokensAsync', () => {
it('should return all the tokens added to the tokenRegistry during the migration', async () => {
expect(tokens).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION);
const schemaValidator = new SchemaValidator();
_.each(tokens, token => {
const validationResult = schemaValidator.validate(token, schemas.tokenSchema);
expect(validationResult.errors).to.have.lengthOf(0);
});
});
});
describe('#getTokenAddressesAsync', () => {
it('should return all the token addresses added to the tokenRegistry during the migration', async () => {
const tokenAddresses = await zeroEx.tokenRegistry.getTokenAddressesAsync();
expect(tokenAddresses).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION);
const schemaValidator = new SchemaValidator();
_.each(tokens, token => {
const validationResult = schemaValidator.validate(token, schemas.tokenSchema);
expect(validationResult.errors).to.have.lengthOf(0);
});
});
});
describe('#getTokenAddressesAsync', () => {
it('should return all the token addresses added to the tokenRegistry during the migration', async () => {
const tokenAddresses = await zeroEx.tokenRegistry.getTokenAddressesAsync();
expect(tokenAddresses).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION);
const schemaValidator = new SchemaValidator();
_.each(tokenAddresses, tokenAddress => {
const validationResult = schemaValidator.validate(tokenAddress, schemas.addressSchema);
expect(validationResult.errors).to.have.lengthOf(0);
expect(tokenAddress).to.not.be.equal(ZeroEx.NULL_ADDRESS);
});
});
});
describe('#getTokenAddressBySymbol', () => {
it('should return correct address for a token in the registry', async () => {
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(registeredSymbol);
expect(tokenAddress).to.be.equal(tokenAddressBySymbol[registeredSymbol]);
});
it('should return undefined for a token out of registry', async () => {
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(unregisteredSymbol);
expect(tokenAddress).to.be.undefined();
});
});
describe('#getTokenAddressByName', () => {
it('should return correct address for a token in the registry', async () => {
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(registeredName);
expect(tokenAddress).to.be.equal(tokenAddressByName[registeredName]);
});
it('should return undefined for a token out of registry', async () => {
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(unregisteredName);
expect(tokenAddress).to.be.undefined();
});
});
describe('#getTokenBySymbol', () => {
it('should return correct token for a token in the registry', async () => {
const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(registeredSymbol);
expect(token).to.be.deep.equal(tokenBySymbol[registeredSymbol]);
});
it('should return undefined for a token out of registry', async () => {
const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(unregisteredSymbol);
expect(token).to.be.undefined();
});
});
describe('#getTokenByName', () => {
it('should return correct token for a token in the registry', async () => {
const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(registeredName);
expect(token).to.be.deep.equal(tokenByName[registeredName]);
});
it('should return undefined for a token out of registry', async () => {
const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(unregisteredName);
expect(token).to.be.undefined();
});
});
describe('#getTokenIfExistsAsync', () => {
it('should return the token added to the tokenRegistry during the migration', async () => {
const aToken = tokens[0];
const schemaValidator = new SchemaValidator();
_.each(tokenAddresses, tokenAddress => {
const validationResult = schemaValidator.validate(tokenAddress, schemas.addressSchema);
expect(validationResult.errors).to.have.lengthOf(0);
expect(tokenAddress).to.not.be.equal(ZeroEx.NULL_ADDRESS);
});
});
});
describe('#getTokenAddressBySymbol', () => {
it('should return correct address for a token in the registry', async () => {
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(registeredSymbol);
expect(tokenAddress).to.be.equal(tokenAddressBySymbol[registeredSymbol]);
});
it('should return undefined for a token out of registry', async () => {
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync(unregisteredSymbol);
expect(tokenAddress).to.be.undefined();
});
});
describe('#getTokenAddressByName', () => {
it('should return correct address for a token in the registry', async () => {
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(registeredName);
expect(tokenAddress).to.be.equal(tokenAddressByName[registeredName]);
});
it('should return undefined for a token out of registry', async () => {
const tokenAddress = await zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync(unregisteredName);
expect(tokenAddress).to.be.undefined();
});
});
describe('#getTokenBySymbol', () => {
it('should return correct token for a token in the registry', async () => {
const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(registeredSymbol);
expect(token).to.be.deep.equal(tokenBySymbol[registeredSymbol]);
});
it('should return undefined for a token out of registry', async () => {
const token = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync(unregisteredSymbol);
expect(token).to.be.undefined();
});
});
describe('#getTokenByName', () => {
it('should return correct token for a token in the registry', async () => {
const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(registeredName);
expect(token).to.be.deep.equal(tokenByName[registeredName]);
});
it('should return undefined for a token out of registry', async () => {
const token = await zeroEx.tokenRegistry.getTokenByNameIfExistsAsync(unregisteredName);
expect(token).to.be.undefined();
});
});
describe('#getTokenIfExistsAsync', () => {
it('should return the token added to the tokenRegistry during the migration', async () => {
const aToken = tokens[0];
const token = await zeroEx.tokenRegistry.getTokenIfExistsAsync(aToken.address);
const schemaValidator = new SchemaValidator();
const validationResult = schemaValidator.validate(token, schemas.tokenSchema);
expect(validationResult.errors).to.have.lengthOf(0);
});
it('should return return undefined when passed a token address not in the tokenRegistry', async () => {
const unregisteredTokenAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
const tokenIfExists = await zeroEx.tokenRegistry.getTokenIfExistsAsync(unregisteredTokenAddress);
expect(tokenIfExists).to.be.undefined();
});
});
const token = await zeroEx.tokenRegistry.getTokenIfExistsAsync(aToken.address);
const schemaValidator = new SchemaValidator();
const validationResult = schemaValidator.validate(token, schemas.tokenSchema);
expect(validationResult.errors).to.have.lengthOf(0);
});
it('should return return undefined when passed a token address not in the tokenRegistry', async () => {
const unregisteredTokenAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631';
const tokenIfExists = await zeroEx.tokenRegistry.getTokenIfExistsAsync(unregisteredTokenAddress);
expect(tokenIfExists).to.be.undefined();
});
});
});

View File

@@ -10,27 +10,27 @@ chaiSetup.configure();
const expect = chai.expect;
describe('TokenTransferProxyWrapper', () => {
let zeroEx: ZeroEx;
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
const web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
});
describe('#isAuthorizedAsync', () => {
it('should return false if the address is not authorized', async () => {
const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(ZeroEx.NULL_ADDRESS);
expect(isAuthorized).to.be.false();
});
});
describe('#getAuthorizedAddressesAsync', () => {
it('should return the list of authorized addresses', async () => {
const authorizedAddresses = await zeroEx.proxy.getAuthorizedAddressesAsync();
for (const authorizedAddress of authorizedAddresses) {
const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(authorizedAddress);
expect(isAuthorized).to.be.true();
}
});
});
let zeroEx: ZeroEx;
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
const web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
});
describe('#isAuthorizedAsync', () => {
it('should return false if the address is not authorized', async () => {
const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(ZeroEx.NULL_ADDRESS);
expect(isAuthorized).to.be.false();
});
});
describe('#getAuthorizedAddressesAsync', () => {
it('should return the list of authorized addresses', async () => {
const authorizedAddresses = await zeroEx.proxy.getAuthorizedAddressesAsync();
for (const authorizedAddress of authorizedAddresses) {
const isAuthorized = await zeroEx.proxy.isAuthorizedAsync(authorizedAddress);
expect(isAuthorized).to.be.true();
}
});
});
});

View File

@@ -6,15 +6,15 @@ import 'mocha';
import * as Web3 from 'web3';
import {
ApprovalContractEventArgs,
BlockParamLiteral,
BlockRange,
DecodedLogEvent,
Token,
TokenEvents,
TransferContractEventArgs,
ZeroEx,
ZeroExError,
ApprovalContractEventArgs,
BlockParamLiteral,
BlockRange,
DecodedLogEvent,
Token,
TokenEvents,
TransferContractEventArgs,
ZeroEx,
ZeroExError,
} from '../src';
import { DoneCallback } from '../src/types';
@@ -29,493 +29,493 @@ const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle(constants.RPC_URL);
describe('TokenWrapper', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let userAddresses: string[];
let tokens: Token[];
let tokenUtils: TokenUtils;
let coinbase: string;
let addressWithoutFunds: string;
let web3Wrapper: Web3Wrapper;
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
web3Wrapper = new Web3Wrapper(web3.currentProvider);
userAddresses = await zeroEx.getAvailableAddressesAsync();
tokens = await zeroEx.tokenRegistry.getTokensAsync();
tokenUtils = new TokenUtils(tokens);
coinbase = userAddresses[0];
addressWithoutFunds = userAddresses[1];
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#transferAsync', () => {
let token: Token;
let transferAmount: BigNumber;
before(() => {
token = tokens[0];
transferAmount = new BigNumber(42);
});
it('should successfully transfer tokens', async () => {
const fromAddress = coinbase;
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 postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
return expect(postBalance).to.be.bignumber.equal(transferAmount);
});
it('should fail to transfer tokens if fromAddress has an insufficient balance', async () => {
const fromAddress = addressWithoutFunds;
const toAddress = coinbase;
return expect(
zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount),
).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
});
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
const fromAddress = coinbase;
const toAddress = coinbase;
return expect(
zeroEx.token.transferAsync(nonExistentTokenAddress, fromAddress, toAddress, transferAmount),
).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist);
});
});
describe('#transferFromAsync', () => {
let token: Token;
let toAddress: string;
let senderAddress: string;
before(async () => {
token = tokens[0];
toAddress = addressWithoutFunds;
senderAddress = userAddresses[2];
});
it('should fail to transfer tokens if fromAddress has insufficient allowance set', async () => {
const fromAddress = coinbase;
const transferAmount = new BigNumber(42);
let web3: Web3;
let zeroEx: ZeroEx;
let userAddresses: string[];
let tokens: Token[];
let tokenUtils: TokenUtils;
let coinbase: string;
let addressWithoutFunds: string;
let web3Wrapper: Web3Wrapper;
const config = {
networkId: constants.TESTRPC_NETWORK_ID,
};
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider, config);
web3Wrapper = new Web3Wrapper(web3.currentProvider);
userAddresses = await zeroEx.getAvailableAddressesAsync();
tokens = await zeroEx.tokenRegistry.getTokensAsync();
tokenUtils = new TokenUtils(tokens);
coinbase = userAddresses[0];
addressWithoutFunds = userAddresses[1];
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
});
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
describe('#transferAsync', () => {
let token: Token;
let transferAmount: BigNumber;
before(() => {
token = tokens[0];
transferAmount = new BigNumber(42);
});
it('should successfully transfer tokens', async () => {
const fromAddress = coinbase;
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 postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
return expect(postBalance).to.be.bignumber.equal(transferAmount);
});
it('should fail to transfer tokens if fromAddress has an insufficient balance', async () => {
const fromAddress = addressWithoutFunds;
const toAddress = coinbase;
return expect(
zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount),
).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
});
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
const fromAddress = coinbase;
const toAddress = coinbase;
return expect(
zeroEx.token.transferAsync(nonExistentTokenAddress, fromAddress, toAddress, transferAmount),
).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist);
});
});
describe('#transferFromAsync', () => {
let token: Token;
let toAddress: string;
let senderAddress: string;
before(async () => {
token = tokens[0];
toAddress = addressWithoutFunds;
senderAddress = userAddresses[2];
});
it('should fail to transfer tokens if fromAddress has insufficient allowance set', async () => {
const fromAddress = coinbase;
const transferAmount = new BigNumber(42);
const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress);
expect(fromAddressBalance).to.be.bignumber.greaterThan(transferAmount);
const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress);
expect(fromAddressBalance).to.be.bignumber.greaterThan(transferAmount);
const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(token.address, fromAddress, toAddress);
expect(fromAddressAllowance).to.be.bignumber.equal(0);
const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(token.address, fromAddress, toAddress);
expect(fromAddressAllowance).to.be.bignumber.equal(0);
return expect(
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
});
it('[regression] should fail to transfer tokens if set allowance for toAddress instead of senderAddress', async () => {
const fromAddress = coinbase;
const transferAmount = new BigNumber(42);
return expect(
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
});
it('[regression] should fail to transfer tokens if set allowance for toAddress instead of senderAddress', async () => {
const fromAddress = coinbase;
const transferAmount = new BigNumber(42);
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, toAddress, transferAmount);
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, toAddress, transferAmount);
return expect(
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
});
it('should fail to transfer tokens if fromAddress has insufficient balance', async () => {
const fromAddress = addressWithoutFunds;
const transferAmount = new BigNumber(42);
return expect(
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
).to.be.rejectedWith(ZeroExError.InsufficientAllowanceForTransfer);
});
it('should fail to transfer tokens if fromAddress has insufficient balance', async () => {
const fromAddress = addressWithoutFunds;
const transferAmount = new BigNumber(42);
const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress);
expect(fromAddressBalance).to.be.bignumber.equal(0);
const fromAddressBalance = await zeroEx.token.getBalanceAsync(token.address, fromAddress);
expect(fromAddressBalance).to.be.bignumber.equal(0);
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount);
const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(
token.address,
fromAddress,
senderAddress,
);
expect(fromAddressAllowance).to.be.bignumber.equal(transferAmount);
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount);
const fromAddressAllowance = await zeroEx.token.getAllowanceAsync(
token.address,
fromAddress,
senderAddress,
);
expect(fromAddressAllowance).to.be.bignumber.equal(transferAmount);
return expect(
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
});
it('should successfully transfer tokens', async () => {
const fromAddress = coinbase;
return expect(
zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount),
).to.be.rejectedWith(ZeroExError.InsufficientBalanceForTransfer);
});
it('should successfully transfer tokens', async () => {
const fromAddress = coinbase;
const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
expect(preBalance).to.be.bignumber.equal(0);
const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
expect(preBalance).to.be.bignumber.equal(0);
const transferAmount = new BigNumber(42);
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount);
const transferAmount = new BigNumber(42);
await zeroEx.token.setAllowanceAsync(token.address, fromAddress, senderAddress, transferAmount);
await zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount);
const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
return expect(postBalance).to.be.bignumber.equal(transferAmount);
});
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
const fromAddress = coinbase;
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
return expect(
zeroEx.token.transferFromAsync(
nonExistentTokenAddress,
fromAddress,
toAddress,
senderAddress,
new BigNumber(42),
),
).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist);
});
});
describe('#getBalanceAsync', () => {
describe('With web3 provider with accounts', () => {
it('should return the balance for an existing ERC20 token', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress);
const expectedBalance = new BigNumber('1000000000000000000000000000');
return expect(balance).to.be.bignumber.equal(expectedBalance);
});
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
const ownerAddress = coinbase;
return expect(zeroEx.token.getBalanceAsync(nonExistentTokenAddress, ownerAddress)).to.be.rejectedWith(
ZeroExError.TokenContractDoesNotExist,
);
});
it('should return a balance of 0 for a non-existent owner address', async () => {
const token = tokens[0];
const nonExistentOwner = '0x198c6ad858f213fb31b6fe809e25040e6b964593';
const balance = await zeroEx.token.getBalanceAsync(token.address, nonExistentOwner);
const expectedBalance = new BigNumber(0);
return expect(balance).to.be.bignumber.equal(expectedBalance);
});
});
describe('With web3 provider without accounts', () => {
let zeroExWithoutAccounts: ZeroEx;
before(async () => {
const hasAddresses = false;
const web3WithoutAccounts = web3Factory.create(hasAddresses);
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config);
});
it('should return balance even when called with Web3 provider instance without addresses', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress);
const expectedBalance = new BigNumber('1000000000000000000000000000');
return expect(balance).to.be.bignumber.equal(expectedBalance);
});
});
});
describe('#setAllowanceAsync', () => {
it("should set the spender's allowance", async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const spenderAddress = addressWithoutFunds;
await zeroEx.token.transferFromAsync(token.address, fromAddress, toAddress, senderAddress, transferAmount);
const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
return expect(postBalance).to.be.bignumber.equal(transferAmount);
});
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
const fromAddress = coinbase;
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
return expect(
zeroEx.token.transferFromAsync(
nonExistentTokenAddress,
fromAddress,
toAddress,
senderAddress,
new BigNumber(42),
),
).to.be.rejectedWith(ZeroExError.TokenContractDoesNotExist);
});
});
describe('#getBalanceAsync', () => {
describe('With web3 provider with accounts', () => {
it('should return the balance for an existing ERC20 token', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress);
const expectedBalance = new BigNumber('1000000000000000000000000000');
return expect(balance).to.be.bignumber.equal(expectedBalance);
});
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
const nonExistentTokenAddress = '0x9dd402f14d67e001d8efbe6583e51bf9706aa065';
const ownerAddress = coinbase;
return expect(zeroEx.token.getBalanceAsync(nonExistentTokenAddress, ownerAddress)).to.be.rejectedWith(
ZeroExError.TokenContractDoesNotExist,
);
});
it('should return a balance of 0 for a non-existent owner address', async () => {
const token = tokens[0];
const nonExistentOwner = '0x198c6ad858f213fb31b6fe809e25040e6b964593';
const balance = await zeroEx.token.getBalanceAsync(token.address, nonExistentOwner);
const expectedBalance = new BigNumber(0);
return expect(balance).to.be.bignumber.equal(expectedBalance);
});
});
describe('With web3 provider without accounts', () => {
let zeroExWithoutAccounts: ZeroEx;
before(async () => {
const hasAddresses = false;
const web3WithoutAccounts = web3Factory.create(hasAddresses);
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config);
});
it('should return balance even when called with Web3 provider instance without addresses', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress);
const expectedBalance = new BigNumber('1000000000000000000000000000');
return expect(balance).to.be.bignumber.equal(expectedBalance);
});
});
});
describe('#setAllowanceAsync', () => {
it("should set the spender's allowance", async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const spenderAddress = addressWithoutFunds;
const allowanceBeforeSet = await zeroEx.token.getAllowanceAsync(
token.address,
ownerAddress,
spenderAddress,
);
const expectedAllowanceBeforeAllowanceSet = new BigNumber(0);
expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet);
const allowanceBeforeSet = await zeroEx.token.getAllowanceAsync(
token.address,
ownerAddress,
spenderAddress,
);
const expectedAllowanceBeforeAllowanceSet = new BigNumber(0);
expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet);
const amountInBaseUnits = new BigNumber(50);
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
const amountInBaseUnits = new BigNumber(50);
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
const allowanceAfterSet = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
const expectedAllowanceAfterAllowanceSet = amountInBaseUnits;
return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet);
});
});
describe('#setUnlimitedAllowanceAsync', () => {
it("should set the unlimited spender's allowance", async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const spenderAddress = addressWithoutFunds;
const allowanceAfterSet = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
const expectedAllowanceAfterAllowanceSet = amountInBaseUnits;
return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet);
});
});
describe('#setUnlimitedAllowanceAsync', () => {
it("should set the unlimited spender's allowance", async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const spenderAddress = addressWithoutFunds;
await zeroEx.token.setUnlimitedAllowanceAsync(token.address, ownerAddress, spenderAddress);
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
it('should reduce the gas cost for transfers including tokens with unlimited allowance support', async () => {
const transferAmount = new BigNumber(5);
const zrx = tokenUtils.getProtocolTokenOrThrow();
const [, userWithNormalAllowance, userWithUnlimitedAllowance] = userAddresses;
await zeroEx.token.setAllowanceAsync(zrx.address, coinbase, userWithNormalAllowance, transferAmount);
await zeroEx.token.setUnlimitedAllowanceAsync(zrx.address, coinbase, userWithUnlimitedAllowance);
await zeroEx.token.setUnlimitedAllowanceAsync(token.address, ownerAddress, spenderAddress);
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
it('should reduce the gas cost for transfers including tokens with unlimited allowance support', async () => {
const transferAmount = new BigNumber(5);
const zrx = tokenUtils.getProtocolTokenOrThrow();
const [, userWithNormalAllowance, userWithUnlimitedAllowance] = userAddresses;
await zeroEx.token.setAllowanceAsync(zrx.address, coinbase, userWithNormalAllowance, transferAmount);
await zeroEx.token.setUnlimitedAllowanceAsync(zrx.address, coinbase, userWithUnlimitedAllowance);
const initBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance);
const initBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync(
userWithUnlimitedAllowance,
);
const initBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance);
const initBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync(
userWithUnlimitedAllowance,
);
await zeroEx.token.transferFromAsync(
zrx.address,
coinbase,
userWithNormalAllowance,
userWithNormalAllowance,
transferAmount,
);
await zeroEx.token.transferFromAsync(
zrx.address,
coinbase,
userWithUnlimitedAllowance,
userWithUnlimitedAllowance,
transferAmount,
);
await zeroEx.token.transferFromAsync(
zrx.address,
coinbase,
userWithNormalAllowance,
userWithNormalAllowance,
transferAmount,
);
await zeroEx.token.transferFromAsync(
zrx.address,
coinbase,
userWithUnlimitedAllowance,
userWithUnlimitedAllowance,
transferAmount,
);
const finalBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance);
const finalBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync(
userWithUnlimitedAllowance,
);
const finalBalanceWithNormalAllowance = await web3Wrapper.getBalanceInWeiAsync(userWithNormalAllowance);
const finalBalanceWithUnlimitedAllowance = await web3Wrapper.getBalanceInWeiAsync(
userWithUnlimitedAllowance,
);
const normalGasCost = initBalanceWithNormalAllowance.minus(finalBalanceWithNormalAllowance);
const unlimitedGasCost = initBalanceWithUnlimitedAllowance.minus(finalBalanceWithUnlimitedAllowance);
const normalGasCost = initBalanceWithNormalAllowance.minus(finalBalanceWithNormalAllowance);
const unlimitedGasCost = initBalanceWithUnlimitedAllowance.minus(finalBalanceWithUnlimitedAllowance);
// In theory the gas cost with unlimited allowance should be smaller, but with testrpc it's actually bigger.
// This needs to be investigated in ethereumjs-vm. This test is essentially a repro.
// TODO: Make this test pass with inverted assertion.
expect(unlimitedGasCost.toNumber()).to.be.gt(normalGasCost.toNumber());
});
});
describe('#getAllowanceAsync', () => {
describe('With web3 provider with accounts', () => {
it('should get the proxy allowance', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const spenderAddress = addressWithoutFunds;
// In theory the gas cost with unlimited allowance should be smaller, but with testrpc it's actually bigger.
// This needs to be investigated in ethereumjs-vm. This test is essentially a repro.
// TODO: Make this test pass with inverted assertion.
expect(unlimitedGasCost.toNumber()).to.be.gt(normalGasCost.toNumber());
});
});
describe('#getAllowanceAsync', () => {
describe('With web3 provider with accounts', () => {
it('should get the proxy allowance', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const spenderAddress = addressWithoutFunds;
const amountInBaseUnits = new BigNumber(50);
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
const amountInBaseUnits = new BigNumber(50);
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
const expectedAllowance = amountInBaseUnits;
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
});
it('should return 0 if no allowance set yet', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const spenderAddress = addressWithoutFunds;
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
const expectedAllowance = new BigNumber(0);
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
});
});
describe('With web3 provider without accounts', () => {
let zeroExWithoutAccounts: ZeroEx;
before(async () => {
const hasAddresses = false;
const web3WithoutAccounts = web3Factory.create(hasAddresses);
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config);
});
it('should get the proxy allowance', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const spenderAddress = addressWithoutFunds;
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
const expectedAllowance = amountInBaseUnits;
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
});
it('should return 0 if no allowance set yet', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const spenderAddress = addressWithoutFunds;
const allowance = await zeroEx.token.getAllowanceAsync(token.address, ownerAddress, spenderAddress);
const expectedAllowance = new BigNumber(0);
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
});
});
describe('With web3 provider without accounts', () => {
let zeroExWithoutAccounts: ZeroEx;
before(async () => {
const hasAddresses = false;
const web3WithoutAccounts = web3Factory.create(hasAddresses);
zeroExWithoutAccounts = new ZeroEx(web3WithoutAccounts.currentProvider, config);
});
it('should get the proxy allowance', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const spenderAddress = addressWithoutFunds;
const amountInBaseUnits = new BigNumber(50);
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
const amountInBaseUnits = new BigNumber(50);
await zeroEx.token.setAllowanceAsync(token.address, ownerAddress, spenderAddress, amountInBaseUnits);
const allowance = await zeroExWithoutAccounts.token.getAllowanceAsync(
token.address,
ownerAddress,
spenderAddress,
);
const expectedAllowance = amountInBaseUnits;
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
});
});
});
describe('#getProxyAllowanceAsync', () => {
it('should get the proxy allowance', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const allowance = await zeroExWithoutAccounts.token.getAllowanceAsync(
token.address,
ownerAddress,
spenderAddress,
);
const expectedAllowance = amountInBaseUnits;
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
});
});
});
describe('#getProxyAllowanceAsync', () => {
it('should get the proxy allowance', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const amountInBaseUnits = new BigNumber(50);
await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits);
const amountInBaseUnits = new BigNumber(50);
await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits);
const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
const expectedAllowance = amountInBaseUnits;
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
});
});
describe('#setProxyAllowanceAsync', () => {
it('should set the proxy allowance', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
const expectedAllowance = amountInBaseUnits;
return expect(allowance).to.be.bignumber.equal(expectedAllowance);
});
});
describe('#setProxyAllowanceAsync', () => {
it('should set the proxy allowance', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const allowanceBeforeSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
const expectedAllowanceBeforeAllowanceSet = new BigNumber(0);
expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet);
const allowanceBeforeSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
const expectedAllowanceBeforeAllowanceSet = new BigNumber(0);
expect(allowanceBeforeSet).to.be.bignumber.equal(expectedAllowanceBeforeAllowanceSet);
const amountInBaseUnits = new BigNumber(50);
await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits);
const amountInBaseUnits = new BigNumber(50);
await zeroEx.token.setProxyAllowanceAsync(token.address, ownerAddress, amountInBaseUnits);
const allowanceAfterSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
const expectedAllowanceAfterAllowanceSet = amountInBaseUnits;
return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet);
});
});
describe('#setUnlimitedProxyAllowanceAsync', () => {
it('should set the unlimited proxy allowance', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
const allowanceAfterSet = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
const expectedAllowanceAfterAllowanceSet = amountInBaseUnits;
return expect(allowanceAfterSet).to.be.bignumber.equal(expectedAllowanceAfterAllowanceSet);
});
});
describe('#setUnlimitedProxyAllowanceAsync', () => {
it('should set the unlimited proxy allowance', async () => {
const token = tokens[0];
const ownerAddress = coinbase;
await zeroEx.token.setUnlimitedProxyAllowanceAsync(token.address, ownerAddress);
const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
});
describe('#subscribe', () => {
const indexFilterValues = {};
let tokenAddress: string;
const transferAmount = new BigNumber(42);
const allowanceAmount = new BigNumber(42);
before(() => {
const token = tokens[0];
tokenAddress = token.address;
});
afterEach(() => {
zeroEx.token.unsubscribeAll();
});
// Hack: Mocha does not allow a test to be both async and have a `done` callback
// Since we need to await the receipt of the event in the `subscribe` callback,
// 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 tokens are transfered', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<TransferContractEventArgs>) => {
expect(logEvent.isRemoved).to.be.false();
expect(logEvent.log.logIndex).to.be.equal(0);
expect(logEvent.log.transactionIndex).to.be.equal(0);
expect(logEvent.log.blockNumber).to.be.a('number');
const args = logEvent.log.args;
expect(args._from).to.be.equal(coinbase);
expect(args._to).to.be.equal(addressWithoutFunds);
expect(args._value).to.be.bignumber.equal(transferAmount);
},
);
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callback);
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
})().catch(done);
});
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
expect(logEvent.isRemoved).to.be.false();
const args = logEvent.log.args;
expect(args._owner).to.be.equal(coinbase);
expect(args._spender).to.be.equal(addressWithoutFunds);
expect(args._value).to.be.bignumber.equal(allowanceAmount);
},
);
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
})().catch(done);
});
it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => {
(async () => {
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
},
);
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled);
const callbackToBeCalled = reportNodeCallbackErrors(done)();
const newProvider = web3Factory.getRpcProvider();
zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID);
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled);
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
})().catch(done);
});
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
(async () => {
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
},
);
const subscriptionToken = zeroEx.token.subscribe(
tokenAddress,
TokenEvents.Transfer,
indexFilterValues,
callbackNeverToBeCalled,
);
zeroEx.token.unsubscribe(subscriptionToken);
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
done();
})().catch(done);
});
});
describe('#getLogsAsync', () => {
let tokenAddress: string;
let tokenTransferProxyAddress: string;
const blockRange: BlockRange = {
fromBlock: 0,
toBlock: BlockParamLiteral.Latest,
};
let txHash: string;
before(() => {
const token = tokens[0];
tokenAddress = token.address;
tokenTransferProxyAddress = zeroEx.proxy.getContractAddress();
});
it('should get logs with decoded args emitted by Approval', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = TokenEvents.Approval;
const indexFilterValues = {};
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
tokenAddress,
eventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(logs[0].event).to.be.equal(eventName);
expect(args._owner).to.be.equal(coinbase);
expect(args._spender).to.be.equal(tokenTransferProxyAddress);
expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
it('should only get the logs with the correct event name', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
await zeroEx.awaitTransactionMinedAsync(txHash);
const differentEventName = TokenEvents.Transfer;
const indexFilterValues = {};
const logs = await zeroEx.token.getLogsAsync(
tokenAddress,
differentEventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(0);
});
it('should only get the logs with the correct indexed fields', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, addressWithoutFunds);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = TokenEvents.Approval;
const indexFilterValues = {
_owner: coinbase,
};
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
tokenAddress,
eventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(args._owner).to.be.equal(coinbase);
});
});
await zeroEx.token.setUnlimitedProxyAllowanceAsync(token.address, ownerAddress);
const allowance = await zeroEx.token.getProxyAllowanceAsync(token.address, ownerAddress);
return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
});
describe('#subscribe', () => {
const indexFilterValues = {};
let tokenAddress: string;
const transferAmount = new BigNumber(42);
const allowanceAmount = new BigNumber(42);
before(() => {
const token = tokens[0];
tokenAddress = token.address;
});
afterEach(() => {
zeroEx.token.unsubscribeAll();
});
// Hack: Mocha does not allow a test to be both async and have a `done` callback
// Since we need to await the receipt of the event in the `subscribe` callback,
// 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 tokens are transfered', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<TransferContractEventArgs>) => {
expect(logEvent.isRemoved).to.be.false();
expect(logEvent.log.logIndex).to.be.equal(0);
expect(logEvent.log.transactionIndex).to.be.equal(0);
expect(logEvent.log.blockNumber).to.be.a('number');
const args = logEvent.log.args;
expect(args._from).to.be.equal(coinbase);
expect(args._to).to.be.equal(addressWithoutFunds);
expect(args._value).to.be.bignumber.equal(transferAmount);
},
);
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callback);
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
})().catch(done);
});
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
(async () => {
const callback = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
expect(logEvent).to.not.be.undefined();
expect(logEvent.isRemoved).to.be.false();
const args = logEvent.log.args;
expect(args._owner).to.be.equal(coinbase);
expect(args._spender).to.be.equal(addressWithoutFunds);
expect(args._value).to.be.bignumber.equal(allowanceAmount);
},
);
zeroEx.token.subscribe(tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
})().catch(done);
});
it('Outstanding subscriptions are cancelled when zeroEx.setProvider called', (done: DoneCallback) => {
(async () => {
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
},
);
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled);
const callbackToBeCalled = reportNodeCallbackErrors(done)();
const newProvider = web3Factory.getRpcProvider();
zeroEx.setProvider(newProvider, constants.TESTRPC_NETWORK_ID);
zeroEx.token.subscribe(tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled);
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
})().catch(done);
});
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
(async () => {
const callbackNeverToBeCalled = reportNodeCallbackErrors(done)(
(logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
done(new Error('Expected this subscription to have been cancelled'));
},
);
const subscriptionToken = zeroEx.token.subscribe(
tokenAddress,
TokenEvents.Transfer,
indexFilterValues,
callbackNeverToBeCalled,
);
zeroEx.token.unsubscribe(subscriptionToken);
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
done();
})().catch(done);
});
});
describe('#getLogsAsync', () => {
let tokenAddress: string;
let tokenTransferProxyAddress: string;
const blockRange: BlockRange = {
fromBlock: 0,
toBlock: BlockParamLiteral.Latest,
};
let txHash: string;
before(() => {
const token = tokens[0];
tokenAddress = token.address;
tokenTransferProxyAddress = zeroEx.proxy.getContractAddress();
});
it('should get logs with decoded args emitted by Approval', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = TokenEvents.Approval;
const indexFilterValues = {};
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
tokenAddress,
eventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(logs[0].event).to.be.equal(eventName);
expect(args._owner).to.be.equal(coinbase);
expect(args._spender).to.be.equal(tokenTransferProxyAddress);
expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
});
it('should only get the logs with the correct event name', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
await zeroEx.awaitTransactionMinedAsync(txHash);
const differentEventName = TokenEvents.Transfer;
const indexFilterValues = {};
const logs = await zeroEx.token.getLogsAsync(
tokenAddress,
differentEventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(0);
});
it('should only get the logs with the correct indexed fields', async () => {
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
await zeroEx.awaitTransactionMinedAsync(txHash);
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, addressWithoutFunds);
await zeroEx.awaitTransactionMinedAsync(txHash);
const eventName = TokenEvents.Approval;
const indexFilterValues = {
_owner: coinbase,
};
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
tokenAddress,
eventName,
blockRange,
indexFilterValues,
);
expect(logs).to.have.length(1);
const args = logs[0].args;
expect(args._owner).to.be.equal(coinbase);
});
});
});
// tslint:disable:max-file-line-count

View File

@@ -4,10 +4,10 @@ import ChaiBigNumber = require('chai-bignumber');
import * as dirtyChai from 'dirty-chai';
export const chaiSetup = {
configure() {
chai.config.includeStack = true;
chai.use(ChaiBigNumber());
chai.use(dirtyChai);
chai.use(chaiAsPromised);
},
configure() {
chai.config.includeStack = true;
chai.use(ChaiBigNumber());
chai.use(dirtyChai);
chai.use(chaiAsPromised);
},
};

View File

@@ -1,11 +1,11 @@
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
RPC_URL: 'http://localhost:8545',
ROPSTEN_NETWORK_ID: 3,
KOVAN_NETWORK_ID: 42,
TESTRPC_NETWORK_ID: 50,
KOVAN_RPC_URL: 'https://kovan.infura.io/',
ROPSTEN_RPC_URL: 'https://ropsten.infura.io/',
ZRX_DECIMALS: 18,
GAS_ESTIMATE: 500000,
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
RPC_URL: 'http://localhost:8545',
ROPSTEN_NETWORK_ID: 3,
KOVAN_NETWORK_ID: 42,
TESTRPC_NETWORK_ID: 50,
KOVAN_RPC_URL: 'https://kovan.infura.io/',
ROPSTEN_RPC_URL: 'https://ropsten.infura.io/',
ZRX_DECIMALS: 18,
GAS_ESTIMATE: 500000,
};

View File

@@ -11,192 +11,192 @@ import { constants } from './constants';
const INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS = new BigNumber(100);
export class FillScenarios {
private _zeroEx: ZeroEx;
private _userAddresses: string[];
private _tokens: Token[];
private _coinbase: string;
private _zrxTokenAddress: string;
private _exchangeContractAddress: string;
constructor(
zeroEx: ZeroEx,
userAddresses: string[],
tokens: Token[],
zrxTokenAddress: string,
exchangeContractAddress: string,
) {
this._zeroEx = zeroEx;
this._userAddresses = userAddresses;
this._tokens = tokens;
this._coinbase = userAddresses[0];
this._zrxTokenAddress = zrxTokenAddress;
this._exchangeContractAddress = exchangeContractAddress;
}
public async initTokenBalancesAsync() {
const web3Wrapper = (this._zeroEx as any)._web3Wrapper as Web3Wrapper;
for (const token of this._tokens) {
if (token.symbol !== 'ZRX' && token.symbol !== 'WETH') {
const contractInstance = web3Wrapper.getContractInstance(
artifacts.DummyTokenArtifact.abi,
token.address,
);
const defaults = {};
const dummyToken = new DummyTokenContract(contractInstance, defaults);
const tokenSupply = ZeroEx.toBaseUnitAmount(INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS, token.decimals);
const txHash = await dummyToken.setBalance.sendTransactionAsync(this._coinbase, tokenSupply, {
from: this._coinbase,
});
await this._zeroEx.awaitTransactionMinedAsync(txHash);
}
}
}
public async createFillableSignedOrderAsync(
makerTokenAddress: string,
takerTokenAddress: string,
makerAddress: string,
takerAddress: string,
fillableAmount: BigNumber,
expirationUnixTimestampSec?: BigNumber,
): Promise<SignedOrder> {
return this.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
fillableAmount,
expirationUnixTimestampSec,
);
}
public async createFillableSignedOrderWithFeesAsync(
makerTokenAddress: string,
takerTokenAddress: string,
makerFee: BigNumber,
takerFee: BigNumber,
makerAddress: string,
takerAddress: string,
fillableAmount: BigNumber,
feeRecepient: string,
expirationUnixTimestampSec?: BigNumber,
): Promise<SignedOrder> {
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
makerTokenAddress,
takerTokenAddress,
makerFee,
takerFee,
makerAddress,
takerAddress,
fillableAmount,
fillableAmount,
feeRecepient,
expirationUnixTimestampSec,
);
}
public async createAsymmetricFillableSignedOrderAsync(
makerTokenAddress: string,
takerTokenAddress: string,
makerAddress: string,
takerAddress: string,
makerFillableAmount: BigNumber,
takerFillableAmount: BigNumber,
expirationUnixTimestampSec?: BigNumber,
): Promise<SignedOrder> {
const makerFee = new BigNumber(0);
const takerFee = new BigNumber(0);
const feeRecepient = constants.NULL_ADDRESS;
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
makerTokenAddress,
takerTokenAddress,
makerFee,
takerFee,
makerAddress,
takerAddress,
makerFillableAmount,
takerFillableAmount,
feeRecepient,
expirationUnixTimestampSec,
);
}
public async createPartiallyFilledSignedOrderAsync(
makerTokenAddress: string,
takerTokenAddress: string,
takerAddress: string,
fillableAmount: BigNumber,
partialFillAmount: BigNumber,
) {
const [makerAddress] = this._userAddresses;
const signedOrder = await this.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
fillableAmount,
);
const shouldThrowOnInsufficientBalanceOrAllowance = false;
await this._zeroEx.exchange.fillOrderAsync(
signedOrder,
partialFillAmount,
shouldThrowOnInsufficientBalanceOrAllowance,
takerAddress,
);
return signedOrder;
}
private async _createAsymmetricFillableSignedOrderWithFeesAsync(
makerTokenAddress: string,
takerTokenAddress: string,
makerFee: BigNumber,
takerFee: BigNumber,
makerAddress: string,
takerAddress: string,
makerFillableAmount: BigNumber,
takerFillableAmount: BigNumber,
feeRecepient: string,
expirationUnixTimestampSec?: BigNumber,
): Promise<SignedOrder> {
await Promise.all([
this._increaseBalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount),
this._increaseBalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount),
]);
await Promise.all([
this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee),
this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, takerAddress, takerFee),
]);
private _zeroEx: ZeroEx;
private _userAddresses: string[];
private _tokens: Token[];
private _coinbase: string;
private _zrxTokenAddress: string;
private _exchangeContractAddress: string;
constructor(
zeroEx: ZeroEx,
userAddresses: string[],
tokens: Token[],
zrxTokenAddress: string,
exchangeContractAddress: string,
) {
this._zeroEx = zeroEx;
this._userAddresses = userAddresses;
this._tokens = tokens;
this._coinbase = userAddresses[0];
this._zrxTokenAddress = zrxTokenAddress;
this._exchangeContractAddress = exchangeContractAddress;
}
public async initTokenBalancesAsync() {
const web3Wrapper = (this._zeroEx as any)._web3Wrapper as Web3Wrapper;
for (const token of this._tokens) {
if (token.symbol !== 'ZRX' && token.symbol !== 'WETH') {
const contractInstance = web3Wrapper.getContractInstance(
artifacts.DummyTokenArtifact.abi,
token.address,
);
const defaults = {};
const dummyToken = new DummyTokenContract(contractInstance, defaults);
const tokenSupply = ZeroEx.toBaseUnitAmount(INITIAL_COINBASE_TOKEN_SUPPLY_IN_UNITS, token.decimals);
const txHash = await dummyToken.setBalance.sendTransactionAsync(this._coinbase, tokenSupply, {
from: this._coinbase,
});
await this._zeroEx.awaitTransactionMinedAsync(txHash);
}
}
}
public async createFillableSignedOrderAsync(
makerTokenAddress: string,
takerTokenAddress: string,
makerAddress: string,
takerAddress: string,
fillableAmount: BigNumber,
expirationUnixTimestampSec?: BigNumber,
): Promise<SignedOrder> {
return this.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
fillableAmount,
expirationUnixTimestampSec,
);
}
public async createFillableSignedOrderWithFeesAsync(
makerTokenAddress: string,
takerTokenAddress: string,
makerFee: BigNumber,
takerFee: BigNumber,
makerAddress: string,
takerAddress: string,
fillableAmount: BigNumber,
feeRecepient: string,
expirationUnixTimestampSec?: BigNumber,
): Promise<SignedOrder> {
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
makerTokenAddress,
takerTokenAddress,
makerFee,
takerFee,
makerAddress,
takerAddress,
fillableAmount,
fillableAmount,
feeRecepient,
expirationUnixTimestampSec,
);
}
public async createAsymmetricFillableSignedOrderAsync(
makerTokenAddress: string,
takerTokenAddress: string,
makerAddress: string,
takerAddress: string,
makerFillableAmount: BigNumber,
takerFillableAmount: BigNumber,
expirationUnixTimestampSec?: BigNumber,
): Promise<SignedOrder> {
const makerFee = new BigNumber(0);
const takerFee = new BigNumber(0);
const feeRecepient = constants.NULL_ADDRESS;
return this._createAsymmetricFillableSignedOrderWithFeesAsync(
makerTokenAddress,
takerTokenAddress,
makerFee,
takerFee,
makerAddress,
takerAddress,
makerFillableAmount,
takerFillableAmount,
feeRecepient,
expirationUnixTimestampSec,
);
}
public async createPartiallyFilledSignedOrderAsync(
makerTokenAddress: string,
takerTokenAddress: string,
takerAddress: string,
fillableAmount: BigNumber,
partialFillAmount: BigNumber,
) {
const [makerAddress] = this._userAddresses;
const signedOrder = await this.createAsymmetricFillableSignedOrderAsync(
makerTokenAddress,
takerTokenAddress,
makerAddress,
takerAddress,
fillableAmount,
fillableAmount,
);
const shouldThrowOnInsufficientBalanceOrAllowance = false;
await this._zeroEx.exchange.fillOrderAsync(
signedOrder,
partialFillAmount,
shouldThrowOnInsufficientBalanceOrAllowance,
takerAddress,
);
return signedOrder;
}
private async _createAsymmetricFillableSignedOrderWithFeesAsync(
makerTokenAddress: string,
takerTokenAddress: string,
makerFee: BigNumber,
takerFee: BigNumber,
makerAddress: string,
takerAddress: string,
makerFillableAmount: BigNumber,
takerFillableAmount: BigNumber,
feeRecepient: string,
expirationUnixTimestampSec?: BigNumber,
): Promise<SignedOrder> {
await Promise.all([
this._increaseBalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount),
this._increaseBalanceAndAllowanceAsync(takerTokenAddress, takerAddress, takerFillableAmount),
]);
await Promise.all([
this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, makerAddress, makerFee),
this._increaseBalanceAndAllowanceAsync(this._zrxTokenAddress, takerAddress, takerFee),
]);
const signedOrder = await orderFactory.createSignedOrderAsync(
this._zeroEx,
makerAddress,
takerAddress,
makerFee,
takerFee,
makerFillableAmount,
makerTokenAddress,
takerFillableAmount,
takerTokenAddress,
this._exchangeContractAddress,
feeRecepient,
expirationUnixTimestampSec,
);
return signedOrder;
}
private async _increaseBalanceAndAllowanceAsync(
tokenAddress: string,
address: string,
amount: BigNumber,
): Promise<void> {
if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) {
return; // noop
}
await Promise.all([
this._increaseBalanceAsync(tokenAddress, address, amount),
this._increaseAllowanceAsync(tokenAddress, address, amount),
]);
}
private async _increaseBalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
await this._zeroEx.token.transferAsync(tokenAddress, this._coinbase, address, amount);
}
private async _increaseAllowanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
const oldMakerAllowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, address);
const newMakerAllowance = oldMakerAllowance.plus(amount);
await this._zeroEx.token.setProxyAllowanceAsync(tokenAddress, address, newMakerAllowance);
}
const signedOrder = await orderFactory.createSignedOrderAsync(
this._zeroEx,
makerAddress,
takerAddress,
makerFee,
takerFee,
makerFillableAmount,
makerTokenAddress,
takerFillableAmount,
takerTokenAddress,
this._exchangeContractAddress,
feeRecepient,
expirationUnixTimestampSec,
);
return signedOrder;
}
private async _increaseBalanceAndAllowanceAsync(
tokenAddress: string,
address: string,
amount: BigNumber,
): Promise<void> {
if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) {
return; // noop
}
await Promise.all([
this._increaseBalanceAsync(tokenAddress, address, amount),
this._increaseAllowanceAsync(tokenAddress, address, amount),
]);
}
private async _increaseBalanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
await this._zeroEx.token.transferAsync(tokenAddress, this._coinbase, address, amount);
}
private async _increaseAllowanceAsync(tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
const oldMakerAllowance = await this._zeroEx.token.getProxyAllowanceAsync(tokenAddress, address);
const newMakerAllowance = oldMakerAllowance.plus(amount);
await this._zeroEx.token.setProxyAllowanceAsync(tokenAddress, address, newMakerAllowance);
}
}

View File

@@ -6,41 +6,41 @@ import { SignedOrder, ZeroEx } from '../../src';
const SHOULD_ADD_PERSONAL_MESSAGE_PREFIX = false;
export const orderFactory = {
async createSignedOrderAsync(
zeroEx: ZeroEx,
maker: string,
taker: string,
makerFee: BigNumber,
takerFee: BigNumber,
makerTokenAmount: BigNumber,
makerTokenAddress: string,
takerTokenAmount: BigNumber,
takerTokenAddress: string,
exchangeContractAddress: string,
feeRecipient: string,
expirationUnixTimestampSecIfExists?: BigNumber,
): Promise<SignedOrder> {
const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite
const expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSecIfExists)
? defaultExpirationUnixTimestampSec
: expirationUnixTimestampSecIfExists;
const order = {
maker,
taker,
makerFee,
takerFee,
makerTokenAmount,
takerTokenAmount,
makerTokenAddress,
takerTokenAddress,
salt: ZeroEx.generatePseudoRandomSalt(),
exchangeContractAddress,
feeRecipient,
expirationUnixTimestampSec,
};
const orderHash = ZeroEx.getOrderHashHex(order);
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX);
const signedOrder: SignedOrder = _.assign(order, { ecSignature });
return signedOrder;
},
async createSignedOrderAsync(
zeroEx: ZeroEx,
maker: string,
taker: string,
makerFee: BigNumber,
takerFee: BigNumber,
makerTokenAmount: BigNumber,
makerTokenAddress: string,
takerTokenAmount: BigNumber,
takerTokenAddress: string,
exchangeContractAddress: string,
feeRecipient: string,
expirationUnixTimestampSecIfExists?: BigNumber,
): Promise<SignedOrder> {
const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite
const expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSecIfExists)
? defaultExpirationUnixTimestampSec
: expirationUnixTimestampSecIfExists;
const order = {
maker,
taker,
makerFee,
takerFee,
makerTokenAmount,
takerTokenAmount,
makerTokenAddress,
takerTokenAddress,
salt: ZeroEx.generatePseudoRandomSalt(),
exchangeContractAddress,
feeRecipient,
expirationUnixTimestampSec,
};
const orderHash = ZeroEx.getOrderHashHex(order);
const ecSignature = await zeroEx.signOrderHashAsync(orderHash, maker, SHOULD_ADD_PERSONAL_MESSAGE_PREFIX);
const signedOrder: SignedOrder = _.assign(order, { ecSignature });
return signedOrder;
},
};

View File

@@ -6,61 +6,61 @@ import { DoneCallback } from '../../src/types';
const expect = chai.expect;
export const reportNoErrorCallbackErrors = (done: DoneCallback, expectToBeCalledOnce = true) => {
return <T>(f?: (value: T) => void) => {
const wrapped = (value: T) => {
if (_.isUndefined(f)) {
done();
return;
}
try {
f(value);
if (expectToBeCalledOnce) {
done();
}
} catch (err) {
done(err);
}
};
return wrapped;
};
return <T>(f?: (value: T) => void) => {
const wrapped = (value: T) => {
if (_.isUndefined(f)) {
done();
return;
}
try {
f(value);
if (expectToBeCalledOnce) {
done();
}
} catch (err) {
done(err);
}
};
return wrapped;
};
};
export const reportNodeCallbackErrors = (done: DoneCallback, expectToBeCalledOnce = true) => {
return <T>(f?: (value: T) => void) => {
const wrapped = (error: Error | null, value: T | undefined) => {
if (!_.isNull(error)) {
done(error);
} else {
if (_.isUndefined(f)) {
done();
return;
}
try {
f(value as T);
if (expectToBeCalledOnce) {
done();
}
} catch (err) {
done(err);
}
}
};
return wrapped;
};
return <T>(f?: (value: T) => void) => {
const wrapped = (error: Error | null, value: T | undefined) => {
if (!_.isNull(error)) {
done(error);
} else {
if (_.isUndefined(f)) {
done();
return;
}
try {
f(value as T);
if (expectToBeCalledOnce) {
done();
}
} catch (err) {
done(err);
}
}
};
return wrapped;
};
};
export const assertNodeCallbackError = (done: DoneCallback, errMsg: string) => {
const wrapped = <T>(error: Error | null, value: T | undefined) => {
if (_.isNull(error)) {
done(new Error('Expected callback to receive an error'));
} else {
try {
expect(error.message).to.be.equal(errMsg);
done();
} catch (err) {
done(err);
}
}
};
return wrapped;
const wrapped = <T>(error: Error | null, value: T | undefined) => {
if (_.isNull(error)) {
done(new Error('Expected callback to receive an error'));
} else {
try {
expect(error.message).to.be.equal(errMsg);
done();
} catch (err) {
done(err);
}
}
};
return wrapped;
};

View File

@@ -6,22 +6,22 @@ import { JSONRPCPayload } from '../../../src/types';
* Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js
*/
export class EmptyWalletSubprovider {
// This method needs to be here to satisfy the interface but linter wants it to be static.
// tslint:disable-next-line:prefer-function-over-method
public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) {
switch (payload.method) {
case 'eth_accounts':
end(null, []);
return;
// This method needs to be here to satisfy the interface but linter wants it to be static.
// tslint:disable-next-line:prefer-function-over-method
public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) {
switch (payload.method) {
case 'eth_accounts':
end(null, []);
return;
default:
next();
return;
}
}
// Required to implement this method despite not needing it for this subprovider
// tslint:disable-next-line:prefer-function-over-method
public setEngine(engine: any) {
// noop
}
default:
next();
return;
}
}
// Required to implement this method despite not needing it for this subprovider
// tslint:disable-next-line:prefer-function-over-method
public setEngine(engine: any) {
// noop
}
}

View File

@@ -9,26 +9,26 @@ import { JSONRPCPayload } from '../../../src/types';
* Source: https://github.com/MetaMask/provider-engine/blob/master/subproviders/subprovider.js
*/
export class FakeGasEstimateSubprovider {
private _constantGasAmount: number;
constructor(constantGasAmount: number) {
this._constantGasAmount = constantGasAmount;
}
// This method needs to be here to satisfy the interface but linter wants it to be static.
// tslint:disable-next-line:prefer-function-over-method
public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) {
switch (payload.method) {
case 'eth_estimateGas':
end(null, this._constantGasAmount);
return;
private _constantGasAmount: number;
constructor(constantGasAmount: number) {
this._constantGasAmount = constantGasAmount;
}
// This method needs to be here to satisfy the interface but linter wants it to be static.
// tslint:disable-next-line:prefer-function-over-method
public handleRequest(payload: JSONRPCPayload, next: () => void, end: (err: Error | null, result: any) => void) {
switch (payload.method) {
case 'eth_estimateGas':
end(null, this._constantGasAmount);
return;
default:
next();
return;
}
}
// Required to implement this method despite not needing it for this subprovider
// tslint:disable-next-line:prefer-function-over-method
public setEngine(engine: any) {
// noop
}
default:
next();
return;
}
}
// Required to implement this method despite not needing it for this subprovider
// tslint:disable-next-line:prefer-function-over-method
public setEngine(engine: any) {
// noop
}
}

View File

@@ -6,28 +6,28 @@ const PROTOCOL_TOKEN_SYMBOL = 'ZRX';
const WETH_TOKEN_SYMBOL = 'WETH';
export class TokenUtils {
private _tokens: Token[];
constructor(tokens: Token[]) {
this._tokens = tokens;
}
public getProtocolTokenOrThrow(): Token {
const zrxToken = _.find(this._tokens, { symbol: PROTOCOL_TOKEN_SYMBOL });
if (_.isUndefined(zrxToken)) {
throw new Error(InternalZeroExError.ZrxNotInTokenRegistry);
}
return zrxToken;
}
public getWethTokenOrThrow(): Token {
const wethToken = _.find(this._tokens, { symbol: WETH_TOKEN_SYMBOL });
if (_.isUndefined(wethToken)) {
throw new Error(InternalZeroExError.WethNotInTokenRegistry);
}
return wethToken;
}
public getDummyTokens(): Token[] {
const dummyTokens = _.filter(this._tokens, token => {
return !_.includes([PROTOCOL_TOKEN_SYMBOL, WETH_TOKEN_SYMBOL], token.symbol);
});
return dummyTokens;
}
private _tokens: Token[];
constructor(tokens: Token[]) {
this._tokens = tokens;
}
public getProtocolTokenOrThrow(): Token {
const zrxToken = _.find(this._tokens, { symbol: PROTOCOL_TOKEN_SYMBOL });
if (_.isUndefined(zrxToken)) {
throw new Error(InternalZeroExError.ZrxNotInTokenRegistry);
}
return zrxToken;
}
public getWethTokenOrThrow(): Token {
const wethToken = _.find(this._tokens, { symbol: WETH_TOKEN_SYMBOL });
if (_.isUndefined(wethToken)) {
throw new Error(InternalZeroExError.WethNotInTokenRegistry);
}
return wethToken;
}
public getDummyTokens(): Token[] {
const dummyTokens = _.filter(this._tokens, token => {
return !_.includes([PROTOCOL_TOKEN_SYMBOL, WETH_TOKEN_SYMBOL], token.symbol);
});
return dummyTokens;
}
}

View File

@@ -19,24 +19,24 @@ import { constants } from './constants';
import * as Web3 from 'web3';
export const web3Factory = {
create(hasAddresses: boolean = true): Web3 {
const provider = this.getRpcProvider(hasAddresses);
const web3 = new Web3();
web3.setProvider(provider);
return web3;
},
getRpcProvider(hasAddresses: boolean = true): Web3.Provider {
const provider = new ProviderEngine();
if (!hasAddresses) {
provider.addProvider(new EmptyWalletSubprovider());
}
provider.addProvider(new FakeGasEstimateSubprovider(constants.GAS_ESTIMATE));
provider.addProvider(
new RpcSubprovider({
rpcUrl: constants.RPC_URL,
}),
);
provider.start();
return provider;
},
create(hasAddresses: boolean = true): Web3 {
const provider = this.getRpcProvider(hasAddresses);
const web3 = new Web3();
web3.setProvider(provider);
return web3;
},
getRpcProvider(hasAddresses: boolean = true): Web3.Provider {
const provider = new ProviderEngine();
if (!hasAddresses) {
provider.addProvider(new EmptyWalletSubprovider());
}
provider.addProvider(new FakeGasEstimateSubprovider(constants.GAS_ESTIMATE));
provider.addProvider(
new RpcSubprovider({
rpcUrl: constants.RPC_URL,
}),
);
provider.start();
return provider;
},
};

View File

@@ -1,16 +1,16 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "lib",
"noImplicitThis": false
},
"include": [
"./src/**/*",
"./test/**/*",
"../../node_modules/types-bn/index.d.ts",
"../../node_modules/types-ethereumjs-util/index.d.ts",
"../../node_modules/web3-typescript-typings/index.d.ts",
"../../node_modules/chai-typescript-typings/index.d.ts",
"../../node_modules/chai-as-promised-typescript-typings/index.d.ts"
]
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "lib",
"noImplicitThis": false
},
"include": [
"./src/**/*",
"./test/**/*",
"../../node_modules/types-bn/index.d.ts",
"../../node_modules/types-ethereumjs-util/index.d.ts",
"../../node_modules/web3-typescript-typings/index.d.ts",
"../../node_modules/chai-typescript-typings/index.d.ts",
"../../node_modules/chai-as-promised-typescript-typings/index.d.ts"
]
}

View File

@@ -1,3 +1,3 @@
{
"extends": ["@0xproject/tslint-config"]
"extends": ["@0xproject/tslint-config"]
}