diff options
author | Fabio Berger <me@fabioberger.com> | 2017-05-29 15:09:54 +0800 |
---|---|---|
committer | Fabio Berger <me@fabioberger.com> | 2017-05-29 15:09:54 +0800 |
commit | 62cc3b919c73b7726793808e3b9631dba41cef28 (patch) | |
tree | 22c5f1728c56b09fa0e15e5e645c1f891224a6ee /src/ts | |
parent | 281b9eaa5d207d32ae38fbab25cbca83fe81efa1 (diff) | |
parent | b897bdab79fe566ffc8c19c6ec9f1bb7260fa95e (diff) | |
download | dexon-sol-tools-62cc3b919c73b7726793808e3b9631dba41cef28.tar dexon-sol-tools-62cc3b919c73b7726793808e3b9631dba41cef28.tar.gz dexon-sol-tools-62cc3b919c73b7726793808e3b9631dba41cef28.tar.bz2 dexon-sol-tools-62cc3b919c73b7726793808e3b9631dba41cef28.tar.lz dexon-sol-tools-62cc3b919c73b7726793808e3b9631dba41cef28.tar.xz dexon-sol-tools-62cc3b919c73b7726793808e3b9631dba41cef28.tar.zst dexon-sol-tools-62cc3b919c73b7726793808e3b9631dba41cef28.zip |
Merge branch 'master' of github.com:0xProject/0x.js
# Conflicts:
# src/ts/0x.js.ts
# src/ts/utils/utils.ts
Diffstat (limited to 'src/ts')
-rw-r--r-- | src/ts/0x.js.ts | 26 | ||||
-rw-r--r-- | src/ts/contract_wrappers/contract_wrapper.ts | 48 | ||||
-rw-r--r-- | src/ts/contract_wrappers/exchange_wrapper.ts | 37 | ||||
-rw-r--r-- | src/ts/globals.d.ts | 33 | ||||
-rw-r--r-- | src/ts/types.ts | 21 | ||||
-rw-r--r-- | src/ts/utils/assert.ts | 21 | ||||
-rw-r--r-- | src/ts/utils/constants.ts | 2 | ||||
-rw-r--r-- | src/ts/utils/utils.ts | 9 | ||||
-rw-r--r-- | src/ts/web3_wrapper.ts | 69 |
9 files changed, 241 insertions, 25 deletions
diff --git a/src/ts/0x.js.ts b/src/ts/0x.js.ts index 67e7c2fdd..1ebafc604 100644 --- a/src/ts/0x.js.ts +++ b/src/ts/0x.js.ts @@ -1,25 +1,22 @@ +import * as _ from 'lodash'; import * as BigNumber from 'bignumber.js'; import * as ethUtil from 'ethereumjs-util'; +import contract = require('truffle-contract'); +import * as Web3 from 'web3'; import * as ethABI from 'ethereumjs-abi'; -import * as _ from 'lodash'; +import {Web3Wrapper} from './web3_wrapper'; import {constants} from './utils/constants'; import {utils} from './utils/utils'; import {assert} from './utils/assert'; +import {ExchangeWrapper} from './contract_wrappers/exchange_wrapper'; import {ECSignatureSchema} from './schemas/ec_signature_schema'; -import {SolidityTypes} from './types'; - -/** - * Elliptic Curve signature - */ -export interface ECSignature { - v: number; - r: string; - s: string; -} +import {SolidityTypes, ECSignature} from './types'; const MAX_DIGITS_IN_UNSIGNED_256_INT = 78; export class ZeroEx { + public web3Wrapper: Web3Wrapper; + public exchange: ExchangeWrapper; /** * Computes the orderHash given the order parameters and returns it as a hex encoded string. */ @@ -74,7 +71,8 @@ export class ZeroEx { const dataBuff = ethUtil.toBuffer(dataHex); const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); try { - const pubKey = ethUtil.ecrecover(msgHashBuff, + const pubKey = ethUtil.ecrecover( + msgHashBuff, signature.v, ethUtil.toBuffer(signature.r), ethUtil.toBuffer(signature.s)); @@ -129,4 +127,8 @@ export class ZeroEx { const baseUnitAmount = amount.times(unit); return baseUnitAmount; } + constructor(web3: Web3) { + this.web3Wrapper = new Web3Wrapper(web3); + this.exchange = new ExchangeWrapper(this.web3Wrapper); + } } diff --git a/src/ts/contract_wrappers/contract_wrapper.ts b/src/ts/contract_wrappers/contract_wrapper.ts new file mode 100644 index 000000000..9f4cd8039 --- /dev/null +++ b/src/ts/contract_wrappers/contract_wrapper.ts @@ -0,0 +1,48 @@ +import * as _ from 'lodash'; +import contract = require('truffle-contract'); +import {Web3Wrapper} from '../web3_wrapper'; +import {ZeroExError} from '../types'; +import {utils} from '../utils/utils'; + +export class ContractWrapper { + public web3Wrapper: Web3Wrapper; + constructor(web3Wrapper: Web3Wrapper) { + this.web3Wrapper = web3Wrapper; + } + protected async instantiateContractIfExistsAsync(artifact: Artifact, address?: string): Promise<ContractInstance> { + const c = await contract(artifact); + const providerObj = this.web3Wrapper.getCurrentProvider(); + c.setProvider(providerObj); + + const networkIdIfExists = await this.web3Wrapper.getNetworkIdIfExistsAsync(); + const artifactNetworkConfigs = _.isUndefined(networkIdIfExists) ? + undefined : + artifact.networks[networkIdIfExists]; + let contractAddress; + if (!_.isUndefined(address)) { + contractAddress = address; + } else if (!_.isUndefined(artifactNetworkConfigs)) { + contractAddress = artifactNetworkConfigs.address; + } + + if (!_.isUndefined(contractAddress)) { + const doesContractExist = await this.web3Wrapper.doesContractExistAtAddressAsync(contractAddress); + if (!doesContractExist) { + throw new Error(ZeroExError.CONTRACT_DOES_NOT_EXIST); + } + } + + try { + const contractInstance = _.isUndefined(address) ? await c.deployed() : await c.at(address); + return contractInstance; + } catch (err) { + const errMsg = `${err}`; + if (_.includes(errMsg, 'not been deployed to detected network')) { + throw new Error(ZeroExError.CONTRACT_DOES_NOT_EXIST); + } else { + utils.consoleLog(`Notice: Error encountered: ${err} ${err.stack}`); + throw new Error(ZeroExError.UNHANDLED_ERROR); + } + } + } +} diff --git a/src/ts/contract_wrappers/exchange_wrapper.ts b/src/ts/contract_wrappers/exchange_wrapper.ts new file mode 100644 index 000000000..38043dd55 --- /dev/null +++ b/src/ts/contract_wrappers/exchange_wrapper.ts @@ -0,0 +1,37 @@ +import * as _ from 'lodash'; +import {Web3Wrapper} from '../web3_wrapper'; +import {ECSignature, ZeroExError, ExchangeContract} from '../types'; +import {assert} from '../utils/assert'; +import {ContractWrapper} from './contract_wrapper'; +import * as ExchangeArtifacts from '../../artifacts/Exchange.json'; +import {ECSignatureSchema} from '../schemas/ec_signature_schema'; + +export class ExchangeWrapper extends ContractWrapper { + constructor(web3Wrapper: Web3Wrapper) { + super(web3Wrapper); + } + public async isValidSignatureAsync(dataHex: string, ecSignature: ECSignature, + signerAddressHex: string): Promise<boolean> { + assert.isHexString('dataHex', dataHex); + assert.doesConformToSchema('ecSignature', ecSignature, ECSignatureSchema); + assert.isETHAddressHex('signerAddressHex', signerAddressHex); + + const senderAddressIfExists = await this.web3Wrapper.getSenderAddressIfExistsAsync(); + assert.assert(!_.isUndefined(senderAddressIfExists), ZeroExError.USER_HAS_NO_ASSOCIATED_ADDRESSES); + + const contractInstance = await this.instantiateContractIfExistsAsync((ExchangeArtifacts as any)); + const exchangeInstance = contractInstance as ExchangeContract; + + const isValidSignature = await exchangeInstance.isValidSignature.call( + signerAddressHex, + dataHex, + ecSignature.v, + ecSignature.r, + ecSignature.s, + { + from: senderAddressIfExists, + }, + ); + return isValidSignature; + } +} diff --git a/src/ts/globals.d.ts b/src/ts/globals.d.ts index 99baf593f..dee957f2f 100644 --- a/src/ts/globals.d.ts +++ b/src/ts/globals.d.ts @@ -1,5 +1,8 @@ declare module 'chai-bignumber'; declare module 'bn.js'; +declare module 'request-promise-native'; +declare module 'web3-provider-engine'; +declare module 'web3-provider-engine/subproviders/rpc'; declare interface Schema { id: string; @@ -12,10 +15,18 @@ declare interface Schema { declare namespace Chai { interface Assertion { bignumber: Assertion; + eventually: Assertion; } } /* tslint:enable */ +declare module '*.json' { + const json: any; + /* tslint:disable */ + export default json; + /* tslint:enable */ +} + declare module 'ethereumjs-util' { const toBuffer: (dataHex: string) => Buffer; const hashPersonalMessage: (msg: Buffer) => Buffer; @@ -23,6 +34,28 @@ declare module 'ethereumjs-util' { const ecrecover: (msgHashBuff: Buffer, v: number, r: Buffer, s: Buffer) => string; const pubToAddress: (pubKey: string) => Buffer; const isValidAddress: (address: string) => boolean; + const bufferToInt: (buffer: Buffer) => number; +} + +// truffle-contract declarations +declare interface ContractInstance {} +declare interface ContractFactory { + setProvider: (providerObj: any) => void; + deployed: () => ContractInstance; + at: (address: string) => ContractInstance; +} +declare interface Artifact { + networks: {[networkId: number]: any}; +} +declare function contract(artifacts: Artifact): ContractFactory; +declare module 'truffle-contract' { + export = contract; +} + +// es6-promisify declarations +declare function promisify(original: any, settings?: any): ((...arg: any[]) => Promise<any>); +declare module 'es6-promisify' { + export = promisify; } declare module 'ethereumjs-abi' { diff --git a/src/ts/types.ts b/src/ts/types.ts index 04902cca6..4da03a4d3 100644 --- a/src/ts/types.ts +++ b/src/ts/types.ts @@ -1,5 +1,4 @@ import * as _ from 'lodash'; -import * as BigNumber from 'bignumber.js'; // Utility function to create a K:V from a list of strings // Adapted from: https://basarat.gitbooks.io/typescript/content/docs/types/literal-types.html @@ -10,6 +9,26 @@ function strEnum(values: string[]): {[key: string]: string} { }, Object.create(null)); } +export const ZeroExError = strEnum([ + 'CONTRACT_DOES_NOT_EXIST', + 'UNHANDLED_ERROR', + 'USER_HAS_NO_ASSOCIATED_ADDRESSES', +]); +export type ZeroExError = keyof typeof ZeroExError; + +/** + * Elliptic Curve signature + */ +export interface ECSignature { + v: number; + r: string; + s: string; +} + +export interface ExchangeContract { + isValidSignature: any; +} + export const SolidityTypes = strEnum([ 'address', 'uint256', diff --git a/src/ts/utils/assert.ts b/src/ts/utils/assert.ts index 2f52c6a3b..1baf572d1 100644 --- a/src/ts/utils/assert.ts +++ b/src/ts/utils/assert.ts @@ -1,30 +1,33 @@ import * as _ from 'lodash'; import * as BigNumber from 'bignumber.js'; -import Web3 = require('web3'); +import * as Web3 from 'web3'; import {SchemaValidator} from './schema_validator'; const HEX_REGEX = /^0x[0-9A-F]*$/i; export const assert = { - isBigNumber(variableName: string, value: BigNumber.BigNumber) { + isBigNumber(variableName: string, value: BigNumber.BigNumber): void { const isBigNumber = _.isObject(value) && value.isBigNumber; this.assert(isBigNumber, this.typeAssertionMessage(variableName, 'BigNumber', value)); }, - isString(variableName: string, value: string) { + isUndefined(value: any, variableName?: string): void { + this.assert(_.isUndefined(value), this.typeAssertionMessage(variableName, 'undefined', value)); + }, + isString(variableName: string, value: string): void { this.assert(_.isString(value), this.typeAssertionMessage(variableName, 'string', value)); }, - isHexString(variableName: string, value: string) { + isHexString(variableName: string, value: string): void { this.assert(_.isString(value) && HEX_REGEX.test(value), this.typeAssertionMessage(variableName, 'HexString', value)); }, - isETHAddressHex(variableName: string, value: string) { + isETHAddressHex(variableName: string, value: string): void { const web3 = new Web3(); this.assert(web3.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value)); }, - isNumber(variableName: string, value: number) { + isNumber(variableName: string, value: number): void { this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value)); }, - doesConformToSchema(variableName: string, value: object, schema: Schema) { + doesConformToSchema(variableName: string, value: object, schema: Schema): void { const schemaValidator = new SchemaValidator(); const validationResult = schemaValidator.validate(value, schema); const hasValidationErrors = validationResult.errors.length > 0; @@ -33,12 +36,12 @@ Encountered: ${JSON.stringify(value, null, '\t')} Validation errors: ${validationResult.errors.join(', ')}`; this.assert(!hasValidationErrors, msg); }, - assert(condition: boolean, message: string) { + assert(condition: boolean, message: string): void { if (!condition) { throw new Error(message); } }, - typeAssertionMessage(variableName: string, type: string, value: any) { + typeAssertionMessage(variableName: string, type: string, value: any): string { return `Expected ${variableName} to be of type ${type}, encountered: ${value}`; }, }; diff --git a/src/ts/utils/constants.ts b/src/ts/utils/constants.ts index 60af7b674..ec2fe744a 100644 --- a/src/ts/utils/constants.ts +++ b/src/ts/utils/constants.ts @@ -1,3 +1,3 @@ export const constants = { NULL_ADDRESS: '0x0000000000000000000000000000000000000000', -} +}; diff --git a/src/ts/utils/utils.ts b/src/ts/utils/utils.ts index bde1e7aba..b514b702d 100644 --- a/src/ts/utils/utils.ts +++ b/src/ts/utils/utils.ts @@ -3,11 +3,16 @@ import * as BN from 'bn.js'; export const utils = { /** * Converts BigNumber instance to BN - * The only we convert to BN is to remain compatible with `ethABI. soliditySHA3 ` that - * expects values of Solidity type `uint` to be of type `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.BigNumber) { return new BN(value.toString(), 10); }, + consoleLog(message: string): void { + /* tslint:disable */ + console.log(message); + /* tslint:enable */ + }, }; diff --git a/src/ts/web3_wrapper.ts b/src/ts/web3_wrapper.ts new file mode 100644 index 000000000..3b460e4da --- /dev/null +++ b/src/ts/web3_wrapper.ts @@ -0,0 +1,69 @@ +import * as _ from 'lodash'; +import * as Web3 from 'web3'; +import * as BigNumber from 'bignumber.js'; +import promisify = require('es6-promisify'); + +export class Web3Wrapper { + private web3: Web3; + constructor(web3: Web3) { + this.web3 = new Web3(); + this.web3.setProvider(web3.currentProvider); + } + public isAddress(address: string): boolean { + return this.web3.isAddress(address); + } + public async getSenderAddressIfExistsAsync(): Promise<string|undefined> { + const defaultAccount = this.web3.eth.defaultAccount; + if (!_.isUndefined(defaultAccount)) { + return defaultAccount; + } + const firstAccount = await this.getFirstAddressIfExistsAsync(); + return firstAccount; + } + public async getFirstAddressIfExistsAsync(): Promise<string|undefined> { + const addresses = await promisify(this.web3.eth.getAccounts)(); + if (_.isEmpty(addresses)) { + return undefined; + } + return (addresses as string[])[0]; + } + public async getNodeVersionAsync(): Promise<string> { + const nodeVersion = await promisify(this.web3.version.getNode)(); + return nodeVersion; + } + public getCurrentProvider(): Web3.Provider { + return this.web3.currentProvider; + } + public async getNetworkIdIfExistsAsync(): Promise<number|undefined> { + try { + const networkId = await this.getNetworkAsync(); + return Number(networkId); + } catch (err) { + return undefined; + } + } + public async getBalanceInEthAsync(owner: string): Promise<BigNumber.BigNumber> { + const balanceInWei = await promisify(this.web3.eth.getBalance)(owner); + const balanceEth = this.web3.fromWei(balanceInWei, 'ether'); + return balanceEth; + } + 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\{0,40\}$/i; + const didFindCode = _.isNull(code.match(zeroHexAddressRegex)); + return didFindCode; + } + public async signTransactionAsync(address: string, message: string): Promise<string> { + const signData = await promisify(this.web3.eth.sign)(address, message); + return signData; + } + public async getBlockTimestampAsync(blockHash: string): Promise<number> { + const {timestamp} = await promisify(this.web3.eth.getBlock)(blockHash); + return timestamp; + } + private async getNetworkAsync(): Promise<number> { + const networkId = await promisify(this.web3.version.getNetwork)(); + return networkId; + } +} |