From 0848fe96cf09679926d307c86414cfb8b6f16d78 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 29 May 2017 11:39:12 +0200 Subject: Move files up and remove ts folder --- src/0x.js.ts | 134 +++++++++++++++++++++++++++ src/contract_wrappers/contract_wrapper.ts | 48 ++++++++++ src/contract_wrappers/exchange_wrapper.ts | 37 ++++++++ src/globals.d.ts | 63 +++++++++++++ src/schemas/ec_signature_schema.ts | 20 ++++ src/ts/0x.js.ts | 134 --------------------------- src/ts/contract_wrappers/contract_wrapper.ts | 48 ---------- src/ts/contract_wrappers/exchange_wrapper.ts | 37 -------- src/ts/globals.d.ts | 63 ------------- src/ts/schemas/ec_signature_schema.ts | 20 ---- src/ts/types.ts | 36 ------- src/ts/utils/assert.ts | 47 ---------- src/ts/utils/constants.ts | 3 - src/ts/utils/schema_validator.ts | 14 --- src/ts/utils/utils.ts | 18 ---- src/ts/web3_wrapper.ts | 69 -------------- src/types.ts | 36 +++++++ src/utils/assert.ts | 47 ++++++++++ src/utils/constants.ts | 3 + src/utils/schema_validator.ts | 14 +++ src/utils/utils.ts | 18 ++++ src/web3_wrapper.ts | 69 ++++++++++++++ 22 files changed, 489 insertions(+), 489 deletions(-) create mode 100644 src/0x.js.ts create mode 100644 src/contract_wrappers/contract_wrapper.ts create mode 100644 src/contract_wrappers/exchange_wrapper.ts create mode 100644 src/globals.d.ts create mode 100644 src/schemas/ec_signature_schema.ts delete mode 100644 src/ts/0x.js.ts delete mode 100644 src/ts/contract_wrappers/contract_wrapper.ts delete mode 100644 src/ts/contract_wrappers/exchange_wrapper.ts delete mode 100644 src/ts/globals.d.ts delete mode 100644 src/ts/schemas/ec_signature_schema.ts delete mode 100644 src/ts/types.ts delete mode 100644 src/ts/utils/assert.ts delete mode 100644 src/ts/utils/constants.ts delete mode 100644 src/ts/utils/schema_validator.ts delete mode 100644 src/ts/utils/utils.ts delete mode 100644 src/ts/web3_wrapper.ts create mode 100644 src/types.ts create mode 100644 src/utils/assert.ts create mode 100644 src/utils/constants.ts create mode 100644 src/utils/schema_validator.ts create mode 100644 src/utils/utils.ts create mode 100644 src/web3_wrapper.ts diff --git a/src/0x.js.ts b/src/0x.js.ts new file mode 100644 index 000000000..1ebafc604 --- /dev/null +++ b/src/0x.js.ts @@ -0,0 +1,134 @@ +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 {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, 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. + */ + public static getOrderHashHex(exchangeContractAddr: string, makerAddr: string, takerAddr: string, + tokenMAddress: string, tokenTAddress: string, feeRecipient: string, + valueM: BigNumber.BigNumber, valueT: BigNumber.BigNumber, + makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber, + expiration: BigNumber.BigNumber, salt: BigNumber.BigNumber): string { + takerAddr = _.isEmpty(takerAddr) ? constants.NULL_ADDRESS : takerAddr ; + assert.isETHAddressHex('exchangeContractAddr', exchangeContractAddr); + assert.isETHAddressHex('makerAddr', makerAddr); + assert.isETHAddressHex('takerAddr', takerAddr); + assert.isETHAddressHex('tokenMAddress', tokenMAddress); + assert.isETHAddressHex('tokenTAddress', tokenTAddress); + assert.isETHAddressHex('feeRecipient', feeRecipient); + assert.isBigNumber('valueM', valueM); + assert.isBigNumber('valueT', valueT); + assert.isBigNumber('makerFee', makerFee); + assert.isBigNumber('takerFee', takerFee); + assert.isBigNumber('expiration', expiration); + assert.isBigNumber('salt', salt); + + const orderParts = [ + {value: exchangeContractAddr, type: SolidityTypes.address}, + {value: makerAddr, type: SolidityTypes.address}, + {value: takerAddr, type: SolidityTypes.address}, + {value: tokenMAddress, type: SolidityTypes.address}, + {value: tokenTAddress, type: SolidityTypes.address}, + {value: feeRecipient, type: SolidityTypes.address}, + {value: utils.bigNumberToBN(valueM), type: SolidityTypes.uint256}, + {value: utils.bigNumberToBN(valueT), type: SolidityTypes.uint256}, + {value: utils.bigNumberToBN(makerFee), type: SolidityTypes.uint256}, + {value: utils.bigNumberToBN(takerFee), type: SolidityTypes.uint256}, + {value: utils.bigNumberToBN(expiration), type: SolidityTypes.uint256}, + {value: utils.bigNumberToBN(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; + } + /** + * Verifies that the elliptic curve signature `signature` was generated + * by signing `data` with the private key corresponding to the `signerAddressHex` address. + */ + public static isValidSignature(dataHex: string, signature: ECSignature, signerAddressHex: string): boolean { + assert.isHexString('dataHex', dataHex); + assert.doesConformToSchema('signature', signature, ECSignatureSchema); + assert.isETHAddressHex('signerAddressHex', signerAddressHex); + + const dataBuff = ethUtil.toBuffer(dataHex); + 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 === signerAddressHex; + } catch (err) { + return false; + } + } + /** + * Generates pseudo-random 256 bit salt. + * The salt is used to ensure that the 0x order generated has a unique orderHash that does + * not collide with any other outstanding orders. + */ + public static generatePseudoRandomSalt(): BigNumber.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(MAX_DIGITS_IN_UNSIGNED_256_INT); + const factor = new BigNumber(10).pow(MAX_DIGITS_IN_UNSIGNED_256_INT - 1); + const salt = randomNumber.times(factor).round(); + return salt; + } + /** Checks if order hash is valid */ + public static isValidOrderHash(orderHash: string): boolean { + assert.isString('orderHash', orderHash); + const isValid = /^0x[0-9A-F]{64}$/i.test(orderHash); + return isValid; + } + /* + * 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. + */ + public static toUnitAmount(amount: BigNumber.BigNumber, decimals: number): BigNumber.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 + */ + public static toBaseUnitAmount(amount: BigNumber.BigNumber, decimals: number): BigNumber.BigNumber { + assert.isBigNumber('amount', amount); + assert.isNumber('decimals', decimals); + + const unit = new BigNumber(10).pow(decimals); + 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/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts new file mode 100644 index 000000000..9f4cd8039 --- /dev/null +++ b/src/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 { + 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/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts new file mode 100644 index 000000000..38043dd55 --- /dev/null +++ b/src/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 { + 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/globals.d.ts b/src/globals.d.ts new file mode 100644 index 000000000..dee957f2f --- /dev/null +++ b/src/globals.d.ts @@ -0,0 +1,63 @@ +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; +} + +// 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 */ +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; + const bufferToHex: (buff: Buffer) => string; + 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); +declare module 'es6-promisify' { + export = promisify; +} + +declare module 'ethereumjs-abi' { + const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; +} diff --git a/src/schemas/ec_signature_schema.ts b/src/schemas/ec_signature_schema.ts new file mode 100644 index 000000000..94e58e53c --- /dev/null +++ b/src/schemas/ec_signature_schema.ts @@ -0,0 +1,20 @@ +export const ECSignatureParameter = { + id: '/ECSignatureParameter', + type: 'string', + pattern: '^0[xX][0-9A-Fa-f]{64}$', +}; + +export const ECSignatureSchema = { + id: '/ECSignature', + properties: { + v: { + type: 'number', + minimum: 27, + maximum: 28, + }, + r: {$ref: '/ECSignatureParameter'}, + s: {$ref: '/ECSignatureParameter'}, + }, + required: ['v', 'r', 's'], + type: 'object', +}; diff --git a/src/ts/0x.js.ts b/src/ts/0x.js.ts deleted file mode 100644 index 1ebafc604..000000000 --- a/src/ts/0x.js.ts +++ /dev/null @@ -1,134 +0,0 @@ -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 {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, 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. - */ - public static getOrderHashHex(exchangeContractAddr: string, makerAddr: string, takerAddr: string, - tokenMAddress: string, tokenTAddress: string, feeRecipient: string, - valueM: BigNumber.BigNumber, valueT: BigNumber.BigNumber, - makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber, - expiration: BigNumber.BigNumber, salt: BigNumber.BigNumber): string { - takerAddr = _.isEmpty(takerAddr) ? constants.NULL_ADDRESS : takerAddr ; - assert.isETHAddressHex('exchangeContractAddr', exchangeContractAddr); - assert.isETHAddressHex('makerAddr', makerAddr); - assert.isETHAddressHex('takerAddr', takerAddr); - assert.isETHAddressHex('tokenMAddress', tokenMAddress); - assert.isETHAddressHex('tokenTAddress', tokenTAddress); - assert.isETHAddressHex('feeRecipient', feeRecipient); - assert.isBigNumber('valueM', valueM); - assert.isBigNumber('valueT', valueT); - assert.isBigNumber('makerFee', makerFee); - assert.isBigNumber('takerFee', takerFee); - assert.isBigNumber('expiration', expiration); - assert.isBigNumber('salt', salt); - - const orderParts = [ - {value: exchangeContractAddr, type: SolidityTypes.address}, - {value: makerAddr, type: SolidityTypes.address}, - {value: takerAddr, type: SolidityTypes.address}, - {value: tokenMAddress, type: SolidityTypes.address}, - {value: tokenTAddress, type: SolidityTypes.address}, - {value: feeRecipient, type: SolidityTypes.address}, - {value: utils.bigNumberToBN(valueM), type: SolidityTypes.uint256}, - {value: utils.bigNumberToBN(valueT), type: SolidityTypes.uint256}, - {value: utils.bigNumberToBN(makerFee), type: SolidityTypes.uint256}, - {value: utils.bigNumberToBN(takerFee), type: SolidityTypes.uint256}, - {value: utils.bigNumberToBN(expiration), type: SolidityTypes.uint256}, - {value: utils.bigNumberToBN(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; - } - /** - * Verifies that the elliptic curve signature `signature` was generated - * by signing `data` with the private key corresponding to the `signerAddressHex` address. - */ - public static isValidSignature(dataHex: string, signature: ECSignature, signerAddressHex: string): boolean { - assert.isHexString('dataHex', dataHex); - assert.doesConformToSchema('signature', signature, ECSignatureSchema); - assert.isETHAddressHex('signerAddressHex', signerAddressHex); - - const dataBuff = ethUtil.toBuffer(dataHex); - 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 === signerAddressHex; - } catch (err) { - return false; - } - } - /** - * Generates pseudo-random 256 bit salt. - * The salt is used to ensure that the 0x order generated has a unique orderHash that does - * not collide with any other outstanding orders. - */ - public static generatePseudoRandomSalt(): BigNumber.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(MAX_DIGITS_IN_UNSIGNED_256_INT); - const factor = new BigNumber(10).pow(MAX_DIGITS_IN_UNSIGNED_256_INT - 1); - const salt = randomNumber.times(factor).round(); - return salt; - } - /** Checks if order hash is valid */ - public static isValidOrderHash(orderHash: string): boolean { - assert.isString('orderHash', orderHash); - const isValid = /^0x[0-9A-F]{64}$/i.test(orderHash); - return isValid; - } - /* - * 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. - */ - public static toUnitAmount(amount: BigNumber.BigNumber, decimals: number): BigNumber.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 - */ - public static toBaseUnitAmount(amount: BigNumber.BigNumber, decimals: number): BigNumber.BigNumber { - assert.isBigNumber('amount', amount); - assert.isNumber('decimals', decimals); - - const unit = new BigNumber(10).pow(decimals); - 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 deleted file mode 100644 index 9f4cd8039..000000000 --- a/src/ts/contract_wrappers/contract_wrapper.ts +++ /dev/null @@ -1,48 +0,0 @@ -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 { - 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 deleted file mode 100644 index 38043dd55..000000000 --- a/src/ts/contract_wrappers/exchange_wrapper.ts +++ /dev/null @@ -1,37 +0,0 @@ -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 { - 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 deleted file mode 100644 index dee957f2f..000000000 --- a/src/ts/globals.d.ts +++ /dev/null @@ -1,63 +0,0 @@ -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; -} - -// 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 */ -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; - const bufferToHex: (buff: Buffer) => string; - 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); -declare module 'es6-promisify' { - export = promisify; -} - -declare module 'ethereumjs-abi' { - const soliditySHA3: (argTypes: string[], args: any[]) => Buffer; -} diff --git a/src/ts/schemas/ec_signature_schema.ts b/src/ts/schemas/ec_signature_schema.ts deleted file mode 100644 index 94e58e53c..000000000 --- a/src/ts/schemas/ec_signature_schema.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const ECSignatureParameter = { - id: '/ECSignatureParameter', - type: 'string', - pattern: '^0[xX][0-9A-Fa-f]{64}$', -}; - -export const ECSignatureSchema = { - id: '/ECSignature', - properties: { - v: { - type: 'number', - minimum: 27, - maximum: 28, - }, - r: {$ref: '/ECSignatureParameter'}, - s: {$ref: '/ECSignatureParameter'}, - }, - required: ['v', 'r', 's'], - type: 'object', -}; diff --git a/src/ts/types.ts b/src/ts/types.ts deleted file mode 100644 index 4da03a4d3..000000000 --- a/src/ts/types.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as _ from 'lodash'; - -// 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 -function strEnum(values: string[]): {[key: string]: string} { - return _.reduce(values, (result, key) => { - result[key] = key; - return result; - }, 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', -]); -export type SolidityTypes = keyof typeof SolidityTypes; diff --git a/src/ts/utils/assert.ts b/src/ts/utils/assert.ts deleted file mode 100644 index 1baf572d1..000000000 --- a/src/ts/utils/assert.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as _ from 'lodash'; -import * as BigNumber from 'bignumber.js'; -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): void { - const isBigNumber = _.isObject(value) && value.isBigNumber; - this.assert(isBigNumber, this.typeAssertionMessage(variableName, 'BigNumber', value)); - }, - 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): void { - this.assert(_.isString(value) && HEX_REGEX.test(value), - this.typeAssertionMessage(variableName, 'HexString', value)); - }, - 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): void { - this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value)); - }, - doesConformToSchema(variableName: string, value: object, schema: Schema): void { - const schemaValidator = new SchemaValidator(); - const validationResult = schemaValidator.validate(value, schema); - const hasValidationErrors = validationResult.errors.length > 0; - const msg = `Expected ${variableName} to conform to schema ${schema.id} -Encountered: ${JSON.stringify(value, null, '\t')} -Validation errors: ${validationResult.errors.join(', ')}`; - this.assert(!hasValidationErrors, msg); - }, - assert(condition: boolean, message: string): void { - if (!condition) { - throw new Error(message); - } - }, - 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 deleted file mode 100644 index ec2fe744a..000000000 --- a/src/ts/utils/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const constants = { - NULL_ADDRESS: '0x0000000000000000000000000000000000000000', -}; diff --git a/src/ts/utils/schema_validator.ts b/src/ts/utils/schema_validator.ts deleted file mode 100644 index bd2f97d2b..000000000 --- a/src/ts/utils/schema_validator.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Validator, ValidatorResult} from 'jsonschema'; -import {ECSignatureSchema, ECSignatureParameter} from '../schemas/ec_signature_schema'; - -export class SchemaValidator { - private validator: Validator; - constructor() { - this.validator = new Validator(); - this.validator.addSchema(ECSignatureParameter, ECSignatureParameter.id); - this.validator.addSchema(ECSignatureSchema, ECSignatureSchema.id); - } - public validate(instance: object, schema: Schema): ValidatorResult { - return this.validator.validate(instance, schema); - } -} diff --git a/src/ts/utils/utils.ts b/src/ts/utils/utils.ts deleted file mode 100644 index b514b702d..000000000 --- a/src/ts/utils/utils.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as BN from 'bn.js'; - -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.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 deleted file mode 100644 index 3b460e4da..000000000 --- a/src/ts/web3_wrapper.ts +++ /dev/null @@ -1,69 +0,0 @@ -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 { - const defaultAccount = this.web3.eth.defaultAccount; - if (!_.isUndefined(defaultAccount)) { - return defaultAccount; - } - const firstAccount = await this.getFirstAddressIfExistsAsync(); - return firstAccount; - } - public async getFirstAddressIfExistsAsync(): Promise { - const addresses = await promisify(this.web3.eth.getAccounts)(); - if (_.isEmpty(addresses)) { - return undefined; - } - return (addresses as string[])[0]; - } - public async getNodeVersionAsync(): Promise { - const nodeVersion = await promisify(this.web3.version.getNode)(); - return nodeVersion; - } - public getCurrentProvider(): Web3.Provider { - return this.web3.currentProvider; - } - public async getNetworkIdIfExistsAsync(): Promise { - try { - const networkId = await this.getNetworkAsync(); - return Number(networkId); - } catch (err) { - return undefined; - } - } - public async getBalanceInEthAsync(owner: string): Promise { - const balanceInWei = await promisify(this.web3.eth.getBalance)(owner); - const balanceEth = this.web3.fromWei(balanceInWei, 'ether'); - return balanceEth; - } - public async doesContractExistAtAddressAsync(address: string): Promise { - 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 { - const signData = await promisify(this.web3.eth.sign)(address, message); - return signData; - } - public async getBlockTimestampAsync(blockHash: string): Promise { - const {timestamp} = await promisify(this.web3.eth.getBlock)(blockHash); - return timestamp; - } - private async getNetworkAsync(): Promise { - const networkId = await promisify(this.web3.version.getNetwork)(); - return networkId; - } -} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..4da03a4d3 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,36 @@ +import * as _ from 'lodash'; + +// 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 +function strEnum(values: string[]): {[key: string]: string} { + return _.reduce(values, (result, key) => { + result[key] = key; + return result; + }, 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', +]); +export type SolidityTypes = keyof typeof SolidityTypes; diff --git a/src/utils/assert.ts b/src/utils/assert.ts new file mode 100644 index 000000000..1baf572d1 --- /dev/null +++ b/src/utils/assert.ts @@ -0,0 +1,47 @@ +import * as _ from 'lodash'; +import * as BigNumber from 'bignumber.js'; +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): void { + const isBigNumber = _.isObject(value) && value.isBigNumber; + this.assert(isBigNumber, this.typeAssertionMessage(variableName, 'BigNumber', value)); + }, + 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): void { + this.assert(_.isString(value) && HEX_REGEX.test(value), + this.typeAssertionMessage(variableName, 'HexString', value)); + }, + 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): void { + this.assert(_.isFinite(value), this.typeAssertionMessage(variableName, 'number', value)); + }, + doesConformToSchema(variableName: string, value: object, schema: Schema): void { + const schemaValidator = new SchemaValidator(); + const validationResult = schemaValidator.validate(value, schema); + const hasValidationErrors = validationResult.errors.length > 0; + const msg = `Expected ${variableName} to conform to schema ${schema.id} +Encountered: ${JSON.stringify(value, null, '\t')} +Validation errors: ${validationResult.errors.join(', ')}`; + this.assert(!hasValidationErrors, msg); + }, + assert(condition: boolean, message: string): void { + if (!condition) { + throw new Error(message); + } + }, + typeAssertionMessage(variableName: string, type: string, value: any): string { + return `Expected ${variableName} to be of type ${type}, encountered: ${value}`; + }, +}; diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 000000000..ec2fe744a --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,3 @@ +export const constants = { + NULL_ADDRESS: '0x0000000000000000000000000000000000000000', +}; diff --git a/src/utils/schema_validator.ts b/src/utils/schema_validator.ts new file mode 100644 index 000000000..bd2f97d2b --- /dev/null +++ b/src/utils/schema_validator.ts @@ -0,0 +1,14 @@ +import {Validator, ValidatorResult} from 'jsonschema'; +import {ECSignatureSchema, ECSignatureParameter} from '../schemas/ec_signature_schema'; + +export class SchemaValidator { + private validator: Validator; + constructor() { + this.validator = new Validator(); + this.validator.addSchema(ECSignatureParameter, ECSignatureParameter.id); + this.validator.addSchema(ECSignatureSchema, ECSignatureSchema.id); + } + public validate(instance: object, schema: Schema): ValidatorResult { + return this.validator.validate(instance, schema); + } +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 000000000..b514b702d --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,18 @@ +import * as BN from 'bn.js'; + +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.BigNumber) { + return new BN(value.toString(), 10); + }, + consoleLog(message: string): void { + /* tslint:disable */ + console.log(message); + /* tslint:enable */ + }, +}; diff --git a/src/web3_wrapper.ts b/src/web3_wrapper.ts new file mode 100644 index 000000000..3b460e4da --- /dev/null +++ b/src/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 { + const defaultAccount = this.web3.eth.defaultAccount; + if (!_.isUndefined(defaultAccount)) { + return defaultAccount; + } + const firstAccount = await this.getFirstAddressIfExistsAsync(); + return firstAccount; + } + public async getFirstAddressIfExistsAsync(): Promise { + const addresses = await promisify(this.web3.eth.getAccounts)(); + if (_.isEmpty(addresses)) { + return undefined; + } + return (addresses as string[])[0]; + } + public async getNodeVersionAsync(): Promise { + const nodeVersion = await promisify(this.web3.version.getNode)(); + return nodeVersion; + } + public getCurrentProvider(): Web3.Provider { + return this.web3.currentProvider; + } + public async getNetworkIdIfExistsAsync(): Promise { + try { + const networkId = await this.getNetworkAsync(); + return Number(networkId); + } catch (err) { + return undefined; + } + } + public async getBalanceInEthAsync(owner: string): Promise { + const balanceInWei = await promisify(this.web3.eth.getBalance)(owner); + const balanceEth = this.web3.fromWei(balanceInWei, 'ether'); + return balanceEth; + } + public async doesContractExistAtAddressAsync(address: string): Promise { + 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 { + const signData = await promisify(this.web3.eth.sign)(address, message); + return signData; + } + public async getBlockTimestampAsync(blockHash: string): Promise { + const {timestamp} = await promisify(this.web3.eth.getBlock)(blockHash); + return timestamp; + } + private async getNetworkAsync(): Promise { + const networkId = await promisify(this.web3.version.getNetwork)(); + return networkId; + } +} -- cgit v1.2.3 From 021391744f7441594e36297c2d52cc513f06dbbd Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 29 May 2017 11:39:20 +0200 Subject: Fix imports --- src/contract_wrappers/exchange_wrapper.ts | 2 +- test/0x.js_test.ts | 4 ++-- test/contract_wrapper_test.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index 38043dd55..f9585e991 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -3,7 +3,7 @@ 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 * as ExchangeArtifacts from '../artifacts/Exchange.json'; import {ECSignatureSchema} from '../schemas/ec_signature_schema'; export class ExchangeWrapper extends ContractWrapper { diff --git a/test/0x.js_test.ts b/test/0x.js_test.ts index 9c61c3e10..5dbb781ab 100644 --- a/test/0x.js_test.ts +++ b/test/0x.js_test.ts @@ -3,8 +3,8 @@ import * as chai from 'chai'; import 'mocha'; import * as BigNumber from 'bignumber.js'; import ChaiBigNumber = require('chai-bignumber'); -import {ZeroEx} from '../src/ts/0x.js'; -import {constants} from '../src/ts/utils/constants'; +import {ZeroEx} from '../src/0x.js'; +import {constants} from '../src/utils/constants'; // Use BigNumber chai add-on chai.use(ChaiBigNumber()); diff --git a/test/contract_wrapper_test.ts b/test/contract_wrapper_test.ts index 67dae3a4e..4ff56ea3e 100644 --- a/test/contract_wrapper_test.ts +++ b/test/contract_wrapper_test.ts @@ -3,9 +3,9 @@ import * as chai from 'chai'; import chaiAsPromised = require('chai-as-promised'); import * as Web3 from 'web3'; import {web3Factory} from './utils/web3_factory'; -import {ExchangeWrapper} from '../src/ts/contract_wrappers/exchange_wrapper'; +import {ExchangeWrapper} from '../src/contract_wrappers/exchange_wrapper'; import {BlockchainLifecycle} from './utils/blockchain_lifecycle'; -import {Web3Wrapper} from './../src/ts/web3_wrapper'; +import {Web3Wrapper} from './../src/web3_wrapper'; const expect = chai.expect; chai.use(chaiAsPromised); -- cgit v1.2.3 From ff5db0d1cb0daaa032bf467830665ec98b4cbfed Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 29 May 2017 11:41:09 +0200 Subject: Fix lint command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d83c1d095..5ed9354cd 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "build:bundle": "webpack", "build:commonjs": "tsc; copyfiles -u 2 ./src/artifacts/*.json ../0x.js/lib/src/artifacts;", "build": "npm run clean && run-p build:*", - "lint": "tslint src/ts/**/*", + "lint": "tslint src/**/*", "test": "run-s clean build:commonjs && mocha lib/test/**/*_test.js", "test:coverage": "nyc npm run test --all", "docs:json": "typedoc --json docs/index.json .", -- cgit v1.2.3 From 209d8483cf48a87a8e90de2b9a5be840dfc23613 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Mon, 29 May 2017 11:42:17 +0200 Subject: Fix linter command --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ed9354cd..5c352bc88 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "build:bundle": "webpack", "build:commonjs": "tsc; copyfiles -u 2 ./src/artifacts/*.json ../0x.js/lib/src/artifacts;", "build": "npm run clean && run-p build:*", - "lint": "tslint src/**/*", + "lint": "tslint src/**/*.ts", "test": "run-s clean build:commonjs && mocha lib/test/**/*_test.js", "test:coverage": "nyc npm run test --all", "docs:json": "typedoc --json docs/index.json .", -- cgit v1.2.3