diff options
author | Leonid Logvinov <logvinov.leon@gmail.com> | 2017-05-30 20:55:43 +0800 |
---|---|---|
committer | Leonid Logvinov <logvinov.leon@gmail.com> | 2017-05-30 20:55:43 +0800 |
commit | 3e65ac018c145d9fb49ece6cb113ae577f84323b (patch) | |
tree | 49f3b63f898b78c2b210b56b01e96f62887161b0 | |
parent | dfcf49464b1a93b6e5df39289e9d14b2e60e62d2 (diff) | |
parent | 6b321ca1c70b9dcf188b2a112015386cba0ad5f2 (diff) | |
download | dexon-0x-contracts-3e65ac018c145d9fb49ece6cb113ae577f84323b.tar.gz dexon-0x-contracts-3e65ac018c145d9fb49ece6cb113ae577f84323b.tar.zst dexon-0x-contracts-3e65ac018c145d9fb49ece6cb113ae577f84323b.zip |
Merge branch 'master' into fillOrderAsync
-rw-r--r-- | src/0x.js.ts | 13 | ||||
-rw-r--r-- | src/contract_wrappers/exchange_wrapper.ts | 14 | ||||
-rw-r--r-- | src/contract_wrappers/token_registry_wrapper.ts | 44 | ||||
-rw-r--r-- | src/schemas/token_schema.ts | 12 | ||||
-rw-r--r-- | src/types.ts | 20 | ||||
-rw-r--r-- | src/utils/schema_validator.ts | 2 | ||||
-rw-r--r-- | src/web3_wrapper.ts | 10 | ||||
-rw-r--r-- | test/0x.js_test.ts | 36 | ||||
-rw-r--r-- | test/token_registry_wrapper_test.ts | 43 | ||||
-rw-r--r-- | test/utils/web3_factory.ts | 10 |
10 files changed, 191 insertions, 13 deletions
diff --git a/src/0x.js.ts b/src/0x.js.ts index d708a8db6..69c0cc567 100644 --- a/src/0x.js.ts +++ b/src/0x.js.ts @@ -11,14 +11,16 @@ import {assert} from './utils/assert'; import findVersions = require('find-versions'); import compareVersions = require('compare-versions'); import {ExchangeWrapper} from './contract_wrappers/exchange_wrapper'; +import {TokenRegistryWrapper} from './contract_wrappers/token_registry_wrapper'; import {ecSignatureSchema} from './schemas/ec_signature_schema'; import {SolidityTypes, ECSignature, ZeroExError} from './types'; const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; export class ZeroEx { - public web3Wrapper: Web3Wrapper; public exchange: ExchangeWrapper; + public tokenRegistry: TokenRegistryWrapper; + private web3Wrapper: Web3Wrapper; /** * Computes the orderHash given the order parameters and returns it as a hex encoded string. */ @@ -132,6 +134,15 @@ export class ZeroEx { constructor(web3: Web3) { this.web3Wrapper = new Web3Wrapper(web3); this.exchange = new ExchangeWrapper(this.web3Wrapper); + this.tokenRegistry = new TokenRegistryWrapper(this.web3Wrapper); + } + /** + * Sets a new provider for the web3 instance used by 0x.js + */ + public setProvider(provider: Web3.Provider) { + this.web3Wrapper.setProvider(provider); + this.exchange.invalidateContractInstance(); + this.tokenRegistry.invalidateContractInstance(); } /** * Signs an orderHash and returns it's elliptic curve signature diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index 7a80c5800..66e53a7d4 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -26,9 +26,13 @@ export class ExchangeWrapper extends ContractWrapper { [ExchangeContractErrs.ERROR_FILL_TRUNCATION]: 'The rounding error was too large when filling this order', [ExchangeContractErrs.ERROR_FILL_BALANCE_ALLOWANCE]: 'Maker or taker has insufficient balance or allowance', }; + private exchangeContractIfExists?: ExchangeContract; constructor(web3Wrapper: Web3Wrapper) { super(web3Wrapper); } + public invalidateContractInstance(): void { + delete this.exchangeContractIfExists; + } public async isValidSignatureAsync(dataHex: string, ecSignature: ECSignature, signerAddressHex: string): Promise<boolean> { assert.isHexString('dataHex', dataHex); @@ -36,7 +40,7 @@ export class ExchangeWrapper extends ContractWrapper { assert.isETHAddressHex('signerAddressHex', signerAddressHex); const senderAddress = await this.web3Wrapper.getSenderAddressOrThrowAsync(); - const exchangeInstance = await this.getExchangeInstanceOrThrowAsync(); + const exchangeInstance = await this.getExchangeContractAsync(); const isValidSignature = await exchangeInstance.isValidSignature.call( signerAddressHex, @@ -102,4 +106,12 @@ export class ExchangeWrapper extends ContractWrapper { throw new Error(humanReadableErrMessage); } } + private async getExchangeContractAsync(): Promise<ExchangeContract> { + if (!_.isUndefined(this.exchangeContractIfExists)) { + return this.exchangeContractIfExists; + } + const contractInstance = await this.instantiateContractIfExistsAsync((ExchangeArtifacts as any)); + this.exchangeContractIfExists = contractInstance as ExchangeContract; + return this.exchangeContractIfExists; + } } diff --git a/src/contract_wrappers/token_registry_wrapper.ts b/src/contract_wrappers/token_registry_wrapper.ts new file mode 100644 index 000000000..86bea1c5d --- /dev/null +++ b/src/contract_wrappers/token_registry_wrapper.ts @@ -0,0 +1,44 @@ +import * as _ from 'lodash'; +import {Web3Wrapper} from '../web3_wrapper'; +import {Token, TokenRegistryContract, TokenMetadata} from '../types'; +import {assert} from '../utils/assert'; +import {ContractWrapper} from './contract_wrapper'; +import * as TokenRegistryArtifacts from '../artifacts/TokenRegistry.json'; + +export class TokenRegistryWrapper extends ContractWrapper { + private tokenRegistryContractIfExists?: TokenRegistryContract; + constructor(web3Wrapper: Web3Wrapper) { + super(web3Wrapper); + } + public invalidateContractInstance(): void { + delete this.tokenRegistryContractIfExists; + } + public async getTokensAsync(): Promise<Token[]> { + const tokenRegistryContract = await this.getTokenRegistryContractAsync(); + + const addresses = await tokenRegistryContract.getTokenAddresses.call(); + const tokenMetadataPromises: Array<Promise<TokenMetadata>> = _.map( + addresses, + (address: string) => (tokenRegistryContract.getTokenMetaData.call(address)), + ); + const tokensMetadata = await Promise.all(tokenMetadataPromises); + const tokens = _.map(tokensMetadata, metadata => { + return { + address: metadata[0], + name: metadata[1], + symbol: metadata[2], + url: metadata[3], + decimals: metadata[4].toNumber(), + }; + }); + return tokens; + } + private async getTokenRegistryContractAsync(): Promise<TokenRegistryContract> { + if (!_.isUndefined(this.tokenRegistryContractIfExists)) { + return this.tokenRegistryContractIfExists; + } + const contractInstance = await this.instantiateContractIfExistsAsync((TokenRegistryArtifacts as any)); + this.tokenRegistryContractIfExists = contractInstance as TokenRegistryContract; + return this.tokenRegistryContractIfExists; + } +} diff --git a/src/schemas/token_schema.ts b/src/schemas/token_schema.ts new file mode 100644 index 000000000..01702af68 --- /dev/null +++ b/src/schemas/token_schema.ts @@ -0,0 +1,12 @@ +export const tokenSchema = { + id: '/token', + properties: { + name: {type: 'string'}, + symbol: {type: 'string'}, + decimals: {type: 'number'}, + address: {type: 'string'}, + url: {type: 'string'}, + }, + required: ['name', 'symbol', 'decimals', 'address', 'url'], + type: 'object', +}; diff --git a/src/types.ts b/src/types.ts index 327293cc6..2ada20e3c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -45,6 +45,15 @@ export interface ExchangeContract { ) => ContractResponse; } +export interface TokenRegistryContract { + getTokenMetaData: { + call: (address: string) => Promise<TokenMetadata>; + }; + getTokenAddresses: { + call: () => Promise<string[]>; + }; +} + export const SolidityTypes = strEnum([ 'address', 'uint256', @@ -90,3 +99,14 @@ export interface Order { export interface SignedOrder extends Order { ecSignature: ECSignature; } + +// [address, name, symbol, projectUrl, decimals, ipfsHash, swarmHash] +export type TokenMetadata = [string, string, string, string, BigNumber.BigNumber, string, string]; + +export interface Token { + name: string; + address: string; + symbol: string; + decimals: number; + url: string; +} diff --git a/src/utils/schema_validator.ts b/src/utils/schema_validator.ts index 5b2c4aaa5..cf45d0343 100644 --- a/src/utils/schema_validator.ts +++ b/src/utils/schema_validator.ts @@ -1,11 +1,13 @@ import {Validator, ValidatorResult} from 'jsonschema'; import {ecSignatureSchema, ecSignatureParameter} from '../schemas/ec_signature_schema'; import {addressSchema, numberSchema, orderSchema, signedOrderSchema} from '../schemas/signed_order_schema'; +import {tokenSchema} from '../schemas/token_schema'; export class SchemaValidator { private validator: Validator; constructor() { this.validator = new Validator(); + this.validator.addSchema(tokenSchema, tokenSchema.id); this.validator.addSchema(orderSchema, orderSchema.id); this.validator.addSchema(numberSchema, numberSchema.id); this.validator.addSchema(addressSchema, addressSchema.id); diff --git a/src/web3_wrapper.ts b/src/web3_wrapper.ts index 1ed1c0b29..361f28476 100644 --- a/src/web3_wrapper.ts +++ b/src/web3_wrapper.ts @@ -11,6 +11,9 @@ export class Web3Wrapper { this.web3 = new Web3(); this.web3.setProvider(web3.currentProvider); } + public setProvider(provider: Web3.Provider) { + this.web3.setProvider(provider); + } public isAddress(address: string): boolean { return this.web3.isAddress(address); } @@ -56,10 +59,9 @@ export class Web3Wrapper { } public async doesContractExistAtAddressAsync(address: string): Promise<boolean> { const code = await promisify(this.web3.eth.getCode)(address); - // Regex matches 0x0, 0x00, 0x in order to accomodate poorly implemented clients - const zeroHexAddressRegex = /^0x0*$/i; - const didFindCode = _.isNull(code.match(zeroHexAddressRegex)); - return didFindCode; + // Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients + const codeIsEmpty = /^0x0{0,40}$/i.test(code); + return !codeIsEmpty; } public async signTransactionAsync(address: string, message: string): Promise<string> { const signData = await promisify(this.web3.eth.sign)(address, message); diff --git a/test/0x.js_test.ts b/test/0x.js_test.ts index bb312a00f..5d23d7094 100644 --- a/test/0x.js_test.ts +++ b/test/0x.js_test.ts @@ -13,6 +13,34 @@ chai.use(ChaiBigNumber()); const expect = chai.expect; describe('ZeroEx library', () => { + describe('#setProvider', () => { + it('overrides the provider in the nested web3 instance and invalidates contractInstances', async () => { + const web3 = web3Factory.create(); + const zeroEx = new ZeroEx(web3); + // 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); + + // 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 instances return the updated provider + const nestedWeb3WrapperProvider = (zeroEx as any).web3Wrapper.getCurrentProvider(); + expect((nestedWeb3WrapperProvider as any).zeroExTestId).to.be.a('number'); + const exchangeWeb3WrapperProvider = zeroEx.exchange.web3Wrapper.getCurrentProvider(); + expect((exchangeWeb3WrapperProvider as any).zeroExTestId).to.be.a('number'); + const tokenRegistryWeb3WrapperProvider = zeroEx.tokenRegistry.web3Wrapper.getCurrentProvider(); + expect((tokenRegistryWeb3WrapperProvider as any).zeroExTestId).to.be.a('number'); + }); + }); describe('#getOrderHash', () => { const expectedOrderHash = '0x103a5e97dab5dbeb8f385636f86a7d1e458a7ccbe1bd194727f0b2f85ab116c7'; it('defaults takerAddress to NULL address', () => { @@ -194,9 +222,9 @@ describe('ZeroEx library', () => { const web3 = web3Factory.create(); const zeroEx = new ZeroEx(web3); stubs = [ - Sinon.stub(zeroEx.web3Wrapper, 'getNodeVersionAsync') + Sinon.stub((zeroEx as any).web3Wrapper, 'getNodeVersionAsync') .returns(Promise.resolve(newParityNodeVersion)), - Sinon.stub(zeroEx.web3Wrapper, 'signTransactionAsync') + Sinon.stub((zeroEx as any).web3Wrapper, 'signTransactionAsync') .returns(Promise.resolve(signature)), Sinon.stub(ZeroEx, 'isValidSignature').returns(true), ]; @@ -218,9 +246,9 @@ describe('ZeroEx library', () => { const web3 = web3Factory.create(); const zeroEx = new ZeroEx(web3); stubs = [ - Sinon.stub(zeroEx.web3Wrapper, 'getNodeVersionAsync') + Sinon.stub((zeroEx as any).web3Wrapper, 'getNodeVersionAsync') .returns(Promise.resolve(newParityNodeVersion)), - Sinon.stub(zeroEx.web3Wrapper, 'signTransactionAsync') + Sinon.stub((zeroEx as any).web3Wrapper, 'signTransactionAsync') .returns(Promise.resolve(signature)), Sinon.stub(ZeroEx, 'isValidSignature').returns(true), ]; diff --git a/test/token_registry_wrapper_test.ts b/test/token_registry_wrapper_test.ts new file mode 100644 index 000000000..c91555d8b --- /dev/null +++ b/test/token_registry_wrapper_test.ts @@ -0,0 +1,43 @@ +import * as _ from 'lodash'; +import 'mocha'; +import * as chai from 'chai'; +import chaiAsPromised = require('chai-as-promised'); +import * as Web3 from 'web3'; +import {web3Factory} from './utils/web3_factory'; +import {ZeroEx} from '../src/0x.js'; +import {BlockchainLifecycle} from './utils/blockchain_lifecycle'; +import {Token} from '../src/types'; +import {SchemaValidator} from '../src/utils/schema_validator'; +import {tokenSchema} from '../src/schemas/token_schema'; + +const expect = chai.expect; +chai.use(chaiAsPromised); +const blockchainLifecycle = new BlockchainLifecycle(); + +const TOKEN_REGISTRY_SIZE_AFTER_MIGRATION = 7; + +describe('TokenRegistryWrapper', () => { + let zeroEx: ZeroEx; + before(async () => { + const web3 = web3Factory.create(); + zeroEx = new ZeroEx(web3); + }); + 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 () => { + const tokens = await zeroEx.tokenRegistry.getTokensAsync(); + expect(tokens).to.have.lengthOf(TOKEN_REGISTRY_SIZE_AFTER_MIGRATION); + + const schemaValidator = new SchemaValidator(); + _.each(tokens, token => { + const validationResult = schemaValidator.validate(token, tokenSchema); + expect(validationResult.errors).to.have.lengthOf(0); + }); + }); + }); +}); diff --git a/test/utils/web3_factory.ts b/test/utils/web3_factory.ts index 493fbc2df..ffdc0e4cf 100644 --- a/test/utils/web3_factory.ts +++ b/test/utils/web3_factory.ts @@ -10,14 +10,18 @@ import {constants} from './constants'; export const web3Factory = { create(): Web3 { + const provider = this.getRpcProvider(); + const web3 = new Web3(); + web3.setProvider(provider); + return web3; + }, + getRpcProvider(): Web3.Provider { const provider = new ProviderEngine(); const rpcUrl = `http://${constants.RPC_HOST}:${constants.RPC_PORT}`; provider.addProvider(new RpcSubprovider({ rpcUrl, })); provider.start(); - const web3 = new Web3(); - web3.setProvider(provider); - return web3; + return provider; }, }; |