diff options
-rw-r--r-- | circle.yml | 2 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/contract_wrappers/exchange_wrapper.ts | 91 | ||||
-rw-r--r-- | src/types.ts | 33 | ||||
-rw-r--r-- | src/web3_wrapper.ts | 7 | ||||
-rw-r--r-- | test/utils/blockchain_lifecycle.ts | 2 |
6 files changed, 127 insertions, 10 deletions
diff --git a/circle.yml b/circle.yml index 448524aa1..4919516f2 100644 --- a/circle.yml +++ b/circle.yml @@ -4,7 +4,7 @@ machine: test: override: - - node node_modules/ethereumjs-testrpc/bin/testrpc -m "concert load couple harbor equip island argue ramp clarify fence smart topic": + - npm run testrpc: background: true - git clone git@github.com:0xProject/contracts.git ../contracts - cd ../contracts; git checkout 38c2b4c; npm install && npm run migrate diff --git a/package.json b/package.json index 75a55b899..59e33a7dd 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "prebuild": "npm run clean", "build": "run-p build:*:prod", - "lint": "tslint src/**/*.ts", + "lint": "tslint src/**/*.ts test/**/*.ts", "test": "run-s clean test:commonjs", "test:umd": "run-s substitute_umd_bundle run_mocha; npm run clean", "test:coverage": "nyc npm run test --all", diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index f0f153c2b..c129eb367 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -1,12 +1,28 @@ import * as _ from 'lodash'; import {Web3Wrapper} from '../web3_wrapper'; -import {ECSignature, ZeroExError, ExchangeContract} from '../types'; +import { + ECSignature, + ExchangeContract, + ExchangeContractErrs, + OrderValues, + OrderAddresses, +} 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'; +import {ContractResponse} from '../types'; +import {constants} from '../utils/constants'; export class ExchangeWrapper extends ContractWrapper { + private exchangeContractErrToMsg = { + [ExchangeContractErrs.ERROR_FILL_EXPIRED]: 'The order you attempted to fill is expired', + [ExchangeContractErrs.ERROR_CANCEL_EXPIRED]: 'The order you attempted to cancel is expired', + [ExchangeContractErrs.ERROR_FILL_NO_VALUE]: 'This order has already been filled or cancelled', + [ExchangeContractErrs.ERROR_CANCEL_NO_VALUE]: 'This order has already been filled or cancelled', + [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', + }; constructor(web3Wrapper: Web3Wrapper) { super(web3Wrapper); } @@ -16,11 +32,8 @@ export class ExchangeWrapper extends ContractWrapper { 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 senderAddress = await this.web3Wrapper.getSenderAddressOrThrowAsync(); + const exchangeInstance = await this.getExchangeInstanceOrThrowAsync(); const isValidSignature = await exchangeInstance.isValidSignature.call( signerAddressHex, @@ -29,9 +42,73 @@ export class ExchangeWrapper extends ContractWrapper { ecSignature.r, ecSignature.s, { - from: senderAddressIfExists, + from: senderAddress, }, ); return isValidSignature; } + public async fillOrderAsync(maker: string, taker: string, makerTokenAddress: string, + takerTokenAddress: string, makerTokenAmount: BigNumber.BigNumber, + takerTokenAmount: BigNumber.BigNumber, makerFee: BigNumber.BigNumber, + takerFee: BigNumber.BigNumber, expirationUnixTimestampSec: BigNumber.BigNumber, + feeRecipient: string, fillAmount: BigNumber.BigNumber, + ecSignature: ECSignature, salt: BigNumber.BigNumber) { + assert.isBigNumber('salt', salt); + assert.isBigNumber('makerFee', makerFee); + assert.isBigNumber('takerFee', takerFee); + assert.isBigNumber('fillAmount', fillAmount); + assert.isBigNumber('makerTokenAmount', makerTokenAmount); + assert.isBigNumber('takerTokenAmount', takerTokenAmount); + assert.isBigNumber('expirationUnixTimestampSec', expirationUnixTimestampSec); + assert.isETHAddressHex('maker', maker); + assert.isETHAddressHex('taker', taker); + assert.isETHAddressHex('feeRecipient', feeRecipient); + assert.isETHAddressHex('makerTokenAddress', makerTokenAddress); + assert.isETHAddressHex('takerTokenAddress', takerTokenAddress); + assert.doesConformToSchema('ecSignature', ecSignature, ecSignatureSchema); + + const senderAddress = await this.web3Wrapper.getSenderAddressOrThrowAsync(); + const exchangeInstance = await this.getExchangeInstanceOrThrowAsync(); + + taker = taker === '' ? constants.NULL_ADDRESS : taker; + const shouldCheckTransfer = true; + const orderAddresses: OrderAddresses = [ + maker, + taker, + makerTokenAddress, + takerTokenAddress, + feeRecipient, + ]; + const orderValues: OrderValues = [ + makerTokenAmount, + takerTokenAmount, + makerFee, + takerFee, + expirationUnixTimestampSec, + salt, + ]; + const response: ContractResponse = await exchangeInstance.fill( + orderAddresses, + orderValues, + fillAmount, + shouldCheckTransfer, + ecSignature.v, + ecSignature.r, + ecSignature.s, + { + from: senderAddress, + }, + ); + const errEvent = _.find(response.logs, {event: 'LogError'}); + if (!_.isUndefined(errEvent)) { + const errCode = errEvent.args.errorId.toNumber(); + const humanReadableErrMessage = this.exchangeContractErrToMsg[errCode]; + throw new Error(humanReadableErrMessage); + } + return response; + } + private async getExchangeInstanceOrThrowAsync(): Promise<ExchangeContract> { + const contractInstance = await this.instantiateContractIfExistsAsync((ExchangeArtifacts as any)); + return contractInstance as ExchangeContract; + } } diff --git a/src/types.ts b/src/types.ts index 3bed01547..0c661915e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,8 +26,23 @@ export interface ECSignature { s: string; } +export type OrderAddresses = [string, string, string, string, string]; + +export type OrderValues = [ + BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.BigNumber, + BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.BigNumber +]; + +export interface TxData { + from: string; +} + export interface ExchangeContract { isValidSignature: any; + fill: ( + orderAddresses: OrderAddresses, orderValues: OrderValues, fillAmount: BigNumber.BigNumber, + shouldCheckTransfer: boolean, v: number, r: string, s: string, txData: TxData, + ) => ContractResponse; } export const SolidityTypes = strEnum([ @@ -35,3 +50,21 @@ export const SolidityTypes = strEnum([ 'uint256', ]); export type SolidityTypes = keyof typeof SolidityTypes; + +export enum ExchangeContractErrs { + 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 interface ContractResponse { + logs: ContractEvent[]; +} + +export interface ContractEvent { + event: string; + args: any; +} diff --git a/src/web3_wrapper.ts b/src/web3_wrapper.ts index 97d04db8c..1ed1c0b29 100644 --- a/src/web3_wrapper.ts +++ b/src/web3_wrapper.ts @@ -2,6 +2,8 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; import * as BigNumber from 'bignumber.js'; import promisify = require('es6-promisify'); +import {ZeroExError} from "./types"; +import {assert} from "./utils/assert"; export class Web3Wrapper { private web3: Web3; @@ -20,6 +22,11 @@ export class Web3Wrapper { const firstAccount = await this.getFirstAddressIfExistsAsync(); return firstAccount; } + public async getSenderAddressOrThrowAsync(): Promise<string> { + const senderAddressIfExists = await this.getSenderAddressIfExistsAsync(); + assert.assert(!_.isUndefined(senderAddressIfExists), ZeroExError.USER_HAS_NO_ASSOCIATED_ADDRESSES); + return senderAddressIfExists as string; + } public async getFirstAddressIfExistsAsync(): Promise<string|undefined> { const addresses = await promisify(this.web3.eth.getAccounts)(); if (_.isEmpty(addresses)) { diff --git a/test/utils/blockchain_lifecycle.ts b/test/utils/blockchain_lifecycle.ts index 68e169ac0..50eb57b95 100644 --- a/test/utils/blockchain_lifecycle.ts +++ b/test/utils/blockchain_lifecycle.ts @@ -17,4 +17,4 @@ export class BlockchainLifecycle { throw new Error(`Snapshot with id #${this.snapshotId} failed to revert`); } } -}; +} |