From a0ea0415ddd2fb2b9b23ad249d5c0230314260c4 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Mon, 10 Dec 2018 09:16:15 -0800 Subject: fix(instant): Don't update heartbeat if amount is 0 --- packages/instant/src/redux/async_data.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages') diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index c67b222d1..932eb93e9 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -97,6 +97,7 @@ export const asyncData = { if ( !_.isUndefined(selectedAssetUnitAmount) && !_.isUndefined(selectedAsset) && + selectedAssetUnitAmount.greaterThan(BIG_NUMBER_ZERO) && buyOrderState.processState === OrderProcessState.None && selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20 ) { -- cgit v1.2.3 From 6e54514013702fa8ca9a6b69c04f4d2468cd8b66 Mon Sep 17 00:00:00 2001 From: fragosti Date: Mon, 10 Dec 2018 17:30:32 -0800 Subject: feat: change input to number input --- packages/instant/src/components/scaling_input.tsx | 1 + packages/instant/src/components/ui/input.tsx | 4 ++++ 2 files changed, 5 insertions(+) (limited to 'packages') diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx index 791692257..2c5567dc1 100644 --- a/packages/instant/src/components/scaling_input.tsx +++ b/packages/instant/src/components/scaling_input.tsx @@ -135,6 +135,7 @@ export class ScalingInput extends React.Component props.theme[props.fontColor || 'white']} !important; opacity: 0.5 !important; } + &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } } `; -- cgit v1.2.3 From c46870d296e29399ebf9ce8a9c904f170f8a638a Mon Sep 17 00:00:00 2001 From: Brandon Millman Date: Mon, 10 Dec 2018 19:27:27 -0800 Subject: feature(website): add 0x Instant as a track in the docs --- .../public/images/developers/tutorials/integrate_0x_instant.svg | 3 +++ packages/website/translations/english.json | 3 +++ packages/website/ts/pages/documentation/docs_home.tsx | 8 ++++++++ packages/website/ts/types.ts | 2 ++ 4 files changed, 16 insertions(+) create mode 100644 packages/website/public/images/developers/tutorials/integrate_0x_instant.svg (limited to 'packages') diff --git a/packages/website/public/images/developers/tutorials/integrate_0x_instant.svg b/packages/website/public/images/developers/tutorials/integrate_0x_instant.svg new file mode 100644 index 000000000..e9c9278a8 --- /dev/null +++ b/packages/website/public/images/developers/tutorials/integrate_0x_instant.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/website/translations/english.json b/packages/website/translations/english.json index 78f29d0f6..8f882f506 100644 --- a/packages/website/translations/english.json +++ b/packages/website/translations/english.json @@ -92,6 +92,9 @@ "ORDER_BASICS_DESCRIPTION": "Tutorial on how to create, validate and fill an order using 0x", "USE_NETWORKED_LIQUIDITY": "use networked liquidity", "USE_NETWORKED_LIQUIDITY_DESCRIPTION": "Learn how to tap into networked liquidity using the Standard Relayer API", + "INTEGRATE_0X_INSTANT": "integrate 0x Instant and AssetBuyer", + "INTEGRATE_0X_INSTANT_DESCRIPTION": + "Add 0x Instant to your app or website, or use AssetBuyer to programmatically make market buys", "VIEW_ALL_DOCUMENTATION": "view all documentation", "SANDBOX": "0x.js sandbox", "GITHUB": "github", diff --git a/packages/website/ts/pages/documentation/docs_home.tsx b/packages/website/ts/pages/documentation/docs_home.tsx index f68d2892f..9dc779e96 100644 --- a/packages/website/ts/pages/documentation/docs_home.tsx +++ b/packages/website/ts/pages/documentation/docs_home.tsx @@ -44,6 +44,14 @@ const TUTORIALS: TutorialInfo[] = [ to: `${WebsitePaths.Wiki}#Find,-Submit,-Fill-Order-From-Relayer`, }, }, + { + iconUrl: '/images/developers/tutorials/integrate_0x_instant.svg', + description: Key.Integrate0xInstantDescription, + link: { + title: Key.Integrate0xInstant, + to: `${WebsitePaths.Wiki}#Get-Started-With-Instant`, + }, + }, ]; const CATEGORY_TO_PACKAGES: ObjectMap = { diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index b20dd7095..2967fdac5 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -474,6 +474,8 @@ export enum Key { OrderBasicsDescription = 'ORDER_BASICS_DESCRIPTION', UseNetworkedLiquidity = 'USE_NETWORKED_LIQUIDITY', UseNetworkedLiquidityDescription = 'USE_NETWORKED_LIQUIDITY_DESCRIPTION', + Integrate0xInstant = 'INTEGRATE_0X_INSTANT', + Integrate0xInstantDescription = 'INTEGRATE_0X_INSTANT_DESCRIPTION', ViewAllDocumentation = 'VIEW_ALL_DOCUMENTATION', Sandbox = 'SANDBOX', Github = 'GITHUB', -- cgit v1.2.3 From 5cbe04acab982ead39f7794547de0f045952a1b7 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 7 Dec 2018 11:14:55 -0800 Subject: feat(instant): ETH/USD toggle --- packages/instant/src/components/order_details.tsx | 196 +++++++++++++++++---- .../containers/latest_buy_quote_order_details.ts | 33 ++-- packages/instant/src/redux/actions.ts | 4 +- packages/instant/src/redux/reducer.ts | 8 + packages/instant/src/types.ts | 5 + packages/instant/src/util/buy_quote.ts | 71 ++++++++ 6 files changed, 269 insertions(+), 48 deletions(-) create mode 100644 packages/instant/src/util/buy_quote.ts (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index a8e0e2513..85761a5b9 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -6,63 +6,92 @@ import { oc } from 'ts-optchain'; import { BIG_NUMBER_ZERO } from '../constants'; import { ColorOption } from '../style/theme'; +import { BaseCurrency } from '../types'; +import { buyQuoteUtil } from '../util/buy_quote'; import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; import { Container } from './ui/container'; import { Flex } from './ui/flex'; -import { Text } from './ui/text'; +import { Text, TextProps } from './ui/text'; + +interface BaseCurrenySwitchProps { + currencyName: string; + onClick: () => void; + isSelected: boolean; +} +const BaseCurrencySelector: React.StatelessComponent = props => { + const textStyle: TextProps = { onClick: props.onClick, fontSize: '12px' }; + if (props.isSelected) { + textStyle.fontColor = ColorOption.primaryColor; + textStyle.fontWeight = 700; + } + return {props.currencyName}; +}; export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; selectedAssetUnitAmount?: BigNumber; ethUsdPrice?: BigNumber; isLoading: boolean; + assetName?: string; + baseCurrency: BaseCurrency; + onBaseCurrencySwitchEth: () => void; + onBaseCurrencySwitchUsd: () => void; } export class OrderDetails extends React.Component { public render(): React.ReactNode { const { buyQuoteInfo, ethUsdPrice, selectedAssetUnitAmount } = this.props; - const buyQuoteAccessor = oc(buyQuoteInfo); - const assetEthBaseUnitAmount = buyQuoteAccessor.assetEthAmount(); - const feeEthBaseUnitAmount = buyQuoteAccessor.feeEthAmount(); - const totalEthBaseUnitAmount = buyQuoteAccessor.totalEthAmount(); - const pricePerTokenEth = - !_.isUndefined(assetEthBaseUnitAmount) && - !_.isUndefined(selectedAssetUnitAmount) && - !selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO) - ? assetEthBaseUnitAmount.div(selectedAssetUnitAmount).ceil() - : undefined; + const weiAmounts = buyQuoteUtil.getWeiAmounts(selectedAssetUnitAmount, buyQuoteInfo); + + const displayAmounts = + this.props.baseCurrency === BaseCurrency.USD + ? buyQuoteUtil.displayAmountsUsd(weiAmounts, ethUsdPrice) + : buyQuoteUtil.displayAmountsEth(weiAmounts, ethUsdPrice); + return ( - - Order Details - + + + Order Details + + + + + + / + + + + - - - + @@ -79,6 +108,103 @@ export interface EthAmountRowProps { isLoading: boolean; } +export interface OrderDetailsRowProps { + labelText: string; + isLabelBold?: boolean; + isLoading: boolean; + value?: React.ReactNode; +} +export class OrderDetailsRow extends React.Component { + public render(): React.ReactNode { + const { labelText, value, isLabelBold, isLoading } = this.props; + return ( + + + + {labelText} + + + {value || ( + + + + )} + + + + ); + } +} +export interface TotalCostRowProps { + displayPrimaryTotalCost?: React.ReactNode; + displaySecondaryTotalCost?: React.ReactNode; + isLoading: boolean; +} +export class TotalCostRow extends React.Component { + public render(): React.ReactNode { + let value: React.ReactNode; + if (this.props.displayPrimaryTotalCost) { + const secondaryText = this.props.displaySecondaryTotalCost && ( + + ({this.props.displaySecondaryTotalCost}) + + ); + value = ( + + {secondaryText} + + {this.props.displayPrimaryTotalCost} + + + ); + } + + return ( + + ); + } +} + +export interface TokenAmountRowProps { + assetName?: string; + displayPricePerToken?: React.ReactNode; + displayTotalPrice?: React.ReactNode; + isLoading: boolean; + numTokens?: BigNumber; +} +export class TokenAmountRow extends React.Component { + public static DEFAULT_TEXT: string = 'Token Price'; + public render(): React.ReactNode { + return ( + + ); + } + private _labelText(): string { + if (this.props.isLoading) { + return TokenAmountRow.DEFAULT_TEXT; + } + const { numTokens, displayPricePerToken, assetName } = this.props; + if (numTokens) { + let numTokensWithSymbol = numTokens.toString(); + + if (assetName) { + numTokensWithSymbol += ` ${assetName}`; + } + + if (displayPricePerToken) { + numTokensWithSymbol += ` @ ${displayPricePerToken}`; + } + return numTokensWithSymbol; + } + + return TokenAmountRow.DEFAULT_TEXT; + } +} + export class EthAmountRow extends React.Component { public static defaultProps = { shouldEmphasize: false, diff --git a/packages/instant/src/containers/latest_buy_quote_order_details.ts b/packages/instant/src/containers/latest_buy_quote_order_details.ts index 5dfe535e7..148735c47 100644 --- a/packages/instant/src/containers/latest_buy_quote_order_details.ts +++ b/packages/instant/src/containers/latest_buy_quote_order_details.ts @@ -1,32 +1,41 @@ -import { BuyQuoteInfo } from '@0x/asset-buyer'; -import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; import { oc } from 'ts-optchain'; +import { Action, actions } from '../redux/actions'; import { State } from '../redux/reducer'; -import { OrderDetails } from '../components/order_details'; -import { AsyncProcessState } from '../types'; +import { OrderDetails, OrderDetailsProps } from '../components/order_details'; +import { AsyncProcessState, BaseCurrency, Omit } from '../types'; +import { assetUtils } from '../util/asset'; -export interface LatestBuyQuoteOrderDetailsProps {} - -interface ConnectedState { - buyQuoteInfo?: BuyQuoteInfo; - selectedAssetUnitAmount?: BigNumber; - ethUsdPrice?: BigNumber; - isLoading: boolean; -} +type DispatchProperties = 'onBaseCurrencySwitchEth' | 'onBaseCurrencySwitchUsd'; +interface ConnectedState extends Omit {} const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProps): ConnectedState => ({ // use the worst case quote info buyQuoteInfo: oc(state).latestBuyQuote.worstCaseQuoteInfo(), selectedAssetUnitAmount: state.selectedAssetUnitAmount, ethUsdPrice: state.ethUsdPrice, isLoading: state.quoteRequestState === AsyncProcessState.Pending, + assetName: assetUtils.bestNameForAsset(state.selectedAsset), + baseCurrency: state.baseCurrency, }); +interface ConnectedDispatch extends Pick {} +const mapDispatchToProps = (dispatch: Dispatch): ConnectedDispatch => ({ + onBaseCurrencySwitchEth: () => { + dispatch(actions.updateBaseCurrency(BaseCurrency.ETH)); + }, + onBaseCurrencySwitchUsd: () => { + dispatch(actions.updateBaseCurrency(BaseCurrency.USD)); + }, +}); + +export interface LatestBuyQuoteOrderDetailsProps {} export const LatestBuyQuoteOrderDetails: React.ComponentClass = connect( mapStateToProps, + mapDispatchToProps, )(OrderDetails); diff --git a/packages/instant/src/redux/actions.ts b/packages/instant/src/redux/actions.ts index 77e3dec12..9d7a61fc7 100644 --- a/packages/instant/src/redux/actions.ts +++ b/packages/instant/src/redux/actions.ts @@ -2,7 +2,7 @@ import { BuyQuote } from '@0x/asset-buyer'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; -import { ActionsUnion, AddressAndEthBalanceInWei, Asset, StandardSlidingPanelContent } from '../types'; +import { ActionsUnion, AddressAndEthBalanceInWei, Asset, BaseCurrency, StandardSlidingPanelContent } from '../types'; export interface PlainAction { type: T; @@ -43,6 +43,7 @@ export enum ActionTypes { RESET_AMOUNT = 'RESET_AMOUNT', OPEN_STANDARD_SLIDING_PANEL = 'OPEN_STANDARD_SLIDING_PANEL', CLOSE_STANDARD_SLIDING_PANEL = 'CLOSE_STANDARD_SLIDING_PANEL', + UPDATE_BASE_CURRENCY = 'UPDATE_BASE_CURRENCY', } export const actions = { @@ -72,4 +73,5 @@ export const actions = { openStandardSlidingPanel: (content: StandardSlidingPanelContent) => createAction(ActionTypes.OPEN_STANDARD_SLIDING_PANEL, content), closeStandardSlidingPanel: () => createAction(ActionTypes.CLOSE_STANDARD_SLIDING_PANEL), + updateBaseCurrency: (baseCurrency: BaseCurrency) => createAction(ActionTypes.UPDATE_BASE_CURRENCY, baseCurrency), }; diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index a9a407b7d..4e734041f 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -14,6 +14,7 @@ import { Asset, AssetMetaData, AsyncProcessState, + BaseCurrency, DisplayStatus, Network, OrderProcessState, @@ -33,6 +34,7 @@ export interface DefaultState { latestErrorDisplayStatus: DisplayStatus; quoteRequestState: AsyncProcessState; standardSlidingPanelSettings: StandardSlidingPanelSettings; + baseCurrency: BaseCurrency; } // State that is required but needs to be derived from the props @@ -64,6 +66,7 @@ export const DEFAULT_STATE: DefaultState = { animationState: 'none', content: StandardSlidingPanelContent.None, }, + baseCurrency: BaseCurrency.ETH, }; export const createReducer = (initialState: State) => { @@ -243,6 +246,11 @@ export const createReducer = (initialState: State) => { animationState: 'slidOut', }, }; + case ActionTypes.UPDATE_BASE_CURRENCY: + return { + ...state, + baseCurrency: action.data, + }; default: return state; } diff --git a/packages/instant/src/types.ts b/packages/instant/src/types.ts index 1c7490e63..e7c920f36 100644 --- a/packages/instant/src/types.ts +++ b/packages/instant/src/types.ts @@ -26,6 +26,11 @@ export enum QuoteFetchOrigin { Heartbeat = 'Heartbeat', } +export enum BaseCurrency { + USD = 'USD', + ETH = 'ETH', +} + export interface SimulatedProgress { startTimeUnix: number; expectedEndTimeUnix: number; diff --git a/packages/instant/src/util/buy_quote.ts b/packages/instant/src/util/buy_quote.ts new file mode 100644 index 000000000..acd4d389c --- /dev/null +++ b/packages/instant/src/util/buy_quote.ts @@ -0,0 +1,71 @@ +import { BuyQuoteInfo } from '@0x/asset-buyer'; +import { BigNumber } from '@0x/utils'; +import * as _ from 'lodash'; +import { oc } from 'ts-optchain'; + +import { format } from '../util/format'; + +import { BIG_NUMBER_ZERO } from '../constants'; + +export interface DisplayAmounts { + pricePerToken: React.ReactNode; + assetTotal: React.ReactNode; + feeTotal: React.ReactNode; + primaryGrandTotal: React.ReactNode; + secondaryGrandTotal?: React.ReactNode; +} + +export interface BuyQuoteWeiAmounts { + assetTotalInWei: BigNumber | undefined; + feeTotalInWei: BigNumber | undefined; + grandTotalInWei: BigNumber | undefined; + pricePerTokenInWei: BigNumber | undefined; +} + +const ethDisplayFormat = (amountInWei?: BigNumber) => { + return format.ethBaseUnitAmount(amountInWei, 4, ''); +}; +const usdDisplayFormat = (amountInWei?: BigNumber, ethUsdPrice?: BigNumber) => { + return format.ethBaseUnitAmountInUsd(amountInWei, ethUsdPrice, 2, ''); +}; + +export const buyQuoteUtil = { + getWeiAmounts: ( + selectedAssetUnitAmount: BigNumber | undefined, + buyQuoteInfo: BuyQuoteInfo | undefined, + ): BuyQuoteWeiAmounts => { + const buyQuoteAccessor = oc(buyQuoteInfo); + const assetTotalInWei = buyQuoteAccessor.assetEthAmount(); + const pricePerTokenInWei = + !_.isUndefined(assetTotalInWei) && + !_.isUndefined(selectedAssetUnitAmount) && + !selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO) + ? assetTotalInWei.div(selectedAssetUnitAmount).ceil() + : undefined; + + return { + assetTotalInWei, + feeTotalInWei: buyQuoteAccessor.feeEthAmount(), + grandTotalInWei: buyQuoteAccessor.totalEthAmount(), + pricePerTokenInWei, + }; + }, + displayAmountsEth: (weiAmounts: BuyQuoteWeiAmounts, ethUsdPrice?: BigNumber): DisplayAmounts => { + return { + pricePerToken: ethDisplayFormat(weiAmounts.pricePerTokenInWei), + assetTotal: ethDisplayFormat(weiAmounts.assetTotalInWei), + feeTotal: ethDisplayFormat(weiAmounts.feeTotalInWei), + primaryGrandTotal: ethDisplayFormat(weiAmounts.grandTotalInWei), + secondaryGrandTotal: usdDisplayFormat(weiAmounts.grandTotalInWei, ethUsdPrice), + }; + }, + displayAmountsUsd: (weiAmounts: BuyQuoteWeiAmounts, ethUsdPrice?: BigNumber): DisplayAmounts => { + return { + pricePerToken: usdDisplayFormat(weiAmounts.pricePerTokenInWei, ethUsdPrice), + assetTotal: usdDisplayFormat(weiAmounts.assetTotalInWei, ethUsdPrice), + feeTotal: usdDisplayFormat(weiAmounts.feeTotalInWei, ethUsdPrice), + primaryGrandTotal: usdDisplayFormat(weiAmounts.grandTotalInWei, ethUsdPrice), + secondaryGrandTotal: ethDisplayFormat(weiAmounts.grandTotalInWei), + }; + }, +}; -- cgit v1.2.3 From 286474ca767e955b4f7fca985d3864511fa4dfbb Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 11 Dec 2018 14:43:07 -0800 Subject: Remove unused component --- packages/instant/src/components/order_details.tsx | 49 ----------------------- 1 file changed, 49 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 85761a5b9..2fcde5aa4 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -2,9 +2,7 @@ import { BuyQuoteInfo } from '@0x/asset-buyer'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; -import { oc } from 'ts-optchain'; -import { BIG_NUMBER_ZERO } from '../constants'; import { ColorOption } from '../style/theme'; import { BaseCurrency } from '../types'; import { buyQuoteUtil } from '../util/buy_quote'; @@ -204,50 +202,3 @@ export class TokenAmountRow extends React.Component { return TokenAmountRow.DEFAULT_TEXT; } } - -export class EthAmountRow extends React.Component { - public static defaultProps = { - shouldEmphasize: false, - isEthAmountInBaseUnits: true, - }; - public render(): React.ReactNode { - const { rowLabel, ethAmount, isEthAmountInBaseUnits, shouldEmphasize, isLoading } = this.props; - - const fontWeight = shouldEmphasize ? 700 : 400; - const ethFormatter = isEthAmountInBaseUnits ? format.ethBaseUnitAmount : format.ethUnitAmount; - return ( - - - - {rowLabel} - - - {this._renderUsdSection()} - - {ethFormatter( - ethAmount, - 4, - - - , - )} - - - - - ); - } - private _renderUsdSection(): React.ReactNode { - const usdFormatter = this.props.isEthAmountInBaseUnits - ? format.ethBaseUnitAmountInUsd - : format.ethUnitAmountInUsd; - const shouldHideUsdPriceSection = _.isUndefined(this.props.ethUsdPrice) || _.isUndefined(this.props.ethAmount); - return shouldHideUsdPriceSection ? null : ( - - - ({usdFormatter(this.props.ethAmount, this.props.ethUsdPrice)}) - - - ); - } -} -- cgit v1.2.3 From ee7d6fb3afad313fa699018c727c24db4f2024bd Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 11 Dec 2018 14:55:32 -0800 Subject: Show as 0 when selected --- packages/instant/src/components/order_details.tsx | 18 +++++++++++------- packages/instant/src/constants.ts | 1 + packages/instant/src/util/asset.ts | 3 ++- 3 files changed, 14 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 2fcde5aa4..67090898e 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -3,10 +3,10 @@ import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; +import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { ColorOption } from '../style/theme'; import { BaseCurrency } from '../types'; import { buyQuoteUtil } from '../util/buy_quote'; -import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; @@ -171,7 +171,7 @@ export interface TokenAmountRowProps { numTokens?: BigNumber; } export class TokenAmountRow extends React.Component { - public static DEFAULT_TEXT: string = 'Token Price'; + public static DEFAULT_TEXT: string = 'Token Amount'; public render(): React.ReactNode { return ( { ); } private _labelText(): string { - if (this.props.isLoading) { - return TokenAmountRow.DEFAULT_TEXT; - } - const { numTokens, displayPricePerToken, assetName } = this.props; - if (numTokens) { + const { displayPricePerToken, assetName } = this.props; + + // Display as 0 if we have a selected asset + const numTokens = + assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(this.props.numTokens) + ? 0 + : this.props.numTokens; + + if (!_.isUndefined(numTokens)) { let numTokensWithSymbol = numTokens.toString(); if (assetName) { diff --git a/packages/instant/src/constants.ts b/packages/instant/src/constants.ts index f83eb4ac7..975dfcbea 100644 --- a/packages/instant/src/constants.ts +++ b/packages/instant/src/constants.ts @@ -17,6 +17,7 @@ export const ONE_MINUTE_MS = ONE_SECOND_MS * 60; export const GIT_SHA = process.env.GIT_SHA; export const NODE_ENV = process.env.NODE_ENV; export const NPM_PACKAGE_VERSION = process.env.NPM_PACKAGE_VERSION; +export const DEFAULT_UNKOWN_ASSET_NAME = '???'; export const ACCOUNT_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 5; export const BUY_QUOTE_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 15; export const DEFAULT_GAS_PRICE = GWEI_IN_WEI.mul(6); diff --git a/packages/instant/src/util/asset.ts b/packages/instant/src/util/asset.ts index 13f84ef74..faaeb7c22 100644 --- a/packages/instant/src/util/asset.ts +++ b/packages/instant/src/util/asset.ts @@ -2,6 +2,7 @@ import { AssetBuyerError } from '@0x/asset-buyer'; import { AssetProxyId, ObjectMap } from '@0x/types'; import * as _ from 'lodash'; +import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { assetDataNetworkMapping } from '../data/asset_data_network_mapping'; import { Asset, AssetMetaData, ERC20Asset, Network, ZeroExInstantError } from '../types'; @@ -71,7 +72,7 @@ export const assetUtils = { } return metaData; }, - bestNameForAsset: (asset?: Asset, defaultName: string = '???'): string => { + bestNameForAsset: (asset?: Asset, defaultName: string = DEFAULT_UNKOWN_ASSET_NAME): string => { if (_.isUndefined(asset)) { return defaultName; } -- cgit v1.2.3 From b1a73f5c742b96906e832e532dbdc3e4e9583c6d Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 11 Dec 2018 17:03:07 -0800 Subject: Refactor out custom components --- packages/instant/src/components/order_details.tsx | 137 +++++++++------------- 1 file changed, 55 insertions(+), 82 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 67090898e..9eaf2fb2e 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -28,6 +28,49 @@ const BaseCurrencySelector: React.StatelessComponent = p return {props.currencyName}; }; +const grandTotalDisplayValue = ( + displayPrimaryTotalCost?: React.ReactNode, + displaySecondaryTotalCost?: React.ReactNode, +): React.ReactNode => { + if (!displayPrimaryTotalCost) { + return undefined; + } + const secondaryText = displaySecondaryTotalCost && ( + + ({displaySecondaryTotalCost}) + + ); + return ( + + {secondaryText} + + {displayPrimaryTotalCost} + + + ); +}; + +const tokenAmountLabel = (displayPricePerToken?: React.ReactNode, assetName?: string, numTokens?: BigNumber) => { + // Display as 0 if we have a selected asset + const displayNumTokens = + assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(numTokens) ? new BigNumber(0) : numTokens; + + if (!_.isUndefined(displayNumTokens)) { + let numTokensWithSymbol = displayNumTokens.toString(); + + if (assetName) { + numTokensWithSymbol += ` ${assetName}`; + } + + if (displayPricePerToken) { + numTokensWithSymbol += ` @ ${displayPricePerToken}`; + } + return numTokensWithSymbol; + } + + return 'Token Amount'; +}; + export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; selectedAssetUnitAmount?: BigNumber; @@ -79,18 +122,21 @@ export class OrderDetails extends React.Component { - - - + ); @@ -133,76 +179,3 @@ export class OrderDetailsRow extends React.Component { ); } } -export interface TotalCostRowProps { - displayPrimaryTotalCost?: React.ReactNode; - displaySecondaryTotalCost?: React.ReactNode; - isLoading: boolean; -} -export class TotalCostRow extends React.Component { - public render(): React.ReactNode { - let value: React.ReactNode; - if (this.props.displayPrimaryTotalCost) { - const secondaryText = this.props.displaySecondaryTotalCost && ( - - ({this.props.displaySecondaryTotalCost}) - - ); - value = ( - - {secondaryText} - - {this.props.displayPrimaryTotalCost} - - - ); - } - - return ( - - ); - } -} - -export interface TokenAmountRowProps { - assetName?: string; - displayPricePerToken?: React.ReactNode; - displayTotalPrice?: React.ReactNode; - isLoading: boolean; - numTokens?: BigNumber; -} -export class TokenAmountRow extends React.Component { - public static DEFAULT_TEXT: string = 'Token Amount'; - public render(): React.ReactNode { - return ( - - ); - } - private _labelText(): string { - const { displayPricePerToken, assetName } = this.props; - - // Display as 0 if we have a selected asset - const numTokens = - assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(this.props.numTokens) - ? 0 - : this.props.numTokens; - - if (!_.isUndefined(numTokens)) { - let numTokensWithSymbol = numTokens.toString(); - - if (assetName) { - numTokensWithSymbol += ` ${assetName}`; - } - - if (displayPricePerToken) { - numTokensWithSymbol += ` @ ${displayPricePerToken}`; - } - return numTokensWithSymbol; - } - - return TokenAmountRow.DEFAULT_TEXT; - } -} -- cgit v1.2.3 From 5f7ed71937c8319bb5613dc57f005848b1fa336c Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Tue, 11 Dec 2018 17:06:27 -0800 Subject: Delete old interface and rename BaseCurrencyChoice --- packages/instant/src/components/order_details.tsx | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 9eaf2fb2e..8bff0af14 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -14,12 +14,12 @@ import { Container } from './ui/container'; import { Flex } from './ui/flex'; import { Text, TextProps } from './ui/text'; -interface BaseCurrenySwitchProps { +interface BaseCurryChoiceProps { currencyName: string; onClick: () => void; isSelected: boolean; } -const BaseCurrencySelector: React.StatelessComponent = props => { +const BaseCurrencyChoice: React.StatelessComponent = props => { const textStyle: TextProps = { onClick: props.onClick, fontSize: '12px' }; if (props.isSelected) { textStyle.fontColor = ColorOption.primaryColor; @@ -106,7 +106,7 @@ export class OrderDetails extends React.Component { - { / - { } } -export interface EthAmountRowProps { - rowLabel: string; - ethAmount?: BigNumber; - isEthAmountInBaseUnits?: boolean; - ethUsdPrice?: BigNumber; - shouldEmphasize?: boolean; - isLoading: boolean; -} - export interface OrderDetailsRowProps { labelText: string; isLabelBold?: boolean; -- cgit v1.2.3 From 7aacf1f5a457e1166f5188836d30a8c38f3f2c68 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 08:23:40 -0800 Subject: Render OrderDetailsRow directly --- packages/instant/src/components/order_details.tsx | 39 +++++++++++++++++------ packages/instant/src/util/buy_quote.ts | 35 -------------------- 2 files changed, 29 insertions(+), 45 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 8bff0af14..f553351ff 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -7,6 +7,7 @@ import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { ColorOption } from '../style/theme'; import { BaseCurrency } from '../types'; import { buyQuoteUtil } from '../util/buy_quote'; +import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; @@ -71,6 +72,19 @@ const tokenAmountLabel = (displayPricePerToken?: React.ReactNode, assetName?: st return 'Token Amount'; }; +const getDisplayAmount = ( + baseCurrency: BaseCurrency, + weiAmount?: BigNumber, + ethUsdPrice?: BigNumber, +): React.ReactNode => { + switch (baseCurrency) { + case BaseCurrency.USD: + return format.ethBaseUnitAmountInUsd(weiAmount, ethUsdPrice, 2, ''); + case BaseCurrency.ETH: + return format.ethBaseUnitAmount(weiAmount, 4, ''); + } +}; + export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; selectedAssetUnitAmount?: BigNumber; @@ -83,13 +97,14 @@ export interface OrderDetailsProps { } export class OrderDetails extends React.Component { public render(): React.ReactNode { - const { buyQuoteInfo, ethUsdPrice, selectedAssetUnitAmount } = this.props; - const weiAmounts = buyQuoteUtil.getWeiAmounts(selectedAssetUnitAmount, buyQuoteInfo); + const { baseCurrency, buyQuoteInfo, ethUsdPrice, selectedAssetUnitAmount } = this.props; - const displayAmounts = - this.props.baseCurrency === BaseCurrency.USD - ? buyQuoteUtil.displayAmountsUsd(weiAmounts, ethUsdPrice) - : buyQuoteUtil.displayAmountsEth(weiAmounts, ethUsdPrice); + const weiAmounts = buyQuoteUtil.getWeiAmounts(selectedAssetUnitAmount, buyQuoteInfo); + const secondaryCurrency = baseCurrency === BaseCurrency.USD ? BaseCurrency.ETH : BaseCurrency.USD; + const grandTotalValue = grandTotalDisplayValue( + getDisplayAmount(baseCurrency, weiAmounts.grandTotalInWei, ethUsdPrice), + getDisplayAmount(secondaryCurrency, weiAmounts.grandTotalInWei, ethUsdPrice), + ); return ( @@ -125,18 +140,22 @@ export class OrderDetails extends React.Component { + - ); diff --git a/packages/instant/src/util/buy_quote.ts b/packages/instant/src/util/buy_quote.ts index acd4d389c..0e880f51c 100644 --- a/packages/instant/src/util/buy_quote.ts +++ b/packages/instant/src/util/buy_quote.ts @@ -3,18 +3,8 @@ import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import { oc } from 'ts-optchain'; -import { format } from '../util/format'; - import { BIG_NUMBER_ZERO } from '../constants'; -export interface DisplayAmounts { - pricePerToken: React.ReactNode; - assetTotal: React.ReactNode; - feeTotal: React.ReactNode; - primaryGrandTotal: React.ReactNode; - secondaryGrandTotal?: React.ReactNode; -} - export interface BuyQuoteWeiAmounts { assetTotalInWei: BigNumber | undefined; feeTotalInWei: BigNumber | undefined; @@ -22,13 +12,6 @@ export interface BuyQuoteWeiAmounts { pricePerTokenInWei: BigNumber | undefined; } -const ethDisplayFormat = (amountInWei?: BigNumber) => { - return format.ethBaseUnitAmount(amountInWei, 4, ''); -}; -const usdDisplayFormat = (amountInWei?: BigNumber, ethUsdPrice?: BigNumber) => { - return format.ethBaseUnitAmountInUsd(amountInWei, ethUsdPrice, 2, ''); -}; - export const buyQuoteUtil = { getWeiAmounts: ( selectedAssetUnitAmount: BigNumber | undefined, @@ -50,22 +33,4 @@ export const buyQuoteUtil = { pricePerTokenInWei, }; }, - displayAmountsEth: (weiAmounts: BuyQuoteWeiAmounts, ethUsdPrice?: BigNumber): DisplayAmounts => { - return { - pricePerToken: ethDisplayFormat(weiAmounts.pricePerTokenInWei), - assetTotal: ethDisplayFormat(weiAmounts.assetTotalInWei), - feeTotal: ethDisplayFormat(weiAmounts.feeTotalInWei), - primaryGrandTotal: ethDisplayFormat(weiAmounts.grandTotalInWei), - secondaryGrandTotal: usdDisplayFormat(weiAmounts.grandTotalInWei, ethUsdPrice), - }; - }, - displayAmountsUsd: (weiAmounts: BuyQuoteWeiAmounts, ethUsdPrice?: BigNumber): DisplayAmounts => { - return { - pricePerToken: usdDisplayFormat(weiAmounts.pricePerTokenInWei, ethUsdPrice), - assetTotal: usdDisplayFormat(weiAmounts.assetTotalInWei, ethUsdPrice), - feeTotal: usdDisplayFormat(weiAmounts.feeTotalInWei, ethUsdPrice), - primaryGrandTotal: usdDisplayFormat(weiAmounts.grandTotalInWei, ethUsdPrice), - secondaryGrandTotal: ethDisplayFormat(weiAmounts.grandTotalInWei), - }; - }, }; -- cgit v1.2.3 From 8923817b2ff6c8d3cffe6914634e75cdf06db4a9 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 08:25:20 -0800 Subject: Move header to helper --- packages/instant/src/components/order_details.tsx | 62 ++++++++++++----------- 1 file changed, 33 insertions(+), 29 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index f553351ff..5d306f43e 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -108,35 +108,7 @@ export class OrderDetails extends React.Component { return ( - - - - Order Details - - - - - - / - - - - - + {this._renderHeader()} { ); } + + private _renderHeader(): React.ReactNode { + return ( + + + Order Details + + + + + + / + + + + + ); + } } export interface OrderDetailsRowProps { -- cgit v1.2.3 From 3b9e8e669f179aeda7a3aa3f91c78477fa7f08cd Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:14:25 -0800 Subject: Refactor OrderDetails to use private instance methods --- packages/instant/src/components/order_details.tsx | 206 ++++++++++++---------- 1 file changed, 115 insertions(+), 91 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 5d306f43e..7e33e06ac 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -2,11 +2,11 @@ import { BuyQuoteInfo } from '@0x/asset-buyer'; import { BigNumber } from '@0x/utils'; import * as _ from 'lodash'; import * as React from 'react'; +import { oc } from 'ts-optchain'; -import { DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; +import { BIG_NUMBER_ZERO, DEFAULT_UNKOWN_ASSET_NAME } from '../constants'; import { ColorOption } from '../style/theme'; import { BaseCurrency } from '../types'; -import { buyQuoteUtil } from '../util/buy_quote'; import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; @@ -29,62 +29,6 @@ const BaseCurrencyChoice: React.StatelessComponent = props return {props.currencyName}; }; -const grandTotalDisplayValue = ( - displayPrimaryTotalCost?: React.ReactNode, - displaySecondaryTotalCost?: React.ReactNode, -): React.ReactNode => { - if (!displayPrimaryTotalCost) { - return undefined; - } - const secondaryText = displaySecondaryTotalCost && ( - - ({displaySecondaryTotalCost}) - - ); - return ( - - {secondaryText} - - {displayPrimaryTotalCost} - - - ); -}; - -const tokenAmountLabel = (displayPricePerToken?: React.ReactNode, assetName?: string, numTokens?: BigNumber) => { - // Display as 0 if we have a selected asset - const displayNumTokens = - assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(numTokens) ? new BigNumber(0) : numTokens; - - if (!_.isUndefined(displayNumTokens)) { - let numTokensWithSymbol = displayNumTokens.toString(); - - if (assetName) { - numTokensWithSymbol += ` ${assetName}`; - } - - if (displayPricePerToken) { - numTokensWithSymbol += ` @ ${displayPricePerToken}`; - } - return numTokensWithSymbol; - } - - return 'Token Amount'; -}; - -const getDisplayAmount = ( - baseCurrency: BaseCurrency, - weiAmount?: BigNumber, - ethUsdPrice?: BigNumber, -): React.ReactNode => { - switch (baseCurrency) { - case BaseCurrency.USD: - return format.ethBaseUnitAmountInUsd(weiAmount, ethUsdPrice, 2, ''); - case BaseCurrency.ETH: - return format.ethBaseUnitAmount(weiAmount, 4, ''); - } -}; - export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; selectedAssetUnitAmount?: BigNumber; @@ -97,42 +41,107 @@ export interface OrderDetailsProps { } export class OrderDetails extends React.Component { public render(): React.ReactNode { - const { baseCurrency, buyQuoteInfo, ethUsdPrice, selectedAssetUnitAmount } = this.props; - - const weiAmounts = buyQuoteUtil.getWeiAmounts(selectedAssetUnitAmount, buyQuoteInfo); - const secondaryCurrency = baseCurrency === BaseCurrency.USD ? BaseCurrency.ETH : BaseCurrency.USD; - const grandTotalValue = grandTotalDisplayValue( - getDisplayAmount(baseCurrency, weiAmounts.grandTotalInWei, ethUsdPrice), - getDisplayAmount(secondaryCurrency, weiAmounts.grandTotalInWei, ethUsdPrice), - ); + const { baseCurrency, buyQuoteInfo } = this.props; return ( {this._renderHeader()} + ); } + private _totalCostSecondaryValue(): React.ReactNode { + const secondaryCurrency = this.props.baseCurrency === BaseCurrency.USD ? BaseCurrency.ETH : BaseCurrency.USD; + + const canDisplayCurrency = + secondaryCurrency === BaseCurrency.ETH || + (secondaryCurrency === BaseCurrency.USD && + this.props.ethUsdPrice && + this.props.ethUsdPrice.greaterThan(BIG_NUMBER_ZERO)); + + if (this.props.buyQuoteInfo && canDisplayCurrency) { + return this._displayAmount(secondaryCurrency, this.props.buyQuoteInfo.totalEthAmount); + } else { + return undefined; + } + } + + private _displayAmountOrPlaceholder(weiAmount?: BigNumber): React.ReactNode { + const { baseCurrency, ethUsdPrice, isLoading } = this.props; + + if (_.isUndefined(weiAmount)) { + return ( + + + + ); + } + + return this._displayAmount(baseCurrency, weiAmount); + } + + private _displayAmount(currency: BaseCurrency, weiAmount: BigNumber): React.ReactNode { + switch (currency) { + case BaseCurrency.USD: + return format.ethBaseUnitAmountInUsd(weiAmount, this.props.ethUsdPrice, 2, ''); + case BaseCurrency.ETH: + return format.ethBaseUnitAmount(weiAmount, 4, ''); + } + } + + private _assetLabelText(): string { + const { assetName, baseCurrency, ethUsdPrice } = this.props; + const numTokens = this.props.selectedAssetUnitAmount; + + // Display as 0 if we have a selected asset + const displayNumTokens = + assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(numTokens) + ? new BigNumber(0) + : numTokens; + + if (!_.isUndefined(displayNumTokens)) { + let numTokensWithSymbol = displayNumTokens.toString(); + + if (assetName) { + numTokensWithSymbol += ` ${assetName}`; + } + + const pricePerTokenWei = this._pricePerTokenWei(); + if (pricePerTokenWei) { + numTokensWithSymbol += ` @ ${this._displayAmount(baseCurrency, pricePerTokenWei)}`; + } + return numTokensWithSymbol; + } + + return 'Token Amount'; + } + + private _pricePerTokenWei(): BigNumber | undefined { + const buyQuoteAccessor = oc(this.props.buyQuoteInfo); + const assetTotalInWei = buyQuoteAccessor.assetEthAmount(); + const selectedAssetUnitAmount = this.props.selectedAssetUnitAmount; + return !_.isUndefined(assetTotalInWei) && + !_.isUndefined(selectedAssetUnitAmount) && + !selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO) + ? assetTotalInWei.div(selectedAssetUnitAmount).ceil() + : undefined; + } + private _renderHeader(): React.ReactNode { return ( @@ -169,27 +178,42 @@ export class OrderDetails extends React.Component { export interface OrderDetailsRowProps { labelText: string; isLabelBold?: boolean; - isLoading: boolean; - value?: React.ReactNode; + isPrimaryValueBold?: boolean; + primaryValue: React.ReactNode; + secondaryValue?: React.ReactNode; } export class OrderDetailsRow extends React.Component { public render(): React.ReactNode { - const { labelText, value, isLabelBold, isLoading } = this.props; return ( - - {labelText} - - - {value || ( - - - - )} - + {this._renderLabel()} + {this._renderValues()} ); } + + private _renderLabel(): React.ReactNode { + return ( + + {this.props.labelText} + + ); + } + + private _renderValues(): React.ReactNode { + const secondaryValueNode: React.ReactNode = this.props.secondaryValue && ( + + ({this.props.secondaryValue}) + + ); + + return ( + + {secondaryValueNode} + {this.props.primaryValue} + + ); + } } -- cgit v1.2.3 From ad3d20b34267eda1a9b1d397fb15f2bce0eb221f Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:15:18 -0800 Subject: Default to USD --- packages/instant/src/redux/reducer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/instant/src/redux/reducer.ts b/packages/instant/src/redux/reducer.ts index 4e734041f..8c13c9c72 100644 --- a/packages/instant/src/redux/reducer.ts +++ b/packages/instant/src/redux/reducer.ts @@ -66,7 +66,7 @@ export const DEFAULT_STATE: DefaultState = { animationState: 'none', content: StandardSlidingPanelContent.None, }, - baseCurrency: BaseCurrency.ETH, + baseCurrency: BaseCurrency.USD, }; export const createReducer = (initialState: State) => { -- cgit v1.2.3 From aeefdeaef78fad38b84e728cceff2141eb302ab5 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:25:48 -0800 Subject: Remove unused util --- packages/instant/src/util/buy_quote.ts | 36 ---------------------------------- 1 file changed, 36 deletions(-) delete mode 100644 packages/instant/src/util/buy_quote.ts (limited to 'packages') diff --git a/packages/instant/src/util/buy_quote.ts b/packages/instant/src/util/buy_quote.ts deleted file mode 100644 index 0e880f51c..000000000 --- a/packages/instant/src/util/buy_quote.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { BuyQuoteInfo } from '@0x/asset-buyer'; -import { BigNumber } from '@0x/utils'; -import * as _ from 'lodash'; -import { oc } from 'ts-optchain'; - -import { BIG_NUMBER_ZERO } from '../constants'; - -export interface BuyQuoteWeiAmounts { - assetTotalInWei: BigNumber | undefined; - feeTotalInWei: BigNumber | undefined; - grandTotalInWei: BigNumber | undefined; - pricePerTokenInWei: BigNumber | undefined; -} - -export const buyQuoteUtil = { - getWeiAmounts: ( - selectedAssetUnitAmount: BigNumber | undefined, - buyQuoteInfo: BuyQuoteInfo | undefined, - ): BuyQuoteWeiAmounts => { - const buyQuoteAccessor = oc(buyQuoteInfo); - const assetTotalInWei = buyQuoteAccessor.assetEthAmount(); - const pricePerTokenInWei = - !_.isUndefined(assetTotalInWei) && - !_.isUndefined(selectedAssetUnitAmount) && - !selectedAssetUnitAmount.eq(BIG_NUMBER_ZERO) - ? assetTotalInWei.div(selectedAssetUnitAmount).ceil() - : undefined; - - return { - assetTotalInWei, - feeTotalInWei: buyQuoteAccessor.feeEthAmount(), - grandTotalInWei: buyQuoteAccessor.totalEthAmount(), - pricePerTokenInWei, - }; - }, -}; -- cgit v1.2.3 From 8a5609718aa6e31e30915300e38e47d30d528896 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:26:00 -0800 Subject: Refactor BaseCurrencyChoice to be done in helper function --- packages/instant/src/components/order_details.tsx | 46 ++++++++++------------- 1 file changed, 19 insertions(+), 27 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 7e33e06ac..298bd86c9 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -15,20 +15,6 @@ import { Container } from './ui/container'; import { Flex } from './ui/flex'; import { Text, TextProps } from './ui/text'; -interface BaseCurryChoiceProps { - currencyName: string; - onClick: () => void; - isSelected: boolean; -} -const BaseCurrencyChoice: React.StatelessComponent = props => { - const textStyle: TextProps = { onClick: props.onClick, fontSize: '12px' }; - if (props.isSelected) { - textStyle.fontColor = ColorOption.primaryColor; - textStyle.fontWeight = 700; - } - return {props.currencyName}; -}; - export interface OrderDetailsProps { buyQuoteInfo?: BuyQuoteInfo; selectedAssetUnitAmount?: BigNumber; @@ -41,7 +27,7 @@ export interface OrderDetailsProps { } export class OrderDetails extends React.Component { public render(): React.ReactNode { - const { baseCurrency, buyQuoteInfo } = this.props; + const { buyQuoteInfo } = this.props; return ( @@ -82,7 +68,7 @@ export class OrderDetails extends React.Component { } private _displayAmountOrPlaceholder(weiAmount?: BigNumber): React.ReactNode { - const { baseCurrency, ethUsdPrice, isLoading } = this.props; + const { baseCurrency, isLoading } = this.props; if (_.isUndefined(weiAmount)) { return ( @@ -105,7 +91,7 @@ export class OrderDetails extends React.Component { } private _assetLabelText(): string { - const { assetName, baseCurrency, ethUsdPrice } = this.props; + const { assetName, baseCurrency } = this.props; const numTokens = this.props.selectedAssetUnitAmount; // Display as 0 if we have a selected asset @@ -142,6 +128,20 @@ export class OrderDetails extends React.Component { : undefined; } + private _baseCurrencyChoice(choice: BaseCurrency): React.ReactNode { + const onClick = + choice === BaseCurrency.ETH ? this.props.onBaseCurrencySwitchEth : this.props.onBaseCurrencySwitchUsd; + const isSelected = this.props.baseCurrency === choice; + + const textStyle: TextProps = { onClick, fontSize: '12px ' }; + if (isSelected) { + textStyle.fontColor = ColorOption.primaryColor; + textStyle.fontWeight = 700; + } + + return {choice}; + } + private _renderHeader(): React.ReactNode { return ( @@ -156,19 +156,11 @@ export class OrderDetails extends React.Component { - + {this._baseCurrencyChoice(BaseCurrency.ETH)} / - + {this._baseCurrencyChoice(BaseCurrency.USD)} ); -- cgit v1.2.3 From 99941972a3965f27b3607a382a476b79722151fc Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:29:23 -0800 Subject: Small clean up --- packages/instant/src/components/order_details.tsx | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 298bd86c9..cbe36be64 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -34,7 +34,7 @@ export class OrderDetails extends React.Component { {this._renderHeader()} { } } - private _assetLabelText(): string { + private _assetAmountLabel(): string { const { assetName, baseCurrency } = this.props; const numTokens = this.props.selectedAssetUnitAmount; @@ -99,21 +99,17 @@ export class OrderDetails extends React.Component { assetName && assetName !== DEFAULT_UNKOWN_ASSET_NAME && _.isUndefined(numTokens) ? new BigNumber(0) : numTokens; - if (!_.isUndefined(displayNumTokens)) { let numTokensWithSymbol = displayNumTokens.toString(); - if (assetName) { numTokensWithSymbol += ` ${assetName}`; } - const pricePerTokenWei = this._pricePerTokenWei(); if (pricePerTokenWei) { numTokensWithSymbol += ` @ ${this._displayAmount(baseCurrency, pricePerTokenWei)}`; } return numTokensWithSymbol; } - return 'Token Amount'; } @@ -138,7 +134,6 @@ export class OrderDetails extends React.Component { textStyle.fontColor = ColorOption.primaryColor; textStyle.fontWeight = 700; } - return {choice}; } @@ -154,7 +149,6 @@ export class OrderDetails extends React.Component { > Order Details - {this._baseCurrencyChoice(BaseCurrency.ETH)} @@ -179,28 +173,21 @@ export class OrderDetailsRow extends React.Component { return ( - {this._renderLabel()} + + {this.props.labelText} + {this._renderValues()} ); } - private _renderLabel(): React.ReactNode { - return ( - - {this.props.labelText} - - ); - } - private _renderValues(): React.ReactNode { const secondaryValueNode: React.ReactNode = this.props.secondaryValue && ( ({this.props.secondaryValue}) ); - return ( {secondaryValueNode} -- cgit v1.2.3 From aa8dfa88e113ca7a98887ed77dc8eb3ad39e5f41 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:37:39 -0800 Subject: Make primary value in total cost bold --- packages/instant/src/components/order_details.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index cbe36be64..537b9ee4e 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -45,6 +45,7 @@ export class OrderDetails extends React.Component { labelText="Total Cost" isLabelBold={true} primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.totalEthAmount)} + isPrimaryValueBold={true} secondaryValue={this._totalCostSecondaryValue()} /> -- cgit v1.2.3 From fb99b5ce9d3639e1584214828216879f99dfa020 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:51:15 -0800 Subject: Show error when fetching usd prices --- packages/instant/src/components/order_details.tsx | 36 ++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 537b9ee4e..ea975a5f7 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -27,12 +27,19 @@ export interface OrderDetailsProps { } export class OrderDetails extends React.Component { public render(): React.ReactNode { - const { buyQuoteInfo } = this.props; - + const showUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice(); return ( {this._renderHeader()} + {showUsdError ? this._renderErrorFetchingUsdPrice() : this._renderRows()} + + ); + } + private _renderRows(): React.ReactNode { + const { buyQuoteInfo } = this.props; + return ( + { isPrimaryValueBold={true} secondaryValue={this._totalCostSecondaryValue()} /> - + ); } + private _renderErrorFetchingUsdPrice(): React.ReactNode { + return ( + + There was an error fetching the USD price. + + Click here + + {' to view ETH prices'} + + ); + } + + private _hadErrorFetchingUsdPrice(): boolean { + return this.props.ethUsdPrice === BIG_NUMBER_ZERO; + } + private _totalCostSecondaryValue(): React.ReactNode { const secondaryCurrency = this.props.baseCurrency === BaseCurrency.USD ? BaseCurrency.ETH : BaseCurrency.USD; @@ -92,7 +119,8 @@ export class OrderDetails extends React.Component { } private _assetAmountLabel(): string { - const { assetName, baseCurrency } = this.props; + const { assetName, baseCurrency, ethUsdPrice } = this.props; + const numTokens = this.props.selectedAssetUnitAmount; // Display as 0 if we have a selected asset -- cgit v1.2.3 From 0690e68a833e0ca75d31614621aa0278009cc10c Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 09:54:06 -0800 Subject: Change base currency to ETH if we can't get USD price --- packages/instant/src/redux/async_data.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index c67b222d1..8ef95add4 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import { Dispatch } from 'redux'; import { BIG_NUMBER_ZERO } from '../constants'; -import { AccountState, ERC20Asset, OrderProcessState, ProviderState, QuoteFetchOrigin } from '../types'; +import { AccountState, BaseCurrency, ERC20Asset, OrderProcessState, ProviderState, QuoteFetchOrigin } from '../types'; import { analytics } from '../util/analytics'; import { assetUtils } from '../util/asset'; import { buyQuoteUpdater } from '../util/buy_quote_updater'; @@ -24,6 +24,7 @@ export const asyncData = { const errorMessage = 'Error fetching ETH/USD price'; errorFlasher.flashNewErrorMessage(dispatch, errorMessage); dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO)); + dispatch(actions.updateBaseCurrency(BaseCurrency.ETH)); errorReporter.report(e); } }, -- cgit v1.2.3 From 68faaaba5f4278d7a1bd5d02f97921fa3c4238f4 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 10:04:23 -0800 Subject: Analytics events for ETH/USD toggle and failure to fetch eth usd price --- packages/instant/src/components/zero_ex_instant_provider.tsx | 1 + packages/instant/src/redux/analytics_middleware.ts | 3 +++ packages/instant/src/redux/async_data.ts | 1 + packages/instant/src/util/analytics.ts | 9 +++++++++ 4 files changed, 14 insertions(+) (limited to 'packages') diff --git a/packages/instant/src/components/zero_ex_instant_provider.tsx b/packages/instant/src/components/zero_ex_instant_provider.tsx index 204115fa9..2de327cd7 100644 --- a/packages/instant/src/components/zero_ex_instant_provider.tsx +++ b/packages/instant/src/components/zero_ex_instant_provider.tsx @@ -122,6 +122,7 @@ export class ZeroExInstantProvider extends React.Component next => middlewareAction analytics.trackInstallWalletModalClosed(); } break; + case ActionTypes.UPDATE_BASE_CURRENCY: + analytics.trackBaseCurrencyChanged(curState.baseCurrency); + analytics.addEventProperties({ baseCurrency: curState.baseCurrency }); } return nextAction; diff --git a/packages/instant/src/redux/async_data.ts b/packages/instant/src/redux/async_data.ts index 8ef95add4..3961d8821 100644 --- a/packages/instant/src/redux/async_data.ts +++ b/packages/instant/src/redux/async_data.ts @@ -26,6 +26,7 @@ export const asyncData = { dispatch(actions.updateEthUsdPrice(BIG_NUMBER_ZERO)); dispatch(actions.updateBaseCurrency(BaseCurrency.ETH)); errorReporter.report(e); + analytics.trackUsdPriceFailed(); } }, fetchAvailableAssetDatasAndDispatchToStore: async (state: State, dispatch: Dispatch) => { diff --git a/packages/instant/src/util/analytics.ts b/packages/instant/src/util/analytics.ts index e6128f857..6c63907dc 100644 --- a/packages/instant/src/util/analytics.ts +++ b/packages/instant/src/util/analytics.ts @@ -6,6 +6,7 @@ import { GIT_SHA, HEAP_ENABLED, INSTANT_DISCHARGE_TARGET, NODE_ENV, NPM_PACKAGE_ import { AffiliateInfo, Asset, + BaseCurrency, Network, OrderProcessState, OrderSource, @@ -37,6 +38,7 @@ enum EventNames { ACCOUNT_UNLOCK_REQUESTED = 'Account - Unlock Requested', ACCOUNT_UNLOCK_DENIED = 'Account - Unlock Denied', ACCOUNT_ADDRESS_CHANGED = 'Account - Address Changed', + BASE_CURRENCY_CHANGED = 'Base Currency - Changed', PAYMENT_METHOD_DROPDOWN_OPENED = 'Payment Method - Dropdown Opened', PAYMENT_METHOD_OPENED_ETHERSCAN = 'Payment Method - Opened Etherscan', PAYMENT_METHOD_COPIED_ADDRESS = 'Payment Method - Copied Address', @@ -47,6 +49,7 @@ enum EventNames { BUY_TX_SUBMITTED = 'Buy - Tx Submitted', BUY_TX_SUCCEEDED = 'Buy - Tx Succeeded', BUY_TX_FAILED = 'Buy - Tx Failed', + USD_PRICE_FETCH_FAILED = 'USD Price - Fetch Failed', INSTALL_WALLET_CLICKED = 'Install Wallet - Clicked', INSTALL_WALLET_MODAL_OPENED = 'Install Wallet - Modal - Opened', INSTALL_WALLET_MODAL_CLICKED_EXPLANATION = 'Install Wallet - Modal - Clicked Explanation', @@ -118,6 +121,7 @@ export interface AnalyticsEventOptions { selectedAssetSymbol?: string; selectedAssetData?: string; selectedAssetDecimals?: number; + baseCurrency?: string; } export enum TokenSelectorClosedVia { ClickedX = 'Clicked X', @@ -141,6 +145,7 @@ export const analytics = { window: Window, selectedAsset?: Asset, affiliateInfo?: AffiliateInfo, + baseCurrency?: BaseCurrency, ): AnalyticsEventOptions => { const affiliateAddress = affiliateInfo ? affiliateInfo.feeRecipient : 'none'; const affiliateFeePercent = affiliateInfo ? parseFloat(affiliateInfo.feePercentage.toFixed(4)) : 0; @@ -159,6 +164,7 @@ export const analytics = { selectedAssetName: selectedAsset ? selectedAsset.metaData.name : 'none', selectedAssetData: selectedAsset ? selectedAsset.assetData : 'none', instantEnvironment: INSTANT_DISCHARGE_TARGET || `Local ${NODE_ENV}`, + baseCurrency, }; return eventOptions; }, @@ -170,6 +176,8 @@ export const analytics = { trackAccountUnlockDenied: trackingEventFnWithoutPayload(EventNames.ACCOUNT_UNLOCK_DENIED), trackAccountAddressChanged: (address: string) => trackingEventFnWithPayload(EventNames.ACCOUNT_ADDRESS_CHANGED)({ address }), + trackBaseCurrencyChanged: (currencyChangedTo: BaseCurrency) => + trackingEventFnWithPayload(EventNames.BASE_CURRENCY_CHANGED)({ currencyChangedTo }), trackPaymentMethodDropdownOpened: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_DROPDOWN_OPENED), trackPaymentMethodOpenedEtherscan: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_OPENED_ETHERSCAN), trackPaymentMethodCopiedAddress: trackingEventFnWithoutPayload(EventNames.PAYMENT_METHOD_COPIED_ADDRESS), @@ -230,4 +238,5 @@ export const analytics = { fetchOrigin, }); }, + trackUsdPriceFailed: trackingEventFnWithoutPayload(EventNames.USD_PRICE_FETCH_FAILED), }; -- cgit v1.2.3 From 756dc1e95ebbb8ef73ff8f712062997ea3d8c45e Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 10:07:45 -0800 Subject: Linting --- packages/instant/src/components/order_details.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index ea975a5f7..c069a8bcc 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -27,11 +27,11 @@ export interface OrderDetailsProps { } export class OrderDetails extends React.Component { public render(): React.ReactNode { - const showUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice(); + const shouldShowUsdError = this.props.baseCurrency === BaseCurrency.USD && this._hadErrorFetchingUsdPrice(); return ( {this._renderHeader()} - {showUsdError ? this._renderErrorFetchingUsdPrice() : this._renderRows()} + {shouldShowUsdError ? this._renderErrorFetchingUsdPrice() : this._renderRows()} ); } @@ -119,8 +119,7 @@ export class OrderDetails extends React.Component { } private _assetAmountLabel(): string { - const { assetName, baseCurrency, ethUsdPrice } = this.props; - + const { assetName, baseCurrency } = this.props; const numTokens = this.props.selectedAssetUnitAmount; // Display as 0 if we have a selected asset -- cgit v1.2.3 From 65579c023670c09f39b4f7a733d7b703888d9d99 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 13:43:08 -0800 Subject: Abstract SectionHeader and make 12px per Chris's comment and figma design --- packages/instant/src/components/order_details.tsx | 21 +++++++++------------ packages/instant/src/components/payment_method.tsx | 11 ++--------- packages/instant/src/components/section_header.tsx | 22 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 21 deletions(-) create mode 100644 packages/instant/src/components/section_header.tsx (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index c069a8bcc..4aa375017 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -10,6 +10,7 @@ import { BaseCurrency } from '../types'; import { format } from '../util/format'; import { AmountPlaceholder } from './amount_placeholder'; +import { SectionHeader } from './section_header'; import { Container } from './ui/container'; import { Flex } from './ui/flex'; @@ -157,10 +158,12 @@ export class OrderDetails extends React.Component { choice === BaseCurrency.ETH ? this.props.onBaseCurrencySwitchEth : this.props.onBaseCurrencySwitchUsd; const isSelected = this.props.baseCurrency === choice; - const textStyle: TextProps = { onClick, fontSize: '12px ' }; + const textStyle: TextProps = { onClick, fontSize: '12px' }; if (isSelected) { textStyle.fontColor = ColorOption.primaryColor; textStyle.fontWeight = 700; + } else { + textStyle.fontColor = ColorOption.lightGrey; } return {choice}; } @@ -168,19 +171,13 @@ export class OrderDetails extends React.Component { private _renderHeader(): React.ReactNode { return ( - - Order Details - + Order Details {this._baseCurrencyChoice(BaseCurrency.ETH)} - - / + + + / + {this._baseCurrencyChoice(BaseCurrency.USD)} diff --git a/packages/instant/src/components/payment_method.tsx b/packages/instant/src/components/payment_method.tsx index 7c93f1d1c..abadf4bd6 100644 --- a/packages/instant/src/components/payment_method.tsx +++ b/packages/instant/src/components/payment_method.tsx @@ -8,6 +8,7 @@ import { envUtil } from '../util/env'; import { CoinbaseWalletLogo } from './coinbase_wallet_logo'; import { MetaMaskLogo } from './meta_mask_logo'; import { PaymentMethodDropdown } from './payment_method_dropdown'; +import { SectionHeader } from './section_header'; import { Circle } from './ui/circle'; import { Container } from './ui/container'; import { Flex } from './ui/flex'; @@ -29,15 +30,7 @@ export class PaymentMethod extends React.Component { - - {this._renderTitleText()} - + {this._renderTitleText()} {this._renderTitleLabel()} diff --git a/packages/instant/src/components/section_header.tsx b/packages/instant/src/components/section_header.tsx new file mode 100644 index 000000000..a6be3d8af --- /dev/null +++ b/packages/instant/src/components/section_header.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; + +import { ColorOption } from '../style/theme'; + +import { Text } from './ui/text'; + +export interface SectionHeaderProps { + children?: React.ReactNode; +} +export const SectionHeader: React.StatelessComponent<{}> = props => { + return ( + + {props.children} + + ); +}; -- cgit v1.2.3 From 9e5e1f568ba71927cec2255bf779322edb6f7d07 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 14:25:56 -0800 Subject: show as <$0.01 when less than a cent in USD, and also show 1 significant digit if rounded amount is 0 --- packages/instant/src/util/format.ts | 15 ++++++++++++--- packages/instant/test/util/format.test.ts | 10 ++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/util/format.ts b/packages/instant/src/util/format.ts index e9c432b2f..3aaf9a3a7 100644 --- a/packages/instant/src/util/format.ts +++ b/packages/instant/src/util/format.ts @@ -2,7 +2,7 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as _ from 'lodash'; -import { ETH_DECIMALS } from '../constants'; +import { BIG_NUMBER_ZERO, ETH_DECIMALS } from '../constants'; export const format = { ethBaseUnitAmount: ( @@ -25,7 +25,10 @@ export const format = { return defaultText; } const roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces); - return `${roundedAmount} ETH`; + // Sometimes for small ETH amounts (i.e. 0.000045) the amount rounded to 4 decimalPlaces is 0 + // If that is the case, show to 1 significant digit + const displayAmount = roundedAmount.eq(BIG_NUMBER_ZERO) ? ethUnitAmount.toPrecision(1) : roundedAmount; + return `${displayAmount} ETH`; }, ethBaseUnitAmountInUsd: ( ethBaseUnitAmount?: BigNumber, @@ -48,7 +51,13 @@ export const format = { if (_.isUndefined(ethUnitAmount) || _.isUndefined(ethUsdPrice)) { return defaultText; } - return `$${ethUnitAmount.mul(ethUsdPrice).toFixed(decimalPlaces)}`; + const rawUsdPrice = ethUnitAmount.mul(ethUsdPrice); + const roundedUsdPrice = rawUsdPrice.toFixed(decimalPlaces); + if (roundedUsdPrice === '0.00' && rawUsdPrice.gt(BIG_NUMBER_ZERO)) { + return '<$0.01'; + } else { + return `$${roundedUsdPrice}`; + } }, ethAddress: (address: string): string => { return `0x${address.slice(2, 7)}…${address.slice(-5)}`; diff --git a/packages/instant/test/util/format.test.ts b/packages/instant/test/util/format.test.ts index fe0a63e6e..b74d6cdaa 100644 --- a/packages/instant/test/util/format.test.ts +++ b/packages/instant/test/util/format.test.ts @@ -41,6 +41,10 @@ describe('format', () => { it('converts BigNumber(5.3014059295032) to the string `5.301 ETH`', () => { expect(format.ethUnitAmount(BIG_NUMBER_IRRATIONAL)).toBe('5.301 ETH'); }); + it('shows 1 significant digit when rounded amount would be 0', () => { + expect(format.ethUnitAmount(new BigNumber(0.00000045))).toBe('0.0000005 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.00000044))).toBe('0.0000004 ETH'); + }); it('returns defaultText param when ethUnitAmount is not defined', () => { const defaultText = 'defaultText'; expect(format.ethUnitAmount(undefined, 4, defaultText)).toBe(defaultText); @@ -86,6 +90,12 @@ describe('format', () => { it('correctly formats 5.3014059295032 ETH to usd according to some price', () => { expect(format.ethUnitAmountInUsd(BIG_NUMBER_IRRATIONAL, BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('$13.43'); }); + it('correctly formats amount that is less than 1 cent', () => { + expect(format.ethUnitAmountInUsd(new BigNumber(0.000001), BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('<$0.01'); + }); + it('correctly formats exactly 1 cent', () => { + expect(format.ethUnitAmountInUsd(new BigNumber(0.0039), BIG_NUMBER_FAKE_ETH_USD_PRICE)).toBe('$0.01'); + }); it('returns defaultText param when ethUnitAmountInUsd or ethUsdPrice is not defined', () => { const defaultText = 'defaultText'; expect(format.ethUnitAmountInUsd(undefined, undefined, 2, defaultText)).toBe(defaultText); -- cgit v1.2.3 From 2e7d363f2cf76e383bb8723cfb0f2e3c1e4783d8 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 15:46:46 -0800 Subject: Use helper function to check for error --- packages/instant/src/components/order_details.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 4aa375017..6ce19742f 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -85,9 +85,7 @@ export class OrderDetails extends React.Component { const canDisplayCurrency = secondaryCurrency === BaseCurrency.ETH || - (secondaryCurrency === BaseCurrency.USD && - this.props.ethUsdPrice && - this.props.ethUsdPrice.greaterThan(BIG_NUMBER_ZERO)); + (secondaryCurrency === BaseCurrency.USD && this.props.ethUsdPrice && !this._hadErrorFetchingUsdPrice); if (this.props.buyQuoteInfo && canDisplayCurrency) { return this._displayAmount(secondaryCurrency, this.props.buyQuoteInfo.totalEthAmount); -- cgit v1.2.3 From 167a3fbc112120b6a583f3ec3c8fdd70d39df078 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 15:57:28 -0800 Subject: Use BN equals and call function --- packages/instant/src/components/order_details.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 6ce19742f..96bdb7150 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -77,7 +77,7 @@ export class OrderDetails extends React.Component { } private _hadErrorFetchingUsdPrice(): boolean { - return this.props.ethUsdPrice === BIG_NUMBER_ZERO; + return this.props.ethUsdPrice ? this.props.ethUsdPrice.equals(BIG_NUMBER_ZERO) : false; } private _totalCostSecondaryValue(): React.ReactNode { @@ -85,7 +85,7 @@ export class OrderDetails extends React.Component { const canDisplayCurrency = secondaryCurrency === BaseCurrency.ETH || - (secondaryCurrency === BaseCurrency.USD && this.props.ethUsdPrice && !this._hadErrorFetchingUsdPrice); + (secondaryCurrency === BaseCurrency.USD && this.props.ethUsdPrice && !this._hadErrorFetchingUsdPrice()); if (this.props.buyQuoteInfo && canDisplayCurrency) { return this._displayAmount(secondaryCurrency, this.props.buyQuoteInfo.totalEthAmount); -- cgit v1.2.3 From 8d54772389b28dec021aa81423ac84795e132581 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 17:34:14 -0800 Subject: show < 0.00001 ETH when amount gets really small --- packages/instant/src/util/format.ts | 21 ++++++++++++++++----- packages/instant/test/util/format.test.ts | 12 ++++++++++-- 2 files changed, 26 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/util/format.ts b/packages/instant/src/util/format.ts index 3aaf9a3a7..4adb63e21 100644 --- a/packages/instant/src/util/format.ts +++ b/packages/instant/src/util/format.ts @@ -20,14 +20,24 @@ export const format = { ethUnitAmount?: BigNumber, decimalPlaces: number = 4, defaultText: React.ReactNode = '0 ETH', + minUnitAmountToDisplay: BigNumber = new BigNumber('0.00001'), ): React.ReactNode => { if (_.isUndefined(ethUnitAmount)) { return defaultText; } - const roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces); - // Sometimes for small ETH amounts (i.e. 0.000045) the amount rounded to 4 decimalPlaces is 0 - // If that is the case, show to 1 significant digit - const displayAmount = roundedAmount.eq(BIG_NUMBER_ZERO) ? ethUnitAmount.toPrecision(1) : roundedAmount; + let roundedAmount = ethUnitAmount.round(decimalPlaces).toDigits(decimalPlaces); + + if (roundedAmount.eq(BIG_NUMBER_ZERO) && ethUnitAmount.greaterThan(BIG_NUMBER_ZERO)) { + // Sometimes for small ETH amounts (i.e. 0.000045) the amount rounded to 4 decimalPlaces is 0 + // If that is the case, show to 1 significant digit + roundedAmount = new BigNumber(ethUnitAmount.toPrecision(1)); + } + + const displayAmount = + roundedAmount.greaterThan(BIG_NUMBER_ZERO) && roundedAmount.lessThan(minUnitAmountToDisplay) + ? `< ${minUnitAmountToDisplay.toString()}` + : roundedAmount.toString(); + return `${displayAmount} ETH`; }, ethBaseUnitAmountInUsd: ( @@ -35,12 +45,13 @@ export const format = { ethUsdPrice?: BigNumber, decimalPlaces: number = 2, defaultText: React.ReactNode = '$0.00', + minUnitAmountToDisplay: BigNumber = new BigNumber('0.00001'), ): React.ReactNode => { if (_.isUndefined(ethBaseUnitAmount) || _.isUndefined(ethUsdPrice)) { return defaultText; } const ethUnitAmount = Web3Wrapper.toUnitAmount(ethBaseUnitAmount, ETH_DECIMALS); - return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces); + return format.ethUnitAmountInUsd(ethUnitAmount, ethUsdPrice, decimalPlaces, minUnitAmountToDisplay); }, ethUnitAmountInUsd: ( ethUnitAmount?: BigNumber, diff --git a/packages/instant/test/util/format.test.ts b/packages/instant/test/util/format.test.ts index b74d6cdaa..38bf356ec 100644 --- a/packages/instant/test/util/format.test.ts +++ b/packages/instant/test/util/format.test.ts @@ -42,8 +42,16 @@ describe('format', () => { expect(format.ethUnitAmount(BIG_NUMBER_IRRATIONAL)).toBe('5.301 ETH'); }); it('shows 1 significant digit when rounded amount would be 0', () => { - expect(format.ethUnitAmount(new BigNumber(0.00000045))).toBe('0.0000005 ETH'); - expect(format.ethUnitAmount(new BigNumber(0.00000044))).toBe('0.0000004 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.00003))).toBe('0.00003 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.000034))).toBe('0.00003 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.000035))).toBe('0.00004 ETH'); + }); + it('shows < 0.00001 when hits threshold', () => { + expect(format.ethUnitAmount(new BigNumber(0.000011))).toBe('0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.00001))).toBe('0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.000009))).toBe('< 0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0.0000000009))).toBe('< 0.00001 ETH'); + expect(format.ethUnitAmount(new BigNumber(0))).toBe('0 ETH'); }); it('returns defaultText param when ethUnitAmount is not defined', () => { const defaultText = 'defaultText'; -- cgit v1.2.3 From 193cd67d6bfffc35feccd46d5c1b5f23320dc8ce Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Wed, 12 Dec 2018 17:43:27 -0800 Subject: A little bit of scaling logic for not cutting off text --- packages/instant/src/components/instant_heading.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 816cc5c33..99e531574 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -113,20 +113,23 @@ export class InstantHeading extends React.Component { } private readonly _renderEthAmount = (): React.ReactNode => { + const ethAmount = format.ethBaseUnitAmount( + this.props.totalEthBaseUnitAmount, + 4, + , + ); + + const fontSize = typeof ethAmount === 'string' && ethAmount.length >= 13 ? '14px' : '16px'; return ( - {format.ethBaseUnitAmount( - this.props.totalEthBaseUnitAmount, - 4, - , - )} + {ethAmount} ); }; -- cgit v1.2.3 From 687749460d026ae8b16e355c85c70e1e79b63252 Mon Sep 17 00:00:00 2001 From: kao Date: Mon, 26 Nov 2018 17:12:33 -0800 Subject: WIP: OrderWatcher WebSocket Currently incomplete. Main challenge is to figure out how to test a client + server setup in the single-threaded javascript environment. --- packages/order-watcher/package.json | 3 +- .../src/order_watcher/order_watcher_websocket.ts | 184 +++++++++++++++++ .../src/schemas/websocket_utf8_message_schema.ts | 8 + .../test/order_watcher_websocket_test.ts | 226 +++++++++++++++++++++ 4 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 packages/order-watcher/src/order_watcher/order_watcher_websocket.ts create mode 100644 packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts create mode 100644 packages/order-watcher/test/order_watcher_websocket_test.ts (limited to 'packages') diff --git a/packages/order-watcher/package.json b/packages/order-watcher/package.json index 9a51203f4..cfaf5d724 100644 --- a/packages/order-watcher/package.json +++ b/packages/order-watcher/package.json @@ -74,7 +74,8 @@ "ethereum-types": "^1.1.2", "ethereumjs-blockstream": "6.0.0", "ethers": "~4.0.4", - "lodash": "^4.17.5" + "lodash": "^4.17.5", + "websocket": "^1.0.25" }, "publishConfig": { "access": "public" diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts new file mode 100644 index 000000000..84afc4000 --- /dev/null +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts @@ -0,0 +1,184 @@ +import { ContractAddresses } from '@0x/contract-addresses'; +import { SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Provider } from 'ethereum-types'; +import * as http from 'http'; +import * as WebSocket from 'websocket'; + +import { webSocketUtf8MessageSchema } from '../schemas/websocket_utf8_message_schema'; +import { OnOrderStateChangeCallback, OrderWatcherConfig } from '../types'; +import { assert } from '../utils/assert'; + +import { OrderWatcher } from './order_watcher'; + +const DEFAULT_HTTP_PORT = 8080; + +const enum OrderWatcherAction { + // Actions initiated by the user. + getStats = 'getStats', + addOrderAsync = 'addOrderAsync', + removeOrder = 'removeOrder', + // These are spontaneous; they are primarily orderstate changes. + orderWatcherUpdate = 'orderWatcherUpdate', + // `subscribe` and `unsubscribe` are methods of OrderWatcher, but we don't + // need to expose them to the WebSocket server user because the user implicitly + // subscribes and unsubscribes by connecting and disconnecting from the server. +} + +// Users have to create a json object of this format and attach it to +// the data field of their WebSocket message to interact with this server. +interface WebSocketRequestData { + action: OrderWatcherAction; + params: any; +} + +// Users should expect a json object of this format in the data field +// of the WebSocket messages that this server sends out. +interface WebSocketResponseData { + action: OrderWatcherAction; + success: number; + result: any; +} + +// Wraps the OrderWatcher functionality in a WebSocket server. Motivations: +// 1) Users can watch orders via non-typescript programs. +// 2) Better encapsulation so that users can work +export class OrderWatcherWebSocketServer { + public httpServer: http.Server; + public readonly _orderWatcher: OrderWatcher; + private readonly _connectionStore: Set; + private readonly _wsServer: WebSocket.server; + /** + * Instantiate a new web socket server which provides OrderWatcher functionality + * @param provider Web3 provider to use for JSON RPC calls (for OrderWatcher) + * @param networkId NetworkId to watch orders on (for OrderWatcher) + * @param contractAddresses Optional contract addresses. Defaults to known + * addresses based on networkId (for OrderWatcher) + * @param partialConfig Optional configurations (for OrderWatcher) + */ + constructor( + provider: Provider, + networkId: number, + contractAddresses?: ContractAddresses, + partialConfig?: Partial, + ) { + this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); + this._connectionStore = new Set(); + this.httpServer = http.createServer(); + this._wsServer = new WebSocket.server({ + httpServer: this.httpServer, + autoAcceptConnections: false, + }); + + this._wsServer.on('request', (request: any) => { + // Designed for usage pattern where client and server are run on the same + // machine by the same user. As such, no security checks are in place. + const connection: WebSocket.connection = request.accept(null, request.origin); + logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); + connection.on('message', this._messageCallback.bind(this, connection)); + connection.on('close', this._closeCallback.bind(this, connection)); + this._connectionStore.add(connection); + }); + + // Have the WebSocket server subscribe to the OrderWatcher to receive updates. + // These updates are then broadcast to clients in the _connectionStore. + this._orderWatcher.subscribe(this._broadcastCallback); + } + + /** + * Activates the WebSocket server by having its HTTP server start listening. + */ + public listen(): void { + this.httpServer.listen(DEFAULT_HTTP_PORT, () => { + logUtils.log(`${new Date()} [Server] Listening on port ${DEFAULT_HTTP_PORT}`); + }); + } + + public close(): void { + this.httpServer.close(); + } + + private _messageCallback(connection: WebSocket.connection, message: any): void { + assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); + const requestData: WebSocketRequestData = JSON.parse(message.utf8Data); + const responseData = this._routeRequest(requestData); + logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(responseData)}`); + connection.sendUTF(JSON.stringify(responseData)); + } + + private _closeCallback(connection: WebSocket.connection): void { + this._connectionStore.delete(connection); + logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); + } + + private _routeRequest(requestData: WebSocketRequestData): WebSocketResponseData { + const responseData: WebSocketResponseData = { + action: requestData.action, + success: 0, + result: undefined, + }; + + try { + logUtils.log(`${new Date()} [Server] Request received: ${requestData.action}`); + switch (requestData.action) { + case 'addOrderAsync': { + const signedOrder: SignedOrder = this._parseSignedOrder(requestData); + // tslint:disable-next-line:no-floating-promises + this._orderWatcher.addOrderAsync(signedOrder); // Ok to fireNforget + break; + } + case 'removeOrder': { + this._orderWatcher.removeOrder(requestData.params.orderHash); + break; + } + case 'getStats': { + responseData.result = this._orderWatcher.getStats(); + break; + } + default: + throw new Error(`[Server] Invalid request action: ${requestData.action}`); + } + responseData.success = 1; + } catch (err) { + responseData.result = { error: err.toString() }; + } + return responseData; + } + + /** + * Broadcasts OrderState changes to ALL connected clients. At the moment, + * we do not support clients subscribing to only a subset of orders. As such, + * Client B will be notified of changes to an order that Client A added. + */ + private readonly _broadcastCallback: OnOrderStateChangeCallback = (err, orderState) => { + this._connectionStore.forEach((connection: WebSocket.connection) => { + const responseData: WebSocketResponseData = { + action: OrderWatcherAction.orderWatcherUpdate, + success: 1, + result: orderState || err, + }; + connection.sendUTF(JSON.stringify(responseData)); + }); + // tslint:disable-next-line:semicolon + }; // tslint thinks this is a class method, It's actally a property that holds a function. + + /** + * Recover types lost when the payload is stringified. + */ + private readonly _parseSignedOrder = (requestData: WebSocketRequestData) => { + const signedOrder = requestData.params.signedOrder; + const bigNumberFields = [ + 'salt', + 'makerFee', + 'takerFee', + 'makerAssetAmount', + 'takerAssetAmount', + 'expirationTimeSeconds', + ]; + for (const field of bigNumberFields) { + signedOrder[field] = new BigNumber(signedOrder[field]); + } + return signedOrder; + // tslint:disable-next-line:semicolon + }; // tslint thinks this is a class method, It's actally a property that holds a function. +} diff --git a/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts b/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts new file mode 100644 index 000000000..0a0eed407 --- /dev/null +++ b/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts @@ -0,0 +1,8 @@ +export const webSocketUtf8MessageSchema = { + id: '/WebSocketUtf8MessageSchema', + properties: { + utf8Data: { type: 'string' }, + }, + type: 'object', + required: ['utf8Data'], +}; diff --git a/packages/order-watcher/test/order_watcher_websocket_test.ts b/packages/order-watcher/test/order_watcher_websocket_test.ts new file mode 100644 index 000000000..e7b18e44b --- /dev/null +++ b/packages/order-watcher/test/order_watcher_websocket_test.ts @@ -0,0 +1,226 @@ +import { ContractWrappers } from '@0x/contract-wrappers'; +import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { FillScenarios } from '@0x/fill-scenarios'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import 'mocha'; +import * as WebSocket from 'websocket'; + +import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_websocket'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { migrateOnceAsync } from './utils/migrate'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +interface WsMessage { + data: string; +} + +describe.only('OrderWatcherWebSocket', async () => { + let contractWrappers: ContractWrappers; + let wsServer: OrderWatcherWebSocketServer; + let wsClient: WebSocket.w3cwebsocket; + let wsClientTwo: WebSocket.w3cwebsocket; + let fillScenarios: FillScenarios; + let userAddresses: string[]; + let makerAssetData: string; + let takerAssetData: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + let zrxTokenAddress: string; + let signedOrder: SignedOrder; + let orderHash: string; + let addOrderPayload: { action: string; params: { signedOrder: SignedOrder } }; + let removeOrderPayload: { action: string; params: { orderHash: string } }; + const decimals = constants.ZRX_DECIMALS; + const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); + // createFillableSignedOrderAsync is Promise-based, which forces us + // to use Promises instead of the done() callbacks for tests. + // onmessage callback must thus be wrapped as a Promise. + const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => + new Promise(resolve => { + client.onmessage = (msg: WsMessage) => resolve(msg); + }); + + before(async () => { + // Set up constants + const contractAddresses = await migrateOnceAsync(); + await blockchainLifecycle.startAsync(); + const networkId = constants.TESTRPC_NETWORK_ID; + const config = { + networkId, + contractAddresses, + }; + contractWrappers = new ContractWrappers(provider, config); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + zrxTokenAddress = contractAddresses.zrxToken; + [makerAddress, takerAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + contractAddresses.exchange, + contractAddresses.erc20Proxy, + contractAddresses.erc721Proxy, + ); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + orderHash = orderHashUtils.getOrderHashHex(signedOrder); + addOrderPayload = { + action: 'addOrderAsync', + params: { signedOrder }, + }; + removeOrderPayload = { + action: 'removeOrder', + params: { orderHash }, + }; + + // Prepare OrderWatcher WebSocket server + const orderWatcherConfig = {}; + wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); + wsServer.listen(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + wsServer.close(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + wsClient.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); + + it('responds to getStats requests correctly', (done: any) => { + const payload = { + action: 'getStats', + params: {}, + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); + wsClient.onmessage = (msg: any) => { + const responseData = JSON.parse(msg.data); + expect(responseData.action).to.be.eq('getStats'); + expect(responseData.success).to.be.eq(1); + expect(responseData.result.orderCount).to.be.eq(0); + done(); + }; + }); + + it('throws an error when an invalid action is attempted', async () => { + const invalidActionPayload = { + action: 'badAction', + params: {}, + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidActionPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + expect(errorData.action).to.be.eq('badAction'); + expect(errorData.success).to.be.eq(0); + expect(errorData.result.error).to.be.eq('Error: [Server] Invalid request action: badAction'); + }); + + it('executes addOrderAsync and removeOrder requests correctly', async () => { + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + const addOrderMsg = await _onMessageAsync(wsClient); + const addOrderData = JSON.parse(addOrderMsg.data); + expect(addOrderData.action).to.be.eq('addOrderAsync'); + expect(addOrderData.success).to.be.eq(1); + expect((wsServer._orderWatcher as any)._orderByOrderHash).to.deep.include({ + [orderHash]: signedOrder, + }); + + wsClient.send(JSON.stringify(removeOrderPayload)); + const removeOrderMsg = await _onMessageAsync(wsClient); + const removeOrderData = JSON.parse(removeOrderMsg.data); + expect(removeOrderData.action).to.be.eq('removeOrder'); + expect(removeOrderData.success).to.be.eq(1); + expect((wsServer._orderWatcher as any)._orderByOrderHash).to.not.deep.include({ + [orderHash]: signedOrder, + }); + }); + + it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { + // Add the regular order + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + await _onMessageAsync(wsClient); + + // Set the allowance to 0 + await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); + + // Ensure that orderStateInvalid message is received. + const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); + const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); + expect(orderWatcherUpdateData.action).to.be.eq('orderWatcherUpdate'); + expect(orderWatcherUpdateData.success).to.be.eq(1); + const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; + expect(invalidOrderState.isValid).to.be.false(); + expect(invalidOrderState.orderHash).to.be.eq(orderHash); + expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance); + }); + + it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => { + // Prepare order + const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); + const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerAssetData, + takerAssetData, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + takerAddress, + ); + const nonZeroMakerFeeOrderPayload = { + action: 'addOrderAsync', + params: { nonZeroMakerFeeSignedOrder }, + }; + + // Set up a second client and have it add the order + wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); + await _onMessageAsync(wsClientTwo); + + // Change the allowance + await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); + + // Check that both clients receive the emitted event + for (const client of [wsClient, wsClientTwo]) { + const updateMsg = await _onMessageAsync(client); + const updateData = JSON.parse(updateMsg.data); + const orderState = updateData.result as OrderStateValid; + expect(orderState.isValid).to.be.true(); + expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); + } + + wsClientTwo.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); +}); -- cgit v1.2.3 From 9a1d2c055e176414ee1e7661d5acc764cc9a745d Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 13 Dec 2018 08:31:40 -0800 Subject: Fix SectionHeaderProps --- packages/instant/src/components/section_header.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/section_header.tsx b/packages/instant/src/components/section_header.tsx index a6be3d8af..d0974ebdc 100644 --- a/packages/instant/src/components/section_header.tsx +++ b/packages/instant/src/components/section_header.tsx @@ -4,10 +4,8 @@ import { ColorOption } from '../style/theme'; import { Text } from './ui/text'; -export interface SectionHeaderProps { - children?: React.ReactNode; -} -export const SectionHeader: React.StatelessComponent<{}> = props => { +export interface SectionHeaderProps {} +export const SectionHeader: React.StatelessComponent = props => { return ( Date: Thu, 13 Dec 2018 11:00:54 -0800 Subject: fix(website): update copy of instant track in dev home --- packages/website/translations/english.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/website/translations/english.json b/packages/website/translations/english.json index 8f882f506..2914ffead 100644 --- a/packages/website/translations/english.json +++ b/packages/website/translations/english.json @@ -92,9 +92,9 @@ "ORDER_BASICS_DESCRIPTION": "Tutorial on how to create, validate and fill an order using 0x", "USE_NETWORKED_LIQUIDITY": "use networked liquidity", "USE_NETWORKED_LIQUIDITY_DESCRIPTION": "Learn how to tap into networked liquidity using the Standard Relayer API", - "INTEGRATE_0X_INSTANT": "integrate 0x Instant and AssetBuyer", + "INTEGRATE_0X_INSTANT": "add seamless purchasing of crypto assets to your website or app", "INTEGRATE_0X_INSTANT_DESCRIPTION": - "Add 0x Instant to your app or website, or use AssetBuyer to programmatically make market buys", + "learn how to use 0x Instant or AssetBuyer to give your users the power of purchasing crypto assets using 0x", "VIEW_ALL_DOCUMENTATION": "view all documentation", "SANDBOX": "0x.js sandbox", "GITHUB": "github", -- cgit v1.2.3 From 33c6e40b7026858be52f804c726b9a1ef41647d3 Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 13 Dec 2018 12:45:38 -0800 Subject: simplify scaling input logic --- .../src/components/scaling_amount_input.tsx | 1 + packages/instant/src/components/scaling_input.tsx | 65 ++++++---------------- packages/instant/src/components/ui/input.tsx | 2 +- 3 files changed, 18 insertions(+), 50 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/scaling_amount_input.tsx b/packages/instant/src/components/scaling_amount_input.tsx index 86aca5a65..4feb0502d 100644 --- a/packages/instant/src/components/scaling_amount_input.tsx +++ b/packages/instant/src/components/scaling_amount_input.tsx @@ -58,6 +58,7 @@ export class ScalingAmountInput extends React.Component { +export class ScalingInput extends React.Component { public static defaultProps = { onChange: util.boundNoop, onFontSizeChange: util.boundNoop, @@ -54,9 +49,6 @@ export class ScalingInput extends React.Component(); public static getPhase(textLengthThreshold: number, value: string): ScalingInputPhase { if (value.length <= textLengthThreshold) { @@ -93,36 +85,15 @@ export class ScalingInput extends React.Component { return ScalingInput.calculateFontSizeFromProps(this.props, phase); @@ -178,4 +137,12 @@ export class ScalingInput extends React.Component): void => { + const value = event.target.value; + const { maxLength } = this.props; + if (!_.isUndefined(value) && !_.isUndefined(maxLength) && value.length > maxLength) { + return; + } + this.props.onChange(event); + }; } diff --git a/packages/instant/src/components/ui/input.tsx b/packages/instant/src/components/ui/input.tsx index 1e476bed3..697bfaf0f 100644 --- a/packages/instant/src/components/ui/input.tsx +++ b/packages/instant/src/components/ui/input.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { ColorOption, styled } from '../../style/theme'; -export interface InputProps { +export interface InputProps extends React.HTMLAttributes { tabIndex?: number; className?: string; value?: string; -- cgit v1.2.3 From 9cd859a68a94e90eb9891c5b9a4ec7518b7fb64d Mon Sep 17 00:00:00 2001 From: fragosti Date: Thu, 13 Dec 2018 13:03:45 -0800 Subject: run linter --- packages/instant/src/components/scaling_input.tsx | 39 ++++++++++++++++------- packages/instant/src/components/ui/input.tsx | 6 ++-- 2 files changed, 31 insertions(+), 14 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/scaling_input.tsx b/packages/instant/src/components/scaling_input.tsx index 78c83115a..00aea37da 100644 --- a/packages/instant/src/components/scaling_input.tsx +++ b/packages/instant/src/components/scaling_input.tsx @@ -1,3 +1,4 @@ +import { ObjectMap } from '@0x/types'; import * as _ from 'lodash'; import * as React from 'react'; @@ -13,6 +14,11 @@ export enum ScalingInputPhase { export interface ScalingSettings { percentageToReduceFontSizePerCharacter: number; + // 1ch = the width of the 0 chararacter. + // Allow to customize 'char' length for different characters. + characterWidthOverrides: ObjectMap; + // How much room to leave to the right of the scaling input. + additionalInputSpaceInCh: number; } export interface ScalingInputProps { @@ -38,13 +44,18 @@ export interface ScalingInputSnapshot { // These are magic numbers that were determined experimentally. const defaultScalingSettings: ScalingSettings = { percentageToReduceFontSizePerCharacter: 0.1, + characterWidthOverrides: { + '1': 0.7, + '.': 0.4, + }, + additionalInputSpaceInCh: 0.4, }; export class ScalingInput extends React.Component { public static defaultProps = { onChange: util.boundNoop, onFontSizeChange: util.boundNoop, - maxLength: 7, + maxLength: 9, scalingSettings: defaultScalingSettings, isDisabled: false, hasAutofocus: false, @@ -102,7 +113,7 @@ export class ScalingInput extends React.Component { } } public render(): React.ReactNode { - const { type, hasAutofocus, isDisabled, fontColor, onChange, placeholder, value, maxLength } = this.props; + const { type, hasAutofocus, isDisabled, fontColor, placeholder, value, maxLength } = this.props; const phase = ScalingInput.getPhaseFromProps(this.props); return ( { ); } private readonly _calculateWidth = (phase: ScalingInputPhase): string => { - const { value, textLengthThreshold, scalingSettings } = this.props; + const { value, scalingSettings } = this.props; if (_.isEmpty(value)) { return `${this.props.emptyInputWidthCh}ch`; } - return `${value.length}ch`; + const lengthInCh = _.reduce( + value.split(''), + (sum, char) => { + const widthOverride = scalingSettings.characterWidthOverrides[char]; + if (!_.isUndefined(widthOverride)) { + // tslint is confused + // tslint:disable-next-line:restrict-plus-operands + return sum + widthOverride; + } + return sum + 1; + }, + scalingSettings.additionalInputSpaceInCh, + ); + return `${lengthInCh}ch`; }; private readonly _calculateFontSize = (phase: ScalingInputPhase): number => { return ScalingInput.calculateFontSizeFromProps(this.props, phase); }; - private readonly _getInputWidthInPx = (): number => { - const ref = this._inputRef.current; - if (!ref) { - return 0; - } - return ref.getBoundingClientRect().width; - }; private readonly _handleChange = (event: React.ChangeEvent): void => { const value = event.target.value; const { maxLength } = this.props; diff --git a/packages/instant/src/components/ui/input.tsx b/packages/instant/src/components/ui/input.tsx index 697bfaf0f..53c43ea0b 100644 --- a/packages/instant/src/components/ui/input.tsx +++ b/packages/instant/src/components/ui/input.tsx @@ -32,9 +32,9 @@ export const Input = color: ${props => props.theme[props.fontColor || 'white']} !important; opacity: 0.5 !important; } - &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; + &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; } } `; -- cgit v1.2.3 From 8906c30f37d82fdaafc084ecee2e3206df8f6e38 Mon Sep 17 00:00:00 2001 From: Xianny <8582774+xianny@users.noreply.github.com> Date: Thu, 13 Dec 2018 13:13:28 -0800 Subject: add special case to scrape OHLCV for eth/usd (#1428) --- .../pipeline/src/utils/get_ohlcv_trading_pairs.ts | 44 +++++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/pipeline/src/utils/get_ohlcv_trading_pairs.ts b/packages/pipeline/src/utils/get_ohlcv_trading_pairs.ts index 9d3ef2fba..19f81344e 100644 --- a/packages/pipeline/src/utils/get_ohlcv_trading_pairs.ts +++ b/packages/pipeline/src/utils/get_ohlcv_trading_pairs.ts @@ -23,6 +23,18 @@ interface CryptoCompareCoin { const TO_CURRENCIES = ['USD', 'EUR', 'ETH', 'USDT']; const ETHEREUM_IDENTIFIER = '7605'; const HTTP_OK_STATUS = 200; + +interface StaticPair { + fromSymbol: string; + toSymbol: string; +} +const SPECIAL_CASES: StaticPair[] = [ + { + fromSymbol: 'ETH', + toSymbol: 'USD', + }, +]; + /** * Get trading pairs with latest scraped time for OHLCV records * @param conn a typeorm Connection to postgres @@ -44,6 +56,7 @@ export async function fetchOHLCVTradingPairsAsync( FROM raw.ohlcv_external GROUP BY from_symbol, to_symbol;`); + // build addressable index: { fromsym: { tosym: time }} const latestTradingPairsIndex: { [fromSym: string]: { [toSym: string]: number } } = {}; latestTradingPairs.forEach(pair => { const latestIndex: { [toSym: string]: number } = latestTradingPairsIndex[pair.from_symbol] || {}; @@ -51,6 +64,13 @@ export async function fetchOHLCVTradingPairsAsync( latestTradingPairsIndex[pair.from_symbol] = latestIndex; }); + // match time to special cases + const specialCases: TradingPair[] = SPECIAL_CASES.map(pair => { + const latestSavedTime = + R.path([pair.fromSymbol, pair.toSymbol], latestTradingPairsIndex) || earliestBackfillTime; + return R.assoc('latestSavedTime', latestSavedTime, pair); + }); + // get token symbols used by Crypto Compare const allCoinsResp = await fetchAsync(COINLIST_API); if (allCoinsResp.status !== HTTP_OK_STATUS) { @@ -66,27 +86,31 @@ export async function fetchOHLCVTradingPairsAsync( }); // fetch all tokens that are traded on 0x - const rawTokenAddresses: Array<{ tokenaddress: string }> = await conn.query( + const rawEventTokenAddresses: Array<{ tokenaddress: string }> = await conn.query( `SELECT DISTINCT(maker_token_address) as tokenaddress FROM raw.exchange_fill_events UNION SELECT DISTINCT(taker_token_address) as tokenaddress FROM raw.exchange_fill_events`, ); - const tokenAddresses = R.pluck('tokenaddress', rawTokenAddresses); + + // tslint:disable-next-line:no-unbound-method + const eventTokenAddresses = R.pluck('tokenaddress', rawEventTokenAddresses).map(R.toLower); // join token addresses with CC symbols - const allTokenSymbols: string[] = tokenAddresses - .map(tokenAddress => erc20CoinsIndex.get(tokenAddress.toLowerCase()) || '') - .filter(x => x); + const eventTokenSymbols: string[] = eventTokenAddresses + .filter(tokenAddress => erc20CoinsIndex.has(tokenAddress)) + .map(tokenAddress => erc20CoinsIndex.get(tokenAddress) as string); - // generate list of all tokens with time of latest existing record OR default earliest time - const allTradingPairCombinations: TradingPair[] = R.chain(sym => { + // join traded tokens with fiat and latest backfill time + const eventTradingPairs: TradingPair[] = R.chain(sym => { return TO_CURRENCIES.map(fiat => { - return { + const pair = { fromSymbol: sym, toSymbol: fiat, latestSavedTime: R.path([sym, fiat], latestTradingPairsIndex) || earliestBackfillTime, }; + return pair; }); - }, allTokenSymbols); + }, eventTokenSymbols); - return allTradingPairCombinations; + // join with special cases + return R.concat(eventTradingPairs, specialCases); } -- cgit v1.2.3 From 9697d66b66a9c8ae9f350aae8cf0d6b951a3c96b Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 13 Dec 2018 13:31:30 -0800 Subject: typeof -> isString --- packages/instant/src/components/instant_heading.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/instant/src/components/instant_heading.tsx b/packages/instant/src/components/instant_heading.tsx index 99e531574..5b1f9592d 100644 --- a/packages/instant/src/components/instant_heading.tsx +++ b/packages/instant/src/components/instant_heading.tsx @@ -119,7 +119,7 @@ export class InstantHeading extends React.Component { , ); - const fontSize = typeof ethAmount === 'string' && ethAmount.length >= 13 ? '14px' : '16px'; + const fontSize = _.isString(ethAmount) && ethAmount.length >= 13 ? '14px' : '16px'; return ( Date: Thu, 13 Dec 2018 14:04:56 -0800 Subject: Updated CHANGELOGS --- packages/0x.js/CHANGELOG.json | 9 +++++++++ packages/0x.js/CHANGELOG.md | 4 ++++ packages/abi-gen-wrappers/CHANGELOG.json | 9 +++++++++ packages/abi-gen-wrappers/CHANGELOG.md | 4 ++++ packages/abi-gen/CHANGELOG.json | 9 +++++++++ packages/abi-gen/CHANGELOG.md | 4 ++++ packages/assert/CHANGELOG.json | 9 +++++++++ packages/assert/CHANGELOG.md | 4 ++++ packages/asset-buyer/CHANGELOG.json | 9 +++++++++ packages/asset-buyer/CHANGELOG.md | 4 ++++ packages/base-contract/CHANGELOG.json | 9 +++++++++ packages/base-contract/CHANGELOG.md | 4 ++++ packages/connect/CHANGELOG.json | 9 +++++++++ packages/connect/CHANGELOG.md | 4 ++++ packages/contract-wrappers/CHANGELOG.json | 9 +++++++++ packages/contract-wrappers/CHANGELOG.md | 4 ++++ packages/dev-utils/CHANGELOG.json | 9 +++++++++ packages/dev-utils/CHANGELOG.md | 4 ++++ packages/ethereum-types/CHANGELOG.json | 9 +++++++++ packages/ethereum-types/CHANGELOG.md | 4 ++++ packages/fill-scenarios/CHANGELOG.json | 9 +++++++++ packages/fill-scenarios/CHANGELOG.md | 4 ++++ packages/json-schemas/CHANGELOG.json | 9 +++++++++ packages/json-schemas/CHANGELOG.md | 4 ++++ packages/migrations/CHANGELOG.json | 9 +++++++++ packages/migrations/CHANGELOG.md | 4 ++++ packages/order-utils/CHANGELOG.json | 9 +++++++++ packages/order-utils/CHANGELOG.md | 4 ++++ packages/order-watcher/CHANGELOG.json | 9 +++++++++ packages/order-watcher/CHANGELOG.md | 4 ++++ packages/react-docs/CHANGELOG.json | 9 +++++++++ packages/react-docs/CHANGELOG.md | 4 ++++ packages/react-shared/CHANGELOG.json | 9 +++++++++ packages/react-shared/CHANGELOG.md | 4 ++++ packages/sol-compiler/CHANGELOG.json | 9 +++++++++ packages/sol-compiler/CHANGELOG.md | 4 ++++ packages/sol-cov/CHANGELOG.json | 9 +++++++++ packages/sol-cov/CHANGELOG.md | 4 ++++ packages/sol-doc/CHANGELOG.json | 9 +++++++++ packages/sol-doc/CHANGELOG.md | 4 ++++ packages/sol-resolver/CHANGELOG.json | 9 +++++++++ packages/sol-resolver/CHANGELOG.md | 4 ++++ packages/sra-spec/CHANGELOG.json | 9 +++++++++ packages/sra-spec/CHANGELOG.md | 4 ++++ packages/subproviders/CHANGELOG.json | 9 +++++++++ packages/subproviders/CHANGELOG.md | 4 ++++ packages/tslint-config/CHANGELOG.json | 3 ++- packages/tslint-config/CHANGELOG.md | 4 ++++ packages/types/CHANGELOG.json | 9 +++++++++ packages/types/CHANGELOG.md | 4 ++++ packages/typescript-typings/CHANGELOG.json | 9 +++++++++ packages/typescript-typings/CHANGELOG.md | 4 ++++ packages/utils/CHANGELOG.json | 9 +++++++++ packages/utils/CHANGELOG.md | 4 ++++ packages/web3-wrapper/CHANGELOG.json | 9 +++++++++ packages/web3-wrapper/CHANGELOG.md | 4 ++++ 56 files changed, 357 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json index 096b29c78..03abcd29b 100644 --- a/packages/0x.js/CHANGELOG.json +++ b/packages/0x.js/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "2.0.8", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "2.0.7", "changes": [ diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md index 226b4919a..2923fdf03 100644 --- a/packages/0x.js/CHANGELOG.md +++ b/packages/0x.js/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.8 - _December 13, 2018_ + + * Dependencies updated + ## v2.0.7 - _December 11, 2018_ * Dependencies updated diff --git a/packages/abi-gen-wrappers/CHANGELOG.json b/packages/abi-gen-wrappers/CHANGELOG.json index 41e35b712..13936117b 100644 --- a/packages/abi-gen-wrappers/CHANGELOG.json +++ b/packages/abi-gen-wrappers/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "2.0.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "2.0.1", "changes": [ diff --git a/packages/abi-gen-wrappers/CHANGELOG.md b/packages/abi-gen-wrappers/CHANGELOG.md index 1209a059b..c13c7a60f 100644 --- a/packages/abi-gen-wrappers/CHANGELOG.md +++ b/packages/abi-gen-wrappers/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.2 - _December 13, 2018_ + + * Dependencies updated + ## v2.0.1 - _December 11, 2018_ * Dependencies updated diff --git a/packages/abi-gen/CHANGELOG.json b/packages/abi-gen/CHANGELOG.json index 9142d86d0..9a0848f7e 100644 --- a/packages/abi-gen/CHANGELOG.json +++ b/packages/abi-gen/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.0.19", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.0.18", "changes": [ diff --git a/packages/abi-gen/CHANGELOG.md b/packages/abi-gen/CHANGELOG.md index 4edc82bd3..4ca19ad7e 100644 --- a/packages/abi-gen/CHANGELOG.md +++ b/packages/abi-gen/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.19 - _December 13, 2018_ + + * Dependencies updated + ## v1.0.18 - _December 11, 2018_ * Dependencies updated diff --git a/packages/assert/CHANGELOG.json b/packages/assert/CHANGELOG.json index bedde7738..dcbbc6cbb 100644 --- a/packages/assert/CHANGELOG.json +++ b/packages/assert/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.0.20", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.0.19", "changes": [ diff --git a/packages/assert/CHANGELOG.md b/packages/assert/CHANGELOG.md index bef49229b..ef84c48d4 100644 --- a/packages/assert/CHANGELOG.md +++ b/packages/assert/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.20 - _December 13, 2018_ + + * Dependencies updated + ## v1.0.19 - _December 11, 2018_ * Dependencies updated diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index 1a304f4a3..f875aa66b 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "3.0.4", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "3.0.3", "changes": [ diff --git a/packages/asset-buyer/CHANGELOG.md b/packages/asset-buyer/CHANGELOG.md index cc64f295b..cadb1acf8 100644 --- a/packages/asset-buyer/CHANGELOG.md +++ b/packages/asset-buyer/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.4 - _December 13, 2018_ + + * Dependencies updated + ## v3.0.3 - _December 11, 2018_ * Update SRA order provider to include Dai diff --git a/packages/base-contract/CHANGELOG.json b/packages/base-contract/CHANGELOG.json index fc3f065d9..6ecd235e5 100644 --- a/packages/base-contract/CHANGELOG.json +++ b/packages/base-contract/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "3.0.10", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "3.0.9", "changes": [ diff --git a/packages/base-contract/CHANGELOG.md b/packages/base-contract/CHANGELOG.md index 65888d9ab..a8ce346d7 100644 --- a/packages/base-contract/CHANGELOG.md +++ b/packages/base-contract/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.10 - _December 13, 2018_ + + * Dependencies updated + ## v3.0.9 - _December 11, 2018_ * Dependencies updated diff --git a/packages/connect/CHANGELOG.json b/packages/connect/CHANGELOG.json index f9d0f51cc..11ed171c8 100644 --- a/packages/connect/CHANGELOG.json +++ b/packages/connect/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "3.0.10", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "3.0.9", "changes": [ diff --git a/packages/connect/CHANGELOG.md b/packages/connect/CHANGELOG.md index befd0572c..a5f95b988 100644 --- a/packages/connect/CHANGELOG.md +++ b/packages/connect/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.10 - _December 13, 2018_ + + * Dependencies updated + ## v3.0.9 - _December 11, 2018_ * Dependencies updated diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index e0f5c8ded..942484b8c 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "4.1.3", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "4.1.2", "changes": [ diff --git a/packages/contract-wrappers/CHANGELOG.md b/packages/contract-wrappers/CHANGELOG.md index 3716db927..595fbcc31 100644 --- a/packages/contract-wrappers/CHANGELOG.md +++ b/packages/contract-wrappers/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v4.1.3 - _December 13, 2018_ + + * Dependencies updated + ## v4.1.2 - _December 11, 2018_ * Dependencies updated diff --git a/packages/dev-utils/CHANGELOG.json b/packages/dev-utils/CHANGELOG.json index 940a59ea6..c829cad0b 100644 --- a/packages/dev-utils/CHANGELOG.json +++ b/packages/dev-utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.0.21", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.0.20", "changes": [ diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index f2484854b..9a55e4e58 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.21 - _December 13, 2018_ + + * Dependencies updated + ## v1.0.20 - _December 11, 2018_ * Dependencies updated diff --git a/packages/ethereum-types/CHANGELOG.json b/packages/ethereum-types/CHANGELOG.json index 6ce8feb1e..66882f5fd 100644 --- a/packages/ethereum-types/CHANGELOG.json +++ b/packages/ethereum-types/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.1.4", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.1.3", "changes": [ diff --git a/packages/ethereum-types/CHANGELOG.md b/packages/ethereum-types/CHANGELOG.md index 07cf8e042..1d27757f3 100644 --- a/packages/ethereum-types/CHANGELOG.md +++ b/packages/ethereum-types/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.1.4 - _December 13, 2018_ + + * Dependencies updated + ## v1.1.3 - _December 11, 2018_ * Dependencies updated diff --git a/packages/fill-scenarios/CHANGELOG.json b/packages/fill-scenarios/CHANGELOG.json index af44a13d5..50404fc5a 100644 --- a/packages/fill-scenarios/CHANGELOG.json +++ b/packages/fill-scenarios/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.0.16", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.0.15", "changes": [ diff --git a/packages/fill-scenarios/CHANGELOG.md b/packages/fill-scenarios/CHANGELOG.md index f85f5ceed..e9b88547d 100644 --- a/packages/fill-scenarios/CHANGELOG.md +++ b/packages/fill-scenarios/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.16 - _December 13, 2018_ + + * Dependencies updated + ## v1.0.15 - _December 11, 2018_ * Dependencies updated diff --git a/packages/json-schemas/CHANGELOG.json b/packages/json-schemas/CHANGELOG.json index 07649aa60..519026266 100644 --- a/packages/json-schemas/CHANGELOG.json +++ b/packages/json-schemas/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "2.1.4", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "2.1.3", "changes": [ diff --git a/packages/json-schemas/CHANGELOG.md b/packages/json-schemas/CHANGELOG.md index cb5d315c2..7e407bdda 100644 --- a/packages/json-schemas/CHANGELOG.md +++ b/packages/json-schemas/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.1.4 - _December 13, 2018_ + + * Dependencies updated + ## v2.1.3 - _December 11, 2018_ * Dependencies updated diff --git a/packages/migrations/CHANGELOG.json b/packages/migrations/CHANGELOG.json index 8e109414d..3aad8ab5f 100644 --- a/packages/migrations/CHANGELOG.json +++ b/packages/migrations/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "2.2.2", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "2.2.1", "changes": [ diff --git a/packages/migrations/CHANGELOG.md b/packages/migrations/CHANGELOG.md index 7ea114fa1..0b7b9a364 100644 --- a/packages/migrations/CHANGELOG.md +++ b/packages/migrations/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.2.2 - _December 13, 2018_ + + * Dependencies updated + ## v2.2.1 - _December 11, 2018_ * Dependencies updated diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index d974028be..30ea9e823 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "3.0.7", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "3.0.6", "changes": [ diff --git a/packages/order-utils/CHANGELOG.md b/packages/order-utils/CHANGELOG.md index 7b7334db8..bc8b48767 100644 --- a/packages/order-utils/CHANGELOG.md +++ b/packages/order-utils/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.7 - _December 13, 2018_ + + * Dependencies updated + ## v3.0.6 - _December 11, 2018_ * Fix bug in wallet signature type verification (#1414) diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index 9730e68b7..4c6f7b1d6 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "2.2.8", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "2.2.7", "changes": [ diff --git a/packages/order-watcher/CHANGELOG.md b/packages/order-watcher/CHANGELOG.md index 30b4516cf..4e49b4637 100644 --- a/packages/order-watcher/CHANGELOG.md +++ b/packages/order-watcher/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.2.8 - _December 13, 2018_ + + * Dependencies updated + ## v2.2.7 - _December 11, 2018_ * Dependencies updated diff --git a/packages/react-docs/CHANGELOG.json b/packages/react-docs/CHANGELOG.json index 51d3be8ec..5798764d5 100644 --- a/packages/react-docs/CHANGELOG.json +++ b/packages/react-docs/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.0.22", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.0.21", "changes": [ diff --git a/packages/react-docs/CHANGELOG.md b/packages/react-docs/CHANGELOG.md index fc8f7db1f..5c702d562 100644 --- a/packages/react-docs/CHANGELOG.md +++ b/packages/react-docs/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.22 - _December 13, 2018_ + + * Dependencies updated + ## v1.0.21 - _December 11, 2018_ * Dependencies updated diff --git a/packages/react-shared/CHANGELOG.json b/packages/react-shared/CHANGELOG.json index 985e56814..a2bfa24f6 100644 --- a/packages/react-shared/CHANGELOG.json +++ b/packages/react-shared/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.0.25", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.0.24", "changes": [ diff --git a/packages/react-shared/CHANGELOG.md b/packages/react-shared/CHANGELOG.md index 456eb765a..8afa94e04 100644 --- a/packages/react-shared/CHANGELOG.md +++ b/packages/react-shared/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.25 - _December 13, 2018_ + + * Dependencies updated + ## v1.0.24 - _December 11, 2018_ * Dependencies updated diff --git a/packages/sol-compiler/CHANGELOG.json b/packages/sol-compiler/CHANGELOG.json index 8da8726e7..7c081ce5c 100644 --- a/packages/sol-compiler/CHANGELOG.json +++ b/packages/sol-compiler/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.1.16", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.1.15", "changes": [ diff --git a/packages/sol-compiler/CHANGELOG.md b/packages/sol-compiler/CHANGELOG.md index 05b48f0fc..b2066448d 100644 --- a/packages/sol-compiler/CHANGELOG.md +++ b/packages/sol-compiler/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.1.16 - _December 13, 2018_ + + * Dependencies updated + ## v1.1.15 - _December 11, 2018_ * Fix bug where we were appending base path to absolute imports (e.g NPM imports) (#1311) diff --git a/packages/sol-cov/CHANGELOG.json b/packages/sol-cov/CHANGELOG.json index 45b57bdac..e35133550 100644 --- a/packages/sol-cov/CHANGELOG.json +++ b/packages/sol-cov/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "2.1.16", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "2.1.15", "changes": [ diff --git a/packages/sol-cov/CHANGELOG.md b/packages/sol-cov/CHANGELOG.md index c827b1865..879ef9c95 100644 --- a/packages/sol-cov/CHANGELOG.md +++ b/packages/sol-cov/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.1.16 - _December 13, 2018_ + + * Dependencies updated + ## v2.1.15 - _December 11, 2018_ * Dependencies updated diff --git a/packages/sol-doc/CHANGELOG.json b/packages/sol-doc/CHANGELOG.json index 9c6d724bc..a166227ff 100644 --- a/packages/sol-doc/CHANGELOG.json +++ b/packages/sol-doc/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.0.11", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.0.10", "changes": [ diff --git a/packages/sol-doc/CHANGELOG.md b/packages/sol-doc/CHANGELOG.md index 5a1fa1d5a..1b4a938af 100644 --- a/packages/sol-doc/CHANGELOG.md +++ b/packages/sol-doc/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.11 - _December 13, 2018_ + + * Dependencies updated + ## v1.0.10 - _December 11, 2018_ * Dependencies updated diff --git a/packages/sol-resolver/CHANGELOG.json b/packages/sol-resolver/CHANGELOG.json index 40c34d2e7..f8c2f31f8 100644 --- a/packages/sol-resolver/CHANGELOG.json +++ b/packages/sol-resolver/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.1.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.1.0", "changes": [ diff --git a/packages/sol-resolver/CHANGELOG.md b/packages/sol-resolver/CHANGELOG.md index d37938a58..98435be19 100644 --- a/packages/sol-resolver/CHANGELOG.md +++ b/packages/sol-resolver/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.1.1 - _December 13, 2018_ + + * Dependencies updated + ## v1.1.0 - _December 11, 2018_ * NPMResolver now supports scoped packages (#1311) diff --git a/packages/sra-spec/CHANGELOG.json b/packages/sra-spec/CHANGELOG.json index 9897ae6a3..6ddd6bf3e 100644 --- a/packages/sra-spec/CHANGELOG.json +++ b/packages/sra-spec/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.0.13", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.0.12", "changes": [ diff --git a/packages/sra-spec/CHANGELOG.md b/packages/sra-spec/CHANGELOG.md index 0f6aef476..fd673b837 100644 --- a/packages/sra-spec/CHANGELOG.md +++ b/packages/sra-spec/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.0.13 - _December 13, 2018_ + + * Dependencies updated + ## v1.0.12 - _December 11, 2018_ * Dependencies updated diff --git a/packages/subproviders/CHANGELOG.json b/packages/subproviders/CHANGELOG.json index f0766e58b..316b12971 100644 --- a/packages/subproviders/CHANGELOG.json +++ b/packages/subproviders/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "2.1.8", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "2.1.7", "changes": [ diff --git a/packages/subproviders/CHANGELOG.md b/packages/subproviders/CHANGELOG.md index b0bef4b0d..002c76395 100644 --- a/packages/subproviders/CHANGELOG.md +++ b/packages/subproviders/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.1.8 - _December 13, 2018_ + + * Dependencies updated + ## v2.1.7 - _December 11, 2018_ * Dependencies updated diff --git a/packages/tslint-config/CHANGELOG.json b/packages/tslint-config/CHANGELOG.json index eb2698a75..1d3a35159 100644 --- a/packages/tslint-config/CHANGELOG.json +++ b/packages/tslint-config/CHANGELOG.json @@ -6,7 +6,8 @@ "note": "Improve async-suffix rule to check functions too, not just methods", "pr": 1425 } - ] + ], + "timestamp": 1544738225 }, { "version": "1.0.10", diff --git a/packages/tslint-config/CHANGELOG.md b/packages/tslint-config/CHANGELOG.md index f5cacb5d1..3cebb1e95 100644 --- a/packages/tslint-config/CHANGELOG.md +++ b/packages/tslint-config/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.0 - _December 13, 2018_ + + * Improve async-suffix rule to check functions too, not just methods (#1425) + ## v1.0.10 - _November 9, 2018_ * Dependencies updated diff --git a/packages/types/CHANGELOG.json b/packages/types/CHANGELOG.json index 23a317a15..b8f568dc2 100644 --- a/packages/types/CHANGELOG.json +++ b/packages/types/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "1.4.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "1.4.0", "changes": [ diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index ef8337d76..5170eb4db 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v1.4.1 - _December 13, 2018_ + + * Dependencies updated + ## v1.4.0 - _December 11, 2018_ * Add `LengthMismatch` and `LengthGreaterThan3Required` revert reasons (#1224) diff --git a/packages/typescript-typings/CHANGELOG.json b/packages/typescript-typings/CHANGELOG.json index 39ef9d77c..97c53fc95 100644 --- a/packages/typescript-typings/CHANGELOG.json +++ b/packages/typescript-typings/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "3.0.6", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "3.0.5", "changes": [ diff --git a/packages/typescript-typings/CHANGELOG.md b/packages/typescript-typings/CHANGELOG.md index 2e8553a91..1663d40af 100644 --- a/packages/typescript-typings/CHANGELOG.md +++ b/packages/typescript-typings/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.0.6 - _December 13, 2018_ + + * Dependencies updated + ## v3.0.5 - _December 11, 2018_ * Dependencies updated diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index 74fce56aa..25577c5d0 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "2.0.8", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "2.0.7", "changes": [ diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 45553430d..c0437392d 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v2.0.8 - _December 13, 2018_ + + * Dependencies updated + ## v2.0.7 - _December 11, 2018_ * Optimized ABI Encoder/Decoder. Generates compressed calldata to save gas. Generates human-readable calldata to aid development. diff --git a/packages/web3-wrapper/CHANGELOG.json b/packages/web3-wrapper/CHANGELOG.json index ccbd9ed81..a94594853 100644 --- a/packages/web3-wrapper/CHANGELOG.json +++ b/packages/web3-wrapper/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "timestamp": 1544738225, + "version": "3.2.1", + "changes": [ + { + "note": "Dependencies updated" + } + ] + }, { "version": "3.2.0", "changes": [ diff --git a/packages/web3-wrapper/CHANGELOG.md b/packages/web3-wrapper/CHANGELOG.md index 94f50fd92..76ca80e69 100644 --- a/packages/web3-wrapper/CHANGELOG.md +++ b/packages/web3-wrapper/CHANGELOG.md @@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only. CHANGELOG +## v3.2.1 - _December 13, 2018_ + + * Dependencies updated + ## v3.2.0 - _December 11, 2018_ * Return `value` and `gasPrice` as BigNumbers to avoid loss of precision errors (#1402) -- cgit v1.2.3 From 68787056768e834d44ecc23a400036292410dcf6 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Thu, 13 Dec 2018 14:27:27 -0800 Subject: Show @ price in light grey --- packages/instant/src/components/order_details.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 96bdb7150..33a115044 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -117,7 +117,7 @@ export class OrderDetails extends React.Component { } } - private _assetAmountLabel(): string { + private _assetAmountLabel(): React.ReactNode { const { assetName, baseCurrency } = this.props; const numTokens = this.props.selectedAssetUnitAmount; @@ -127,13 +127,14 @@ export class OrderDetails extends React.Component { ? new BigNumber(0) : numTokens; if (!_.isUndefined(displayNumTokens)) { - let numTokensWithSymbol = displayNumTokens.toString(); + let numTokensWithSymbol: React.ReactNode = displayNumTokens.toString(); if (assetName) { numTokensWithSymbol += ` ${assetName}`; } const pricePerTokenWei = this._pricePerTokenWei(); if (pricePerTokenWei) { - numTokensWithSymbol += ` @ ${this._displayAmount(baseCurrency, pricePerTokenWei)}`; + const atPriceDisplay = @ {this._displayAmount(baseCurrency, pricePerTokenWei)}; + numTokensWithSymbol = {numTokensWithSymbol} {atPriceDisplay} } return numTokensWithSymbol; } @@ -185,7 +186,7 @@ export class OrderDetails extends React.Component { } export interface OrderDetailsRowProps { - labelText: string; + labelText: React.ReactNode; isLabelBold?: boolean; isPrimaryValueBold?: boolean; primaryValue: React.ReactNode; -- cgit v1.2.3 From 3c578cfda9b9c05c9c113921fa451a06f27caa7b Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 13 Dec 2018 14:28:17 -0800 Subject: Updated CHANGELOGS --- packages/0x.js/CHANGELOG.json | 4 ++-- packages/abi-gen-wrappers/CHANGELOG.json | 4 ++-- packages/abi-gen/CHANGELOG.json | 4 ++-- packages/assert/CHANGELOG.json | 4 ++-- packages/asset-buyer/CHANGELOG.json | 4 ++-- packages/base-contract/CHANGELOG.json | 4 ++-- packages/connect/CHANGELOG.json | 4 ++-- packages/contract-wrappers/CHANGELOG.json | 4 ++-- packages/dev-utils/CHANGELOG.json | 4 ++-- packages/ethereum-types/CHANGELOG.json | 4 ++-- packages/fill-scenarios/CHANGELOG.json | 4 ++-- packages/json-schemas/CHANGELOG.json | 4 ++-- packages/migrations/CHANGELOG.json | 4 ++-- packages/order-utils/CHANGELOG.json | 4 ++-- packages/order-watcher/CHANGELOG.json | 4 ++-- packages/react-docs/CHANGELOG.json | 4 ++-- packages/react-shared/CHANGELOG.json | 4 ++-- packages/sol-compiler/CHANGELOG.json | 4 ++-- packages/sol-cov/CHANGELOG.json | 4 ++-- packages/sol-doc/CHANGELOG.json | 4 ++-- packages/sol-resolver/CHANGELOG.json | 4 ++-- packages/sra-spec/CHANGELOG.json | 4 ++-- packages/subproviders/CHANGELOG.json | 4 ++-- packages/tslint-config/CHANGELOG.json | 2 +- packages/types/CHANGELOG.json | 4 ++-- packages/typescript-typings/CHANGELOG.json | 4 ++-- packages/utils/CHANGELOG.json | 4 ++-- packages/web3-wrapper/CHANGELOG.json | 4 ++-- 28 files changed, 55 insertions(+), 55 deletions(-) (limited to 'packages') diff --git a/packages/0x.js/CHANGELOG.json b/packages/0x.js/CHANGELOG.json index 03abcd29b..32351ad82 100644 --- a/packages/0x.js/CHANGELOG.json +++ b/packages/0x.js/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "2.0.8", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "2.0.7", diff --git a/packages/abi-gen-wrappers/CHANGELOG.json b/packages/abi-gen-wrappers/CHANGELOG.json index 13936117b..d46e828f4 100644 --- a/packages/abi-gen-wrappers/CHANGELOG.json +++ b/packages/abi-gen-wrappers/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "2.0.2", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "2.0.1", diff --git a/packages/abi-gen/CHANGELOG.json b/packages/abi-gen/CHANGELOG.json index 9a0848f7e..253fb124d 100644 --- a/packages/abi-gen/CHANGELOG.json +++ b/packages/abi-gen/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.0.19", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.0.18", diff --git a/packages/assert/CHANGELOG.json b/packages/assert/CHANGELOG.json index dcbbc6cbb..3805a044f 100644 --- a/packages/assert/CHANGELOG.json +++ b/packages/assert/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.0.20", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.0.19", diff --git a/packages/asset-buyer/CHANGELOG.json b/packages/asset-buyer/CHANGELOG.json index f875aa66b..470d9b03b 100644 --- a/packages/asset-buyer/CHANGELOG.json +++ b/packages/asset-buyer/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "3.0.4", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "3.0.3", diff --git a/packages/base-contract/CHANGELOG.json b/packages/base-contract/CHANGELOG.json index 6ecd235e5..a4cd17d42 100644 --- a/packages/base-contract/CHANGELOG.json +++ b/packages/base-contract/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "3.0.10", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "3.0.9", diff --git a/packages/connect/CHANGELOG.json b/packages/connect/CHANGELOG.json index 11ed171c8..e5bde40ae 100644 --- a/packages/connect/CHANGELOG.json +++ b/packages/connect/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "3.0.10", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "3.0.9", diff --git a/packages/contract-wrappers/CHANGELOG.json b/packages/contract-wrappers/CHANGELOG.json index 942484b8c..9475e5a88 100644 --- a/packages/contract-wrappers/CHANGELOG.json +++ b/packages/contract-wrappers/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "4.1.3", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "4.1.2", diff --git a/packages/dev-utils/CHANGELOG.json b/packages/dev-utils/CHANGELOG.json index c829cad0b..b6cc74ce7 100644 --- a/packages/dev-utils/CHANGELOG.json +++ b/packages/dev-utils/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.0.21", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.0.20", diff --git a/packages/ethereum-types/CHANGELOG.json b/packages/ethereum-types/CHANGELOG.json index 66882f5fd..9a0f2ad49 100644 --- a/packages/ethereum-types/CHANGELOG.json +++ b/packages/ethereum-types/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.1.4", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.1.3", diff --git a/packages/fill-scenarios/CHANGELOG.json b/packages/fill-scenarios/CHANGELOG.json index 50404fc5a..ca256399a 100644 --- a/packages/fill-scenarios/CHANGELOG.json +++ b/packages/fill-scenarios/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.0.16", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.0.15", diff --git a/packages/json-schemas/CHANGELOG.json b/packages/json-schemas/CHANGELOG.json index 519026266..201190145 100644 --- a/packages/json-schemas/CHANGELOG.json +++ b/packages/json-schemas/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "2.1.4", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "2.1.3", diff --git a/packages/migrations/CHANGELOG.json b/packages/migrations/CHANGELOG.json index 3aad8ab5f..e7b18f12b 100644 --- a/packages/migrations/CHANGELOG.json +++ b/packages/migrations/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "2.2.2", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "2.2.1", diff --git a/packages/order-utils/CHANGELOG.json b/packages/order-utils/CHANGELOG.json index 30ea9e823..11889b92e 100644 --- a/packages/order-utils/CHANGELOG.json +++ b/packages/order-utils/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "3.0.7", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "3.0.6", diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index 4c6f7b1d6..c1fd8d4a9 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "2.2.8", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "2.2.7", diff --git a/packages/react-docs/CHANGELOG.json b/packages/react-docs/CHANGELOG.json index 5798764d5..9d8b5bc88 100644 --- a/packages/react-docs/CHANGELOG.json +++ b/packages/react-docs/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.0.22", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.0.21", diff --git a/packages/react-shared/CHANGELOG.json b/packages/react-shared/CHANGELOG.json index a2bfa24f6..23e0d7f7e 100644 --- a/packages/react-shared/CHANGELOG.json +++ b/packages/react-shared/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.0.25", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.0.24", diff --git a/packages/sol-compiler/CHANGELOG.json b/packages/sol-compiler/CHANGELOG.json index 7c081ce5c..0a757f519 100644 --- a/packages/sol-compiler/CHANGELOG.json +++ b/packages/sol-compiler/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.1.16", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.1.15", diff --git a/packages/sol-cov/CHANGELOG.json b/packages/sol-cov/CHANGELOG.json index e35133550..b7973c135 100644 --- a/packages/sol-cov/CHANGELOG.json +++ b/packages/sol-cov/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "2.1.16", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "2.1.15", diff --git a/packages/sol-doc/CHANGELOG.json b/packages/sol-doc/CHANGELOG.json index a166227ff..e8fef746e 100644 --- a/packages/sol-doc/CHANGELOG.json +++ b/packages/sol-doc/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.0.11", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.0.10", diff --git a/packages/sol-resolver/CHANGELOG.json b/packages/sol-resolver/CHANGELOG.json index f8c2f31f8..85398e624 100644 --- a/packages/sol-resolver/CHANGELOG.json +++ b/packages/sol-resolver/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.1.1", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.1.0", diff --git a/packages/sra-spec/CHANGELOG.json b/packages/sra-spec/CHANGELOG.json index 6ddd6bf3e..393054465 100644 --- a/packages/sra-spec/CHANGELOG.json +++ b/packages/sra-spec/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.0.13", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.0.12", diff --git a/packages/subproviders/CHANGELOG.json b/packages/subproviders/CHANGELOG.json index 316b12971..938b2a717 100644 --- a/packages/subproviders/CHANGELOG.json +++ b/packages/subproviders/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "2.1.8", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "2.1.7", diff --git a/packages/tslint-config/CHANGELOG.json b/packages/tslint-config/CHANGELOG.json index 1d3a35159..0070a5b81 100644 --- a/packages/tslint-config/CHANGELOG.json +++ b/packages/tslint-config/CHANGELOG.json @@ -7,7 +7,7 @@ "pr": 1425 } ], - "timestamp": 1544738225 + "timestamp": 1544739608 }, { "version": "1.0.10", diff --git a/packages/types/CHANGELOG.json b/packages/types/CHANGELOG.json index b8f568dc2..f1cd2f18e 100644 --- a/packages/types/CHANGELOG.json +++ b/packages/types/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "1.4.1", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "1.4.0", diff --git a/packages/typescript-typings/CHANGELOG.json b/packages/typescript-typings/CHANGELOG.json index 97c53fc95..fadf5ad14 100644 --- a/packages/typescript-typings/CHANGELOG.json +++ b/packages/typescript-typings/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "3.0.6", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "3.0.5", diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index 25577c5d0..fe66d3f31 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "2.0.8", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "2.0.7", diff --git a/packages/web3-wrapper/CHANGELOG.json b/packages/web3-wrapper/CHANGELOG.json index a94594853..6b76626a9 100644 --- a/packages/web3-wrapper/CHANGELOG.json +++ b/packages/web3-wrapper/CHANGELOG.json @@ -1,12 +1,12 @@ [ { - "timestamp": 1544738225, "version": "3.2.1", "changes": [ { "note": "Dependencies updated" } - ] + ], + "timestamp": 1544739608 }, { "version": "3.2.0", -- cgit v1.2.3 From 2abd8fe4ee860e5a4008a9f92f6d29239d14b2bc Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 13 Dec 2018 14:28:27 -0800 Subject: Publish - 0x.js@2.0.8 - @0x/abi-gen@1.0.19 - @0x/abi-gen-wrappers@2.0.2 - @0x/assert@1.0.20 - @0x/asset-buyer@3.0.4 - @0x/base-contract@3.0.10 - @0x/connect@3.0.10 - @0x/contract-wrappers@4.1.3 - @0x/dev-tools-pages@0.0.10 - @0x/dev-utils@1.0.21 - ethereum-types@1.1.4 - @0x/fill-scenarios@1.0.16 - @0x/instant@1.0.4 - @0x/json-schemas@2.1.4 - @0x/metacoin@0.0.32 - @0x/migrations@2.2.2 - @0x/order-utils@3.0.7 - @0x/order-watcher@2.2.8 - @0x/pipeline@1.0.2 - @0x/react-docs@1.0.22 - @0x/react-shared@1.0.25 - @0x/sol-compiler@1.1.16 - @0x/sol-cov@2.1.16 - @0x/sol-doc@1.0.11 - @0x/sol-resolver@1.1.1 - @0x/sra-spec@1.0.13 - @0x/subproviders@2.1.8 - @0x/testnet-faucets@1.0.60 - @0x/tslint-config@2.0.0 - @0x/types@1.4.1 - @0x/typescript-typings@3.0.6 - @0x/utils@2.0.8 - @0x/web3-wrapper@3.2.1 - @0x/website@0.0.63 - @0x/contracts-examples@1.0.1 - @0x/contracts-extensions@1.0.1 - @0x/contracts-interfaces@1.0.1 - @0x/contracts-libs@1.0.1 - @0x/contracts-multisig@1.0.1 - @0x/contracts-protocol@2.1.58 - @0x/contracts-test-utils@1.0.2 - @0x/contracts-tokens@1.0.1 - @0x/contracts-utils@1.0.1 --- packages/0x.js/package.json | 32 ++++++++++++++++---------------- packages/abi-gen-wrappers/package.json | 16 ++++++++-------- packages/abi-gen/package.json | 10 +++++----- packages/assert/package.json | 10 +++++----- packages/asset-buyer/package.json | 26 +++++++++++++------------- packages/base-contract/package.json | 12 ++++++------ packages/connect/package.json | 16 ++++++++-------- packages/contract-wrappers/package.json | 30 +++++++++++++++--------------- packages/dev-tools-pages/package.json | 4 ++-- packages/dev-utils/package.json | 16 ++++++++-------- packages/ethereum-types/package.json | 4 ++-- packages/fill-scenarios/package.json | 20 ++++++++++---------- packages/instant/package.json | 24 ++++++++++++------------ packages/json-schemas/package.json | 8 ++++---- packages/metacoin/package.json | 26 +++++++++++++------------- packages/migrations/package.json | 26 +++++++++++++------------- packages/order-utils/package.json | 24 ++++++++++++------------ packages/order-watcher/package.json | 32 ++++++++++++++++---------------- packages/pipeline/package.json | 18 +++++++++--------- packages/react-docs/package.json | 12 ++++++------ packages/react-shared/package.json | 8 ++++---- packages/sol-compiler/package.json | 22 +++++++++++----------- packages/sol-cov/package.json | 18 +++++++++--------- packages/sol-doc/package.json | 12 ++++++------ packages/sol-resolver/package.json | 8 ++++---- packages/sra-spec/package.json | 6 +++--- packages/subproviders/package.json | 16 ++++++++-------- packages/testnet-faucets/package.json | 16 ++++++++-------- packages/tslint-config/package.json | 2 +- packages/types/package.json | 6 +++--- packages/typescript-typings/package.json | 4 ++-- packages/utils/package.json | 10 +++++----- packages/web3-wrapper/package.json | 14 +++++++------- packages/website/package.json | 26 +++++++++++++------------- 34 files changed, 267 insertions(+), 267 deletions(-) (limited to 'packages') diff --git a/packages/0x.js/package.json b/packages/0x.js/package.json index fa1c61131..2960c9e4b 100644 --- a/packages/0x.js/package.json +++ b/packages/0x.js/package.json @@ -1,6 +1,6 @@ { "name": "0x.js", - "version": "2.0.7", + "version": "2.0.8", "engines": { "node": ">=6.12" }, @@ -42,11 +42,11 @@ }, "license": "Apache-2.0", "devDependencies": { - "@0x/abi-gen-wrappers": "^2.0.1", + "@0x/abi-gen-wrappers": "^2.0.2", "@0x/contract-addresses": "^2.0.0", - "@0x/dev-utils": "^1.0.20", - "@0x/migrations": "^2.2.1", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/migrations": "^2.2.2", + "@0x/tslint-config": "^2.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/node": "*", @@ -72,18 +72,18 @@ "webpack": "^4.20.2" }, "dependencies": { - "@0x/assert": "^1.0.19", - "@0x/base-contract": "^3.0.9", - "@0x/contract-wrappers": "^4.1.2", - "@0x/order-utils": "^3.0.6", - "@0x/order-watcher": "^2.2.7", - "@0x/subproviders": "^2.1.7", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/assert": "^1.0.20", + "@0x/base-contract": "^3.0.10", + "@0x/contract-wrappers": "^4.1.3", + "@0x/order-utils": "^3.0.7", + "@0x/order-watcher": "^2.2.8", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/web3-provider-engine": "^14.0.0", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5", "web3-provider-engine": "14.0.6" diff --git a/packages/abi-gen-wrappers/package.json b/packages/abi-gen-wrappers/package.json index e40a19615..25ba3a6e0 100644 --- a/packages/abi-gen-wrappers/package.json +++ b/packages/abi-gen-wrappers/package.json @@ -1,6 +1,6 @@ { "name": "@0x/abi-gen-wrappers", - "version": "2.0.1", + "version": "2.0.2", "engines": { "node": ">=6.12" }, @@ -30,19 +30,19 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/abi-gen-wrappers/README.md", "devDependencies": { - "@0x/abi-gen": "^1.0.18", + "@0x/abi-gen": "^1.0.19", "@0x/abi-gen-templates": "^1.0.1", - "@0x/tslint-config": "^1.0.10", - "@0x/types": "^1.4.0", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", - "ethereum-types": "^1.1.3", + "@0x/tslint-config": "^2.0.0", + "@0x/types": "^1.4.1", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5", "shx": "^0.2.2" }, "dependencies": { - "@0x/base-contract": "^3.0.9" + "@0x/base-contract": "^3.0.10" }, "publishConfig": { "access": "public" diff --git a/packages/abi-gen/package.json b/packages/abi-gen/package.json index 8f46eaca0..b122c742d 100644 --- a/packages/abi-gen/package.json +++ b/packages/abi-gen/package.json @@ -1,6 +1,6 @@ { "name": "@0x/abi-gen", - "version": "1.0.18", + "version": "1.0.19", "engines": { "node": ">=6.12" }, @@ -31,10 +31,10 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/abi-gen/README.md", "dependencies": { - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", "chalk": "^2.3.0", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "glob": "^7.1.2", "handlebars": "^4.0.11", "lodash": "^4.17.5", @@ -45,7 +45,7 @@ "yargs": "^10.0.3" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/glob": "5.0.35", "@types/handlebars": "^4.0.36", "@types/mkdirp": "^0.5.1", diff --git a/packages/assert/package.json b/packages/assert/package.json index 2792c066f..cec1748cd 100644 --- a/packages/assert/package.json +++ b/packages/assert/package.json @@ -1,6 +1,6 @@ { "name": "@0x/assert", - "version": "1.0.19", + "version": "1.0.20", "engines": { "node": ">=6.12" }, @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/assert/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/valid-url": "^1.0.2", @@ -44,9 +44,9 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/json-schemas": "^2.1.3", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", + "@0x/json-schemas": "^2.1.4", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", "lodash": "^4.17.5", "valid-url": "^1.0.9" }, diff --git a/packages/asset-buyer/package.json b/packages/asset-buyer/package.json index 54e652361..401aec120 100644 --- a/packages/asset-buyer/package.json +++ b/packages/asset-buyer/package.json @@ -1,6 +1,6 @@ { "name": "@0x/asset-buyer", - "version": "3.0.3", + "version": "3.0.4", "engines": { "node": ">=6.12" }, @@ -36,21 +36,21 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/asset-buyer/README.md", "dependencies": { - "@0x/assert": "^1.0.19", - "@0x/connect": "^3.0.9", - "@0x/contract-wrappers": "^4.1.2", - "@0x/json-schemas": "^2.1.3", - "@0x/order-utils": "^3.0.6", - "@0x/subproviders": "^2.1.7", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", - "ethereum-types": "^1.1.3", + "@0x/assert": "^1.0.20", + "@0x/connect": "^3.0.10", + "@0x/contract-wrappers": "^4.1.3", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "ethereum-types": "^1.1.4", "lodash": "^4.17.5" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/lodash": "^4.14.116", "@types/mocha": "^2.2.42", "@types/node": "*", diff --git a/packages/base-contract/package.json b/packages/base-contract/package.json index 17c2af2dd..87d70dd5f 100644 --- a/packages/base-contract/package.json +++ b/packages/base-contract/package.json @@ -1,6 +1,6 @@ { "name": "@0x/base-contract", - "version": "3.0.9", + "version": "3.0.10", "engines": { "node": ">=6.12" }, @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/base-contract/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/lodash": "4.14.104", "chai": "^4.0.1", "make-promises-safe": "^1.1.0", @@ -40,10 +40,10 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", - "ethereum-types": "^1.1.3", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5" }, diff --git a/packages/connect/package.json b/packages/connect/package.json index 9d1218b5a..4985d0410 100644 --- a/packages/connect/package.json +++ b/packages/connect/package.json @@ -1,6 +1,6 @@ { "name": "@0x/connect", - "version": "3.0.9", + "version": "3.0.10", "engines": { "node": ">=6.12" }, @@ -44,12 +44,12 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/connect/README.md", "dependencies": { - "@0x/assert": "^1.0.19", - "@0x/json-schemas": "^2.1.3", - "@0x/order-utils": "^3.0.6", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", + "@0x/assert": "^1.0.20", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", "lodash": "^4.17.5", "query-string": "^5.0.1", "sinon": "^4.0.0", @@ -57,7 +57,7 @@ "websocket": "^1.0.25" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/fetch-mock": "^6.0.3", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", diff --git a/packages/contract-wrappers/package.json b/packages/contract-wrappers/package.json index ec0dcb0cb..f3f7301fd 100644 --- a/packages/contract-wrappers/package.json +++ b/packages/contract-wrappers/package.json @@ -1,6 +1,6 @@ { "name": "@0x/contract-wrappers", - "version": "4.1.2", + "version": "4.1.3", "description": "Smart TS wrappers for 0x smart contracts", "keywords": [ "0xproject", @@ -37,10 +37,10 @@ "node": ">=6.0.0" }, "devDependencies": { - "@0x/dev-utils": "^1.0.20", - "@0x/migrations": "^2.2.1", - "@0x/subproviders": "^2.1.7", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/migrations": "^2.2.2", + "@0x/subproviders": "^2.1.8", + "@0x/tslint-config": "^2.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "@types/node": "*", @@ -65,18 +65,18 @@ "web3-provider-engine": "14.0.6" }, "dependencies": { - "@0x/abi-gen-wrappers": "^2.0.1", - "@0x/assert": "^1.0.19", + "@0x/abi-gen-wrappers": "^2.0.2", + "@0x/assert": "^1.0.20", "@0x/contract-addresses": "^2.0.0", "@0x/contract-artifacts": "^1.1.2", - "@0x/fill-scenarios": "^1.0.15", - "@0x/json-schemas": "^2.1.3", - "@0x/order-utils": "^3.0.6", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", - "ethereum-types": "^1.1.3", + "@0x/fill-scenarios": "^1.0.16", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "ethereum-types": "^1.1.4", "ethereumjs-blockstream": "6.0.0", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", diff --git a/packages/dev-tools-pages/package.json b/packages/dev-tools-pages/package.json index f83b57308..754db3208 100644 --- a/packages/dev-tools-pages/package.json +++ b/packages/dev-tools-pages/package.json @@ -1,6 +1,6 @@ { "name": "@0x/dev-tools-pages", - "version": "0.0.9", + "version": "0.0.10", "engines": { "node": ">=6.12" }, @@ -16,7 +16,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@0x/react-shared": "^1.0.24", + "@0x/react-shared": "^1.0.25", "basscss": "^8.0.3", "bowser": "^1.9.3", "less": "^2.7.2", diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 872a285a3..a3eb4651a 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@0x/dev-utils", - "version": "1.0.20", + "version": "1.0.21", "engines": { "node": ">=6.12" }, @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/dev-utils/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", "make-promises-safe": "^1.1.0", @@ -41,14 +41,14 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/subproviders": "^2.1.7", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/web3-provider-engine": "^14.0.0", "chai": "^4.0.1", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "lodash": "^4.17.5" }, "publishConfig": { diff --git a/packages/ethereum-types/package.json b/packages/ethereum-types/package.json index 473f9119c..69ae64c2b 100644 --- a/packages/ethereum-types/package.json +++ b/packages/ethereum-types/package.json @@ -1,6 +1,6 @@ { "name": "ethereum-types", - "version": "1.1.3", + "version": "1.1.4", "engines": { "node": ">=6.12" }, @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/ethereum-types/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", diff --git a/packages/fill-scenarios/package.json b/packages/fill-scenarios/package.json index 10f8b0495..7a9d21e0a 100644 --- a/packages/fill-scenarios/package.json +++ b/packages/fill-scenarios/package.json @@ -1,6 +1,6 @@ { "name": "@0x/fill-scenarios", - "version": "1.0.15", + "version": "1.0.16", "description": "0x order fill scenario generator", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,7 +20,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/fill-scenarios/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/lodash": "4.14.104", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", @@ -28,15 +28,15 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/abi-gen-wrappers": "^2.0.1", - "@0x/base-contract": "^3.0.9", + "@0x/abi-gen-wrappers": "^2.0.2", + "@0x/base-contract": "^3.0.10", "@0x/contract-artifacts": "^1.1.2", - "@0x/order-utils": "^3.0.6", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", - "ethereum-types": "^1.1.3", + "@0x/order-utils": "^3.0.7", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5" }, diff --git a/packages/instant/package.json b/packages/instant/package.json index 7a519f460..0a5e152ca 100644 --- a/packages/instant/package.json +++ b/packages/instant/package.json @@ -1,6 +1,6 @@ { "name": "@0x/instant", - "version": "1.0.3", + "version": "1.0.4", "engines": { "node": ">=6.12" }, @@ -41,18 +41,18 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md", "dependencies": { - "@0x/assert": "^1.0.19", - "@0x/asset-buyer": "^3.0.3", - "@0x/json-schemas": "^2.1.3", - "@0x/order-utils": "^3.0.6", - "@0x/subproviders": "^2.1.7", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/assert": "^1.0.20", + "@0x/asset-buyer": "^3.0.4", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "bowser": "^1.9.4", "copy-to-clipboard": "^3.0.8", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "lodash": "^4.17.5", "polished": "^2.2.0", "react": "^16.5.2", @@ -65,7 +65,7 @@ "ts-optchain": "^0.1.1" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@static/discharge": "https://github.com/0xProject/discharge.git", "@types/enzyme": "^3.1.14", "@types/enzyme-adapter-react-16": "^1.0.3", diff --git a/packages/json-schemas/package.json b/packages/json-schemas/package.json index 01662beed..57dd9dc00 100644 --- a/packages/json-schemas/package.json +++ b/packages/json-schemas/package.json @@ -1,6 +1,6 @@ { "name": "@0x/json-schemas", - "version": "2.1.3", + "version": "2.1.4", "engines": { "node": ">=6.12" }, @@ -39,14 +39,14 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/json-schemas/README.md", "dependencies": { - "@0x/typescript-typings": "^3.0.5", + "@0x/typescript-typings": "^3.0.6", "@types/node": "*", "jsonschema": "^1.2.0", "lodash.values": "^4.3.0" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", - "@0x/utils": "^2.0.7", + "@0x/tslint-config": "^2.0.0", + "@0x/utils": "^2.0.8", "@types/lodash.foreach": "^4.5.3", "@types/lodash.values": "^4.3.3", "@types/mocha": "^2.2.42", diff --git a/packages/metacoin/package.json b/packages/metacoin/package.json index e51b240db..0e9fa4920 100644 --- a/packages/metacoin/package.json +++ b/packages/metacoin/package.json @@ -1,6 +1,6 @@ { "name": "@0x/metacoin", - "version": "0.0.31", + "version": "0.0.32", "engines": { "node": ">=6.12" }, @@ -29,26 +29,26 @@ "author": "", "license": "Apache-2.0", "dependencies": { - "@0x/abi-gen": "^1.0.18", + "@0x/abi-gen": "^1.0.19", "@0x/abi-gen-templates": "^1.0.1", - "@0x/base-contract": "^3.0.9", - "@0x/sol-cov": "^2.1.15", - "@0x/subproviders": "^2.1.7", - "@0x/tslint-config": "^1.0.10", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/base-contract": "^3.0.10", + "@0x/sol-cov": "^2.1.16", + "@0x/subproviders": "^2.1.8", + "@0x/tslint-config": "^2.0.0", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/mocha": "^5.2.2", "copyfiles": "^2.0.0", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5", "run-s": "^0.0.0" }, "devDependencies": { - "@0x/dev-utils": "^1.0.20", - "@0x/sol-compiler": "^1.1.15", + "@0x/dev-utils": "^1.0.21", + "@0x/sol-compiler": "^1.1.16", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.1", diff --git a/packages/migrations/package.json b/packages/migrations/package.json index b2d16c0ee..72ffe67b2 100644 --- a/packages/migrations/package.json +++ b/packages/migrations/package.json @@ -1,6 +1,6 @@ { "name": "@0x/migrations", - "version": "2.2.1", + "version": "2.2.2", "engines": { "node": ">=6.12" }, @@ -26,9 +26,9 @@ }, "license": "Apache-2.0", "devDependencies": { - "@0x/dev-utils": "^1.0.20", - "@0x/tslint-config": "^1.0.10", - "@0x/types": "^1.4.0", + "@0x/dev-utils": "^1.0.21", + "@0x/tslint-config": "^2.0.0", + "@0x/types": "^1.4.1", "@types/yargs": "^10.0.0", "make-promises-safe": "^1.1.0", "npm-run-all": "^4.1.2", @@ -39,18 +39,18 @@ "yargs": "^10.0.3" }, "dependencies": { - "@0x/abi-gen-wrappers": "^2.0.1", - "@0x/base-contract": "^3.0.9", + "@0x/abi-gen-wrappers": "^2.0.2", + "@0x/base-contract": "^3.0.10", "@0x/contract-addresses": "^2.0.0", "@0x/contract-artifacts": "^1.1.2", - "@0x/order-utils": "^3.0.6", - "@0x/sol-compiler": "^1.1.15", - "@0x/subproviders": "^2.1.7", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/order-utils": "^3.0.7", + "@0x/sol-compiler": "^1.1.16", + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@ledgerhq/hw-app-eth": "^4.3.0", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethers": "~4.0.4", "lodash": "^4.17.5" }, diff --git a/packages/order-utils/package.json b/packages/order-utils/package.json index 55541787a..400c9b66f 100644 --- a/packages/order-utils/package.json +++ b/packages/order-utils/package.json @@ -1,6 +1,6 @@ { "name": "@0x/order-utils", - "version": "3.0.6", + "version": "3.0.7", "engines": { "node": ">=6.12" }, @@ -35,8 +35,8 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/order-utils/README.md", "devDependencies": { - "@0x/dev-utils": "^1.0.20", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/tslint-config": "^2.0.0", "@types/bn.js": "^4.11.0", "@types/lodash": "4.14.104", "chai": "^4.0.1", @@ -53,18 +53,18 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/abi-gen-wrappers": "^2.0.1", - "@0x/assert": "^1.0.19", - "@0x/base-contract": "^3.0.9", + "@0x/abi-gen-wrappers": "^2.0.2", + "@0x/assert": "^1.0.20", + "@0x/base-contract": "^3.0.10", "@0x/contract-artifacts": "^1.1.2", - "@0x/json-schemas": "^2.1.3", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/json-schemas": "^2.1.4", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/node": "*", "bn.js": "^4.11.8", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethereumjs-abi": "0.6.5", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", diff --git a/packages/order-watcher/package.json b/packages/order-watcher/package.json index b6c519e94..499d4cead 100644 --- a/packages/order-watcher/package.json +++ b/packages/order-watcher/package.json @@ -1,6 +1,6 @@ { "name": "@0x/order-watcher", - "version": "2.2.7", + "version": "2.2.8", "description": "An order watcher daemon that watches for order validity", "keywords": [ "0x", @@ -33,9 +33,9 @@ "node": ">=6.0.0" }, "devDependencies": { - "@0x/dev-utils": "^1.0.20", - "@0x/migrations": "^2.2.1", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/migrations": "^2.2.2", + "@0x/tslint-config": "^2.0.0", "@types/bintrees": "^1.0.2", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", @@ -57,21 +57,21 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/abi-gen-wrappers": "^2.0.1", - "@0x/assert": "^1.0.19", - "@0x/base-contract": "^3.0.9", + "@0x/abi-gen-wrappers": "^2.0.2", + "@0x/assert": "^1.0.20", + "@0x/base-contract": "^3.0.10", "@0x/contract-addresses": "^2.0.0", "@0x/contract-artifacts": "^1.1.2", - "@0x/contract-wrappers": "^4.1.2", - "@0x/fill-scenarios": "^1.0.15", - "@0x/json-schemas": "^2.1.3", - "@0x/order-utils": "^3.0.6", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/contract-wrappers": "^4.1.3", + "@0x/fill-scenarios": "^1.0.16", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "bintrees": "^1.0.2", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethereumjs-blockstream": "6.0.0", "ethers": "~4.0.4", "lodash": "^4.17.5" diff --git a/packages/pipeline/package.json b/packages/pipeline/package.json index f8adf9055..a40f3d21c 100644 --- a/packages/pipeline/package.json +++ b/packages/pipeline/package.json @@ -1,6 +1,6 @@ { "name": "@0x/pipeline", - "version": "1.0.1", + "version": "1.0.2", "private": true, "description": "Data pipeline for offline analysis", "scripts": { @@ -27,7 +27,7 @@ }, "license": "Apache-2.0", "devDependencies": { - "@0x/tslint-config": "^1.0.9", + "@0x/tslint-config": "^2.0.0", "@types/axios": "^0.14.0", "@types/ramda": "^0.25.38", "chai": "^4.1.2", @@ -39,23 +39,23 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/connect": "^3.0.9", + "@0x/connect": "^3.0.10", "@0x/contract-addresses": "^2.0.0", "@0x/contract-artifacts": "^1.0.1", "@0x/contract-wrappers": "^3.0.0", - "@0x/dev-utils": "^1.0.20", + "@0x/dev-utils": "^1.0.21", "@0x/order-utils": "^2.0.0", - "@0x/subproviders": "^2.1.7", - "@0x/types": "^1.4.0", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/dockerode": "^2.5.9", "@types/p-limit": "^2.0.0", "async-parallel": "^1.2.3", "axios": "^0.18.0", "bottleneck": "^2.13.2", "dockerode": "^2.5.7", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "pg": "^7.5.0", "prettier": "^1.15.3", "ramda": "^0.25.0", diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json index b4de54a21..f58a51c90 100644 --- a/packages/react-docs/package.json +++ b/packages/react-docs/package.json @@ -1,6 +1,6 @@ { "name": "@0x/react-docs", - "version": "1.0.21", + "version": "1.0.22", "engines": { "node": ">=6.12" }, @@ -24,8 +24,8 @@ "url": "https://github.com/0xProject/0x-monorepo.git" }, "devDependencies": { - "@0x/dev-utils": "^1.0.20", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/tslint-config": "^2.0.0", "@types/compare-versions": "^3.0.0", "@types/styled-components": "^4.0.0", "make-promises-safe": "^1.1.0", @@ -34,9 +34,9 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/react-shared": "^1.0.24", - "@0x/types": "^1.4.0", - "@0x/utils": "^2.0.7", + "@0x/react-shared": "^1.0.25", + "@0x/types": "^1.4.1", + "@0x/utils": "^2.0.8", "@types/lodash": "4.14.104", "@types/material-ui": "^0.20.0", "@types/node": "*", diff --git a/packages/react-shared/package.json b/packages/react-shared/package.json index 125edf374..17defe35a 100644 --- a/packages/react-shared/package.json +++ b/packages/react-shared/package.json @@ -1,6 +1,6 @@ { "name": "@0x/react-shared", - "version": "1.0.24", + "version": "1.0.25", "engines": { "node": ">=6.12" }, @@ -25,15 +25,15 @@ "url": "https://github.com/0xProject/0x-monorepo.git" }, "devDependencies": { - "@0x/dev-utils": "^1.0.20", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/tslint-config": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "^5.9.1", "typescript": "3.0.1" }, "dependencies": { - "@0x/types": "^1.4.0", + "@0x/types": "^1.4.1", "@material-ui/core": "^3.0.1", "@types/is-mobile": "0.3.0", "@types/lodash": "4.14.104", diff --git a/packages/sol-compiler/package.json b/packages/sol-compiler/package.json index 35c3012f6..0ad620b1f 100644 --- a/packages/sol-compiler/package.json +++ b/packages/sol-compiler/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-compiler", - "version": "1.1.15", + "version": "1.1.16", "engines": { "node": ">=6.12" }, @@ -42,8 +42,8 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-compiler/README.md", "devDependencies": { - "@0x/dev-utils": "^1.0.20", - "@0x/tslint-config": "^1.0.10", + "@0x/dev-utils": "^1.0.21", + "@0x/tslint-config": "^2.0.0", "@types/mkdirp": "^0.5.2", "@types/require-from-string": "^1.2.0", "@types/semver": "^5.5.0", @@ -65,16 +65,16 @@ "zeppelin-solidity": "1.8.0" }, "dependencies": { - "@0x/assert": "^1.0.19", - "@0x/json-schemas": "^2.1.3", - "@0x/sol-resolver": "^1.1.0", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/assert": "^1.0.20", + "@0x/json-schemas": "^2.1.4", + "@0x/sol-resolver": "^1.1.1", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/yargs": "^11.0.0", "chalk": "^2.3.0", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.5", "mkdirp": "^0.5.1", diff --git a/packages/sol-cov/package.json b/packages/sol-cov/package.json index aae65107a..3ade51c80 100644 --- a/packages/sol-cov/package.json +++ b/packages/sol-cov/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-cov", - "version": "2.1.15", + "version": "2.1.16", "engines": { "node": ">=6.12" }, @@ -42,14 +42,14 @@ }, "homepage": "https://github.com/0xProject/0x.js/packages/sol-cov/README.md", "dependencies": { - "@0x/dev-utils": "^1.0.20", - "@0x/sol-compiler": "^1.1.15", - "@0x/subproviders": "^2.1.7", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/dev-utils": "^1.0.21", + "@0x/sol-compiler": "^1.1.16", + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@types/solidity-parser-antlr": "^0.2.0", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "glob": "^7.1.2", "istanbul": "^0.4.5", @@ -61,7 +61,7 @@ "solidity-parser-antlr": "^0.2.12" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/istanbul": "^0.4.30", "@types/loglevel": "^1.5.3", "@types/mkdirp": "^0.5.1", diff --git a/packages/sol-doc/package.json b/packages/sol-doc/package.json index 1d2762875..c83c122df 100644 --- a/packages/sol-doc/package.json +++ b/packages/sol-doc/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-doc", - "version": "1.0.10", + "version": "1.0.11", "description": "Solidity documentation generator", "main": "lib/src/index.js", "types": "lib/src/index.d.js", @@ -25,16 +25,16 @@ "author": "F. Eugene Aumson", "license": "Apache-2.0", "dependencies": { - "@0x/sol-compiler": "^1.1.15", - "@0x/types": "^1.4.0", - "@0x/utils": "^2.0.7", - "ethereum-types": "^1.1.3", + "@0x/sol-compiler": "^1.1.16", + "@0x/types": "^1.4.1", + "@0x/utils": "^2.0.8", + "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.10", "yargs": "^12.0.2" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "chai": "^4.1.2", "chai-as-promised": "^7.1.0", "chai-bignumber": "^2.0.2", diff --git a/packages/sol-resolver/package.json b/packages/sol-resolver/package.json index f6a928356..0163765d9 100644 --- a/packages/sol-resolver/package.json +++ b/packages/sol-resolver/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sol-resolver", - "version": "1.1.0", + "version": "1.1.1", "engines": { "node": ">=6.12" }, @@ -23,15 +23,15 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/resolver/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", "typescript": "3.0.1" }, "dependencies": { - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", "lodash": "^4.17.5" }, "publishConfig": { diff --git a/packages/sra-spec/package.json b/packages/sra-spec/package.json index 2983ba044..3a6ee96d3 100644 --- a/packages/sra-spec/package.json +++ b/packages/sra-spec/package.json @@ -1,6 +1,6 @@ { "name": "@0x/sra-spec", - "version": "1.0.12", + "version": "1.0.13", "engines": { "node": ">=6.12" }, @@ -35,11 +35,11 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/sra-spec/README.md", "dependencies": { - "@0x/json-schemas": "^2.1.3", + "@0x/json-schemas": "^2.1.4", "@loopback/openapi-v3-types": "^0.8.2" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/mocha": "^2.2.42", "@types/node": "^10.5.3", "chai": "^4.0.1", diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 8cd29b8e8..90ef6b677 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -1,6 +1,6 @@ { "name": "@0x/subproviders", - "version": "2.1.7", + "version": "2.1.8", "engines": { "node": ">=6.12" }, @@ -29,11 +29,11 @@ } }, "dependencies": { - "@0x/assert": "^1.0.19", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/assert": "^1.0.20", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "@ledgerhq/hw-app-eth": "^4.3.0", "@ledgerhq/hw-transport-u2f": "4.24.0", "@types/eth-lightwallet": "^3.0.0", @@ -43,7 +43,7 @@ "bip39": "^2.5.0", "bn.js": "^4.11.8", "eth-lightwallet": "^3.0.1", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethereumjs-tx": "^1.3.5", "ethereumjs-util": "^5.1.1", "ganache-core": "^2.2.1", @@ -54,7 +54,7 @@ "web3-provider-engine": "14.0.6" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/bip39": "^2.4.0", "@types/bn.js": "^4.11.0", "@types/ethereumjs-tx": "^1.0.0", diff --git a/packages/testnet-faucets/package.json b/packages/testnet-faucets/package.json index 454314c86..70d785c74 100644 --- a/packages/testnet-faucets/package.json +++ b/packages/testnet-faucets/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@0x/testnet-faucets", - "version": "1.0.59", + "version": "1.0.60", "engines": { "node": ">=6.12" }, @@ -18,13 +18,13 @@ "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { - "0x.js": "^2.0.7", - "@0x/subproviders": "^2.1.7", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "0x.js": "^2.0.8", + "@0x/subproviders": "^2.1.8", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "body-parser": "^1.17.1", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethereumjs-tx": "^1.3.5", "ethereumjs-util": "^5.1.1", "express": "^4.15.2", @@ -32,7 +32,7 @@ "rollbar": "^0.6.5" }, "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/body-parser": "^1.16.1", "@types/express": "^4.0.35", "@types/lodash": "4.14.104", diff --git a/packages/tslint-config/package.json b/packages/tslint-config/package.json index 2914f31e2..64ec1e967 100644 --- a/packages/tslint-config/package.json +++ b/packages/tslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@0x/tslint-config", - "version": "1.0.10", + "version": "2.0.0", "engines": { "node": ">=6.12" }, diff --git a/packages/types/package.json b/packages/types/package.json index 1e2bc5ae8..3c4bb6fe6 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@0x/types", - "version": "1.4.0", + "version": "1.4.1", "engines": { "node": ">=6.12" }, @@ -23,7 +23,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/types/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "make-promises-safe": "^1.1.0", "shx": "^0.2.2", "tslint": "5.11.0", @@ -32,7 +32,7 @@ "dependencies": { "@types/node": "*", "bignumber.js": "~4.1.0", - "ethereum-types": "^1.1.3" + "ethereum-types": "^1.1.4" }, "publishConfig": { "access": "public" diff --git a/packages/typescript-typings/package.json b/packages/typescript-typings/package.json index 59e0803eb..c67b660c8 100644 --- a/packages/typescript-typings/package.json +++ b/packages/typescript-typings/package.json @@ -1,6 +1,6 @@ { "name": "@0x/typescript-typings", - "version": "3.0.5", + "version": "3.0.6", "engines": { "node": ">=6.12" }, @@ -27,7 +27,7 @@ "@types/bn.js": "^4.11.0", "@types/react": "*", "bignumber.js": "~4.1.0", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "popper.js": "1.14.3" }, "devDependencies": { diff --git a/packages/utils/package.json b/packages/utils/package.json index 38c729337..a25dc9cff 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@0x/utils", - "version": "2.0.7", + "version": "2.0.8", "engines": { "node": ">=6.12" }, @@ -28,7 +28,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/utils/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/detect-node": "2.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^2.2.42", @@ -44,13 +44,13 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", "@types/node": "*", "abortcontroller-polyfill": "^1.1.9", "bignumber.js": "~4.1.0", "detect-node": "2.0.3", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", "isomorphic-fetch": "^2.2.1", diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index 09fd184d2..e5af24965 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -1,6 +1,6 @@ { "name": "@0x/web3-wrapper", - "version": "3.2.0", + "version": "3.2.1", "engines": { "node": ">=6.12" }, @@ -36,7 +36,7 @@ }, "homepage": "https://github.com/0xProject/0x-monorepo/packages/web3-wrapper/README.md", "devDependencies": { - "@0x/tslint-config": "^1.0.10", + "@0x/tslint-config": "^2.0.0", "@types/ganache-core": "^2.1.0", "@types/lodash": "4.14.104", "chai": "^4.0.1", @@ -54,11 +54,11 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/assert": "^1.0.19", - "@0x/json-schemas": "^2.1.3", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "ethereum-types": "^1.1.3", + "@0x/assert": "^1.0.20", + "@0x/json-schemas": "^2.1.4", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "ethers": "~4.0.4", "lodash": "^4.17.5" diff --git a/packages/website/package.json b/packages/website/package.json index d56688049..c24a0ba6e 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,6 +1,6 @@ { "name": "@0x/website", - "version": "0.0.62", + "version": "0.0.63", "engines": { "node": ">=6.12" }, @@ -20,24 +20,24 @@ "author": "Fabio Berger", "license": "Apache-2.0", "dependencies": { - "@0x/asset-buyer": "^3.0.3", + "@0x/asset-buyer": "^3.0.4", "@0x/contract-addresses": "^2.0.0", - "@0x/contract-wrappers": "^4.1.2", - "@0x/json-schemas": "^2.1.3", - "@0x/order-utils": "^3.0.6", - "@0x/react-docs": "^1.0.21", - "@0x/react-shared": "^1.0.24", - "@0x/subproviders": "^2.1.7", - "@0x/types": "^1.4.0", - "@0x/typescript-typings": "^3.0.5", - "@0x/utils": "^2.0.7", - "@0x/web3-wrapper": "^3.2.0", + "@0x/contract-wrappers": "^4.1.3", + "@0x/json-schemas": "^2.1.4", + "@0x/order-utils": "^3.0.7", + "@0x/react-docs": "^1.0.22", + "@0x/react-shared": "^1.0.25", + "@0x/subproviders": "^2.1.8", + "@0x/types": "^1.4.1", + "@0x/typescript-typings": "^3.0.6", + "@0x/utils": "^2.0.8", + "@0x/web3-wrapper": "^3.2.1", "accounting": "^0.4.1", "basscss": "^8.0.3", "blockies": "^0.0.2", "bowser": "^1.9.3", "deep-equal": "^1.0.1", - "ethereum-types": "^1.1.3", + "ethereum-types": "^1.1.4", "ethereumjs-util": "^5.1.1", "find-versions": "^2.0.0", "jsonschema": "^1.2.0", -- cgit v1.2.3 From 6d45beccad44e86ddd692d0cf54c09c29c5d9daf Mon Sep 17 00:00:00 2001 From: zkao Date: Fri, 14 Dec 2018 12:15:35 -0800 Subject: Fix dex order quote/base asset assigning (#1432) --- packages/pipeline/src/parsers/ddex_orders/index.ts | 14 ++++++++------ packages/pipeline/src/parsers/oasis_orders/index.ts | 4 ++-- packages/pipeline/src/parsers/paradex_orders/index.ts | 4 ++-- packages/pipeline/test/parsers/ddex_orders/index_test.ts | 14 ++++++++------ packages/pipeline/test/parsers/oasis_orders/index_test.ts | 4 ++-- .../pipeline/test/parsers/paradex_orders/index_test.ts | 4 ++-- 6 files changed, 24 insertions(+), 20 deletions(-) (limited to 'packages') diff --git a/packages/pipeline/src/parsers/ddex_orders/index.ts b/packages/pipeline/src/parsers/ddex_orders/index.ts index 52a998f9f..d7b97efbe 100644 --- a/packages/pipeline/src/parsers/ddex_orders/index.ts +++ b/packages/pipeline/src/parsers/ddex_orders/index.ts @@ -54,12 +54,14 @@ export function parseDdexOrder( tokenOrder.orderType = orderType; tokenOrder.price = price; - tokenOrder.baseAssetSymbol = ddexMarket.baseToken; - tokenOrder.baseAssetAddress = ddexMarket.baseTokenAddress; - tokenOrder.baseVolume = price.times(amount); + // ddex currently confuses quote and base assets. + // We switch them here to maintain our internal consistency. + tokenOrder.baseAssetSymbol = ddexMarket.quoteToken; + tokenOrder.baseAssetAddress = ddexMarket.quoteTokenAddress; + tokenOrder.baseVolume = amount; - tokenOrder.quoteAssetSymbol = ddexMarket.quoteToken; - tokenOrder.quoteAssetAddress = ddexMarket.quoteTokenAddress; - tokenOrder.quoteVolume = amount; + tokenOrder.quoteAssetSymbol = ddexMarket.baseToken; + tokenOrder.quoteAssetAddress = ddexMarket.baseTokenAddress; + tokenOrder.quoteVolume = price.times(amount); return tokenOrder; } diff --git a/packages/pipeline/src/parsers/oasis_orders/index.ts b/packages/pipeline/src/parsers/oasis_orders/index.ts index 7aafbf460..13997f31b 100644 --- a/packages/pipeline/src/parsers/oasis_orders/index.ts +++ b/packages/pipeline/src/parsers/oasis_orders/index.ts @@ -62,10 +62,10 @@ export function parseOasisOrder( tokenOrder.baseAssetSymbol = oasisMarket.base; tokenOrder.baseAssetAddress = null; // Oasis doesn't provide address information - tokenOrder.baseVolume = price.times(amount); + tokenOrder.baseVolume = amount; tokenOrder.quoteAssetSymbol = oasisMarket.quote; tokenOrder.quoteAssetAddress = null; // Oasis doesn't provide address information - tokenOrder.quoteVolume = amount; + tokenOrder.quoteVolume = price.times(amount); return tokenOrder; } diff --git a/packages/pipeline/src/parsers/paradex_orders/index.ts b/packages/pipeline/src/parsers/paradex_orders/index.ts index 7966658a7..5ceeb64a4 100644 --- a/packages/pipeline/src/parsers/paradex_orders/index.ts +++ b/packages/pipeline/src/parsers/paradex_orders/index.ts @@ -57,10 +57,10 @@ export function parseParadexOrder( tokenOrder.baseAssetSymbol = paradexMarket.baseToken; tokenOrder.baseAssetAddress = paradexMarket.baseTokenAddress as string; - tokenOrder.baseVolume = price.times(amount); + tokenOrder.baseVolume = amount; tokenOrder.quoteAssetSymbol = paradexMarket.quoteToken; tokenOrder.quoteAssetAddress = paradexMarket.quoteTokenAddress as string; - tokenOrder.quoteVolume = amount; + tokenOrder.quoteVolume = price.times(amount); return tokenOrder; } diff --git a/packages/pipeline/test/parsers/ddex_orders/index_test.ts b/packages/pipeline/test/parsers/ddex_orders/index_test.ts index 9f4bfe7e3..4a4a86bf8 100644 --- a/packages/pipeline/test/parsers/ddex_orders/index_test.ts +++ b/packages/pipeline/test/parsers/ddex_orders/index_test.ts @@ -39,12 +39,14 @@ describe('ddex_orders', () => { expected.observedTimestamp = observedTimestamp; expected.orderType = 'bid'; expected.price = new BigNumber(0.5); - expected.baseAssetSymbol = 'DEF'; - expected.baseAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; - expected.baseVolume = new BigNumber(5); - expected.quoteAssetSymbol = 'ABC'; - expected.quoteAssetAddress = '0x0000000000000000000000000000000000000000'; - expected.quoteVolume = new BigNumber(10); + // ddex currently confuses base and quote assets. + // Switch them to maintain our internal consistency. + expected.baseAssetSymbol = 'ABC'; + expected.baseAssetAddress = '0x0000000000000000000000000000000000000000'; + expected.baseVolume = new BigNumber(10); + expected.quoteAssetSymbol = 'DEF'; + expected.quoteAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; + expected.quoteVolume = new BigNumber(5); const actual = parseDdexOrder(ddexMarket, observedTimestamp, orderType, source, ddexOrder); expect(actual).deep.equal(expected); diff --git a/packages/pipeline/test/parsers/oasis_orders/index_test.ts b/packages/pipeline/test/parsers/oasis_orders/index_test.ts index 9e8ba9a40..433bfb665 100644 --- a/packages/pipeline/test/parsers/oasis_orders/index_test.ts +++ b/packages/pipeline/test/parsers/oasis_orders/index_test.ts @@ -37,10 +37,10 @@ describe('oasis_orders', () => { expected.price = new BigNumber(0.5); expected.baseAssetSymbol = 'DEF'; expected.baseAssetAddress = null; - expected.baseVolume = new BigNumber(5); + expected.baseVolume = new BigNumber(10); expected.quoteAssetSymbol = 'ABC'; expected.quoteAssetAddress = null; - expected.quoteVolume = new BigNumber(10); + expected.quoteVolume = new BigNumber(5); const actual = parseOasisOrder(oasisMarket, observedTimestamp, orderType, source, oasisOrder); expect(actual).deep.equal(expected); diff --git a/packages/pipeline/test/parsers/paradex_orders/index_test.ts b/packages/pipeline/test/parsers/paradex_orders/index_test.ts index 1522806bf..6b811b90d 100644 --- a/packages/pipeline/test/parsers/paradex_orders/index_test.ts +++ b/packages/pipeline/test/parsers/paradex_orders/index_test.ts @@ -42,10 +42,10 @@ describe('paradex_orders', () => { expected.price = new BigNumber(0.1245); expected.baseAssetSymbol = 'DEF'; expected.baseAssetAddress = '0xb45df06e38540a675fdb5b598abf2c0dbe9d6b81'; - expected.baseVolume = new BigNumber(412 * 0.1245); + expected.baseVolume = new BigNumber(412); expected.quoteAssetSymbol = 'ABC'; expected.quoteAssetAddress = '0x0000000000000000000000000000000000000000'; - expected.quoteVolume = new BigNumber(412); + expected.quoteVolume = new BigNumber(412 * 0.1245); const actual = parseParadexOrder(paradexMarket, observedTimestamp, orderType, source, paradexOrder); expect(actual).deep.equal(expected); -- cgit v1.2.3 From d9b58848346a4be41684eea244e393afaab6a617 Mon Sep 17 00:00:00 2001 From: kao Date: Thu, 13 Dec 2018 13:33:46 -0800 Subject: Respond to CR --- packages/order-watcher/README.md | 81 ++++++++ .../src/order_watcher/order_watcher_websocket.ts | 209 ++++++++++----------- .../order-watcher/src/schemas/websocket_schemas.ts | 32 ++++ .../src/schemas/websocket_utf8_message_schema.ts | 8 - packages/order-watcher/src/types.ts | 30 ++- .../test/order_watcher_websocket_test.ts | 79 +++++--- 6 files changed, 295 insertions(+), 144 deletions(-) create mode 100644 packages/order-watcher/src/schemas/websocket_schemas.ts delete mode 100644 packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts (limited to 'packages') diff --git a/packages/order-watcher/README.md b/packages/order-watcher/README.md index c0b99b272..7eae0ae16 100644 --- a/packages/order-watcher/README.md +++ b/packages/order-watcher/README.md @@ -4,6 +4,9 @@ An order watcher daemon that watches for order validity. #### Read the wiki [article](https://0xproject.com/wiki#0x-OrderWatcher). +OrderWatcher also comes with a WebSocket server to provide language-agnostic access +to order watching functionality. We used the [WebSocket Client and Server Implementation for Node](https://www.npmjs.com/package/websocket). + ## Installation **Install** @@ -26,6 +29,84 @@ If your project is in [TypeScript](https://www.typescriptlang.org/), add the fol } ``` +## Using the WebSocket Server + +**Setup** + +**Environmental Variables** +Several environmental variables can be set to configure the server: + +* `ORDER_WATCHER_HTTP_PORT` specifies the port that the http server will listen on + and accept connections from. When this is not set, we default to 8080. + +**Requests** +The server accepts three types of requests: `ADD_ORDER`, `REMOVE_ORDER` and `GET_STATS`. These mirror what the underlying OrderWatcher does. You can read more in the [wiki](https://0xproject.com/wiki#0x-OrderWatcher). Unlike the OrderWatcher, it does not expose any subscribe or unsubscribe functionality because the client implicitly subscribes and unsubscribes by connecting to the server. + +The first step for making a request is establishing a connection with the server. In Javascript: + +``` +var W3CWebSocket = require('websocket').w3cwebsocket; +wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080'); +``` + +In Python, you could use the [websocket-client library](http://pypi.python.org/pypi/websocket-client/) and run: + +``` +from websocket import create_connection +wsClient = create_connection("ws://127.0.0.1:8080") +``` + +With the connection established, you prepare the payload for your request. The payload is a json object with the following structure: + +* For `GET_STATE`, the payload is `{ action: 'GET_STATS }`. +* For `ADD_ORDER`, use `{ action: 'ADD_ORDER', signedOrder: }`. +* For `REMOVE_ORDER`, use `{ action: 'REMOVE_ORDER', orderHash: }`. + +Next, convert the payload to a string and send it through the connection. +In Javascript: + +``` +const addOrderPayload = { + action: 'ADD_ORDER', + signedOrder: , +}; +wsClient.send(JSON.stringify(addOrderPayload)); +``` + +In Python: + +``` +import json +remove_order_payload = { + 'action': 'REMOVE_ORDER', + 'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e', +} +wsClient.send(json.dumps(remove_order_payload)); +``` + +**Response** +The server responds to all requests in a similar format. In the data field, you'll find another json object that has been converted into a string. This json object contains the following fields: + +* `action`: The action the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, action will be listed as `UPDATE`. +* `success`: `true` or `false`; Indicates whether the server handled the request without problems. +* `result`: This field varies based on the action. `UPDATE` responses contained the new order state. `GET_STATS` responses contain the current order count. When there are errors, the error messages are stored in here. + +In Javascript, the responses can be parsed using the `onmessage` callback: + +``` +wsClient.onmessage = (msg) => { + const responseData = JSON.parse(msg.data); + const action = responseData.action +}; +``` + +In Python, `recv` is a lightweight way to receive a response: + +``` +result = wsClient.recv() +action = result.action +``` + ## Contributing We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository. diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts index 84afc4000..806c7c6a5 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts @@ -1,60 +1,57 @@ import { ContractAddresses } from '@0x/contract-addresses'; -import { SignedOrder } from '@0x/types'; +import { OrderState, SignedOrder } from '@0x/types'; import { BigNumber, logUtils } from '@0x/utils'; import { Provider } from 'ethereum-types'; import * as http from 'http'; import * as WebSocket from 'websocket'; -import { webSocketUtf8MessageSchema } from '../schemas/websocket_utf8_message_schema'; -import { OnOrderStateChangeCallback, OrderWatcherConfig } from '../types'; +import { webSocketRequestSchema, webSocketUtf8MessageSchema } from '../schemas/websocket_schemas'; +import { + OnOrderStateChangeCallback, + OrderWatcherAction, + OrderWatcherConfig, + WebSocketRequest, + WebSocketResponse, +} from '../types'; import { assert } from '../utils/assert'; import { OrderWatcher } from './order_watcher'; const DEFAULT_HTTP_PORT = 8080; -const enum OrderWatcherAction { - // Actions initiated by the user. - getStats = 'getStats', - addOrderAsync = 'addOrderAsync', - removeOrder = 'removeOrder', - // These are spontaneous; they are primarily orderstate changes. - orderWatcherUpdate = 'orderWatcherUpdate', - // `subscribe` and `unsubscribe` are methods of OrderWatcher, but we don't - // need to expose them to the WebSocket server user because the user implicitly - // subscribes and unsubscribes by connecting and disconnecting from the server. -} - -// Users have to create a json object of this format and attach it to -// the data field of their WebSocket message to interact with this server. -interface WebSocketRequestData { - action: OrderWatcherAction; - params: any; -} - -// Users should expect a json object of this format in the data field -// of the WebSocket messages that this server sends out. -interface WebSocketResponseData { - action: OrderWatcherAction; - success: number; - result: any; -} - // Wraps the OrderWatcher functionality in a WebSocket server. Motivations: // 1) Users can watch orders via non-typescript programs. // 2) Better encapsulation so that users can work export class OrderWatcherWebSocketServer { - public httpServer: http.Server; - public readonly _orderWatcher: OrderWatcher; + public readonly _orderWatcher: OrderWatcher; // public for testing + private readonly _httpServer: http.Server; private readonly _connectionStore: Set; private readonly _wsServer: WebSocket.server; /** - * Instantiate a new web socket server which provides OrderWatcher functionality - * @param provider Web3 provider to use for JSON RPC calls (for OrderWatcher) - * @param networkId NetworkId to watch orders on (for OrderWatcher) + * Recover types lost when the payload is stringified. + */ + private static _parseSignedOrder(rawRequest: any): SignedOrder { + const bigNumberFields = [ + 'salt', + 'makerFee', + 'takerFee', + 'makerAssetAmount', + 'takerAssetAmount', + 'expirationTimeSeconds', + ]; + for (const field of bigNumberFields) { + rawRequest[field] = new BigNumber(rawRequest[field]); + } + return rawRequest; + } + + /** + * Instantiate a new WebSocket server which provides OrderWatcher functionality + * @param provider Web3 provider to use for JSON RPC calls. + * @param networkId NetworkId to watch orders on. * @param contractAddresses Optional contract addresses. Defaults to known - * addresses based on networkId (for OrderWatcher) - * @param partialConfig Optional configurations (for OrderWatcher) + * addresses based on networkId. + * @param partialConfig Optional configurations. */ constructor( provider: Provider, @@ -64,85 +61,98 @@ export class OrderWatcherWebSocketServer { ) { this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); this._connectionStore = new Set(); - this.httpServer = http.createServer(); + this._httpServer = http.createServer(); this._wsServer = new WebSocket.server({ - httpServer: this.httpServer, + httpServer: this._httpServer, + // Avoid setting autoAcceptConnections to true as it defeats all + // standard cross-origin protection facilities built into the protocol + // and the browser. Also ensures that a request event is emitted by + // the server whenever a new WebSocket request is made. autoAcceptConnections: false, }); - this._wsServer.on('request', (request: any) => { + this._wsServer.on('request', async (request: any) => { // Designed for usage pattern where client and server are run on the same // machine by the same user. As such, no security checks are in place. const connection: WebSocket.connection = request.accept(null, request.origin); logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); - connection.on('message', this._messageCallback.bind(this, connection)); - connection.on('close', this._closeCallback.bind(this, connection)); + connection.on('message', await this._onMessageCallbackAsync.bind(this, connection)); + connection.on('close', this._onCloseCallback.bind(this, connection)); this._connectionStore.add(connection); }); // Have the WebSocket server subscribe to the OrderWatcher to receive updates. // These updates are then broadcast to clients in the _connectionStore. - this._orderWatcher.subscribe(this._broadcastCallback); + const broadcastCallback: OnOrderStateChangeCallback = this._broadcastCallback.bind(this); + this._orderWatcher.subscribe(broadcastCallback); } /** * Activates the WebSocket server by having its HTTP server start listening. */ public listen(): void { - this.httpServer.listen(DEFAULT_HTTP_PORT, () => { - logUtils.log(`${new Date()} [Server] Listening on port ${DEFAULT_HTTP_PORT}`); + const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; + this._httpServer.listen(port, () => { + logUtils.log(`${new Date()} [Server] Listening on port ${port}`); }); } + /** + * Deactivates the WebSocket server by stopping the HTTP server from accepting + * new connections. + */ public close(): void { - this.httpServer.close(); + this._httpServer.close(); } - private _messageCallback(connection: WebSocket.connection, message: any): void { - assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); - const requestData: WebSocketRequestData = JSON.parse(message.utf8Data); - const responseData = this._routeRequest(requestData); - logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(responseData)}`); - connection.sendUTF(JSON.stringify(responseData)); + private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { + const response: WebSocketResponse = { + action: null, + success: false, + result: null, + }; + try { + assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); + const request: WebSocketRequest = JSON.parse(message.utf8Data); + assert.doesConformToSchema('request', request, webSocketRequestSchema); + response.action = request.action; + response.success = true; + response.result = await this._routeRequestAsync(request); + } catch (err) { + response.result = err.toString(); + } + logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); + connection.sendUTF(JSON.stringify(response)); } - private _closeCallback(connection: WebSocket.connection): void { + private _onCloseCallback(connection: WebSocket.connection): void { this._connectionStore.delete(connection); logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); } - private _routeRequest(requestData: WebSocketRequestData): WebSocketResponseData { - const responseData: WebSocketResponseData = { - action: requestData.action, - success: 0, - result: undefined, - }; - - try { - logUtils.log(`${new Date()} [Server] Request received: ${requestData.action}`); - switch (requestData.action) { - case 'addOrderAsync': { - const signedOrder: SignedOrder = this._parseSignedOrder(requestData); - // tslint:disable-next-line:no-floating-promises - this._orderWatcher.addOrderAsync(signedOrder); // Ok to fireNforget - break; - } - case 'removeOrder': { - this._orderWatcher.removeOrder(requestData.params.orderHash); - break; - } - case 'getStats': { - responseData.result = this._orderWatcher.getStats(); - break; - } - default: - throw new Error(`[Server] Invalid request action: ${requestData.action}`); + private async _routeRequestAsync(request: WebSocketRequest): Promise { + logUtils.log(`${new Date()} [Server] Request received: ${request.action}`); + let result = null; + switch (request.action) { + case OrderWatcherAction.AddOrder: { + const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder(request.signedOrder); + await this._orderWatcher.addOrderAsync(signedOrder); + break; } - responseData.success = 1; - } catch (err) { - responseData.result = { error: err.toString() }; + case OrderWatcherAction.RemoveOrder: { + const orderHash = request.orderHash || '_'; + this._orderWatcher.removeOrder(orderHash); + break; + } + case OrderWatcherAction.GetStats: { + result = this._orderWatcher.getStats(); + break; + } + default: + // Should never reach here. Should be caught by JSON schema check. + throw new Error(`[Server] Invalid request action: ${request.action}`); } - return responseData; + return result; } /** @@ -150,35 +160,14 @@ export class OrderWatcherWebSocketServer { * we do not support clients subscribing to only a subset of orders. As such, * Client B will be notified of changes to an order that Client A added. */ - private readonly _broadcastCallback: OnOrderStateChangeCallback = (err, orderState) => { + private _broadcastCallback(err: Error | null, orderState?: OrderState): void { this._connectionStore.forEach((connection: WebSocket.connection) => { - const responseData: WebSocketResponseData = { - action: OrderWatcherAction.orderWatcherUpdate, - success: 1, + const response: WebSocketResponse = { + action: OrderWatcherAction.Update, + success: true, result: orderState || err, }; - connection.sendUTF(JSON.stringify(responseData)); + connection.sendUTF(JSON.stringify(response)); }); - // tslint:disable-next-line:semicolon - }; // tslint thinks this is a class method, It's actally a property that holds a function. - - /** - * Recover types lost when the payload is stringified. - */ - private readonly _parseSignedOrder = (requestData: WebSocketRequestData) => { - const signedOrder = requestData.params.signedOrder; - const bigNumberFields = [ - 'salt', - 'makerFee', - 'takerFee', - 'makerAssetAmount', - 'takerAssetAmount', - 'expirationTimeSeconds', - ]; - for (const field of bigNumberFields) { - signedOrder[field] = new BigNumber(signedOrder[field]); - } - return signedOrder; - // tslint:disable-next-line:semicolon - }; // tslint thinks this is a class method, It's actally a property that holds a function. + } } diff --git a/packages/order-watcher/src/schemas/websocket_schemas.ts b/packages/order-watcher/src/schemas/websocket_schemas.ts new file mode 100644 index 000000000..c250d12f1 --- /dev/null +++ b/packages/order-watcher/src/schemas/websocket_schemas.ts @@ -0,0 +1,32 @@ +export const webSocketUtf8MessageSchema = { + id: '/webSocketUtf8MessageSchema', + properties: { + utf8Data: { type: 'string' }, + }, + type: 'object', + required: ['utf8Data'], +}; + +export const webSocketRequestSchema = { + id: '/webSocketRequestSchema', + properties: { + action: { enum: ['GET_STATS', 'ADD_ORDER', 'REMOVE_ORDER'] }, + signedOrder: { $ref: '/signedOrderSchema' }, + orderHash: { type: 'string' }, + }, + anyOf: [ + { + properties: { action: { enum: ['ADD_ORDER'] } }, + required: ['signedOrder'], + }, + { + properties: { action: { enum: ['REMOVE_ORDER'] } }, + required: ['orderHash'], + }, + { + properties: { action: { enum: ['GET_STATS'] } }, + required: [], + }, + ], + type: 'object', +}; diff --git a/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts b/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts deleted file mode 100644 index 0a0eed407..000000000 --- a/packages/order-watcher/src/schemas/websocket_utf8_message_schema.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const webSocketUtf8MessageSchema = { - id: '/WebSocketUtf8MessageSchema', - properties: { - utf8Data: { type: 'string' }, - }, - type: 'object', - required: ['utf8Data'], -}; diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index 8078dd971..7f6219732 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -1,4 +1,4 @@ -import { OrderState } from '@0x/types'; +import { OrderState, SignedOrder } from '@0x/types'; import { LogEntryEvent } from 'ethereum-types'; export enum OrderWatcherError { @@ -31,3 +31,31 @@ export enum InternalOrderWatcherError { ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY', WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY', } + +export enum OrderWatcherAction { + // Actions initiated by the user. + GetStats = 'GET_STATS', + AddOrder = 'ADD_ORDER', + RemoveOrder = 'REMOVE_ORDER', + // These are spontaneous; they are primarily orderstate changes. + Update = 'UPDATE', + // `subscribe` and `unsubscribe` are methods of OrderWatcher, but we don't + // need to expose them to the WebSocket server user because the user implicitly + // subscribes and unsubscribes by connecting and disconnecting from the server. +} + +// Users have to create a json object of this format and attach it to +// the data field of their WebSocket message to interact with the server. +export interface WebSocketRequest { + action: OrderWatcherAction; + signedOrder?: SignedOrder; + orderHash?: string; +} + +// Users should expect a json object of this format in the data field +// of the WebSocket messages that the server sends out. +export interface WebSocketResponse { + action: OrderWatcherAction | null; + success: boolean; + result: any; +} diff --git a/packages/order-watcher/test/order_watcher_websocket_test.ts b/packages/order-watcher/test/order_watcher_websocket_test.ts index e7b18e44b..a9e72ce21 100644 --- a/packages/order-watcher/test/order_watcher_websocket_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_test.ts @@ -41,11 +41,11 @@ describe.only('OrderWatcherWebSocket', async () => { let zrxTokenAddress: string; let signedOrder: SignedOrder; let orderHash: string; - let addOrderPayload: { action: string; params: { signedOrder: SignedOrder } }; - let removeOrderPayload: { action: string; params: { orderHash: string } }; + let addOrderPayload: { action: string; signedOrder: SignedOrder }; + let removeOrderPayload: { action: string; orderHash: string }; const decimals = constants.ZRX_DECIMALS; const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); - // createFillableSignedOrderAsync is Promise-based, which forces us + // HACK: createFillableSignedOrderAsync is Promise-based, which forces us // to use Promises instead of the done() callbacks for tests. // onmessage callback must thus be wrapped as a Promise. const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => @@ -88,12 +88,12 @@ describe.only('OrderWatcherWebSocket', async () => { ); orderHash = orderHashUtils.getOrderHashHex(signedOrder); addOrderPayload = { - action: 'addOrderAsync', - params: { signedOrder }, + action: 'ADD_ORDER', + signedOrder, }; removeOrderPayload = { - action: 'removeOrder', - params: { orderHash }, + action: 'REMOVE_ORDER', + orderHash, }; // Prepare OrderWatcher WebSocket server @@ -118,14 +118,13 @@ describe.only('OrderWatcherWebSocket', async () => { it('responds to getStats requests correctly', (done: any) => { const payload = { - action: 'getStats', - params: {}, + action: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); wsClient.onmessage = (msg: any) => { const responseData = JSON.parse(msg.data); - expect(responseData.action).to.be.eq('getStats'); - expect(responseData.success).to.be.eq(1); + expect(responseData.action).to.be.eq('GET_STATS'); + expect(responseData.success).to.be.true(); expect(responseData.result.orderCount).to.be.eq(0); done(); }; @@ -133,23 +132,53 @@ describe.only('OrderWatcherWebSocket', async () => { it('throws an error when an invalid action is attempted', async () => { const invalidActionPayload = { - action: 'badAction', - params: {}, + action: 'BAD_ACTION', }; wsClient.onopen = () => wsClient.send(JSON.stringify(invalidActionPayload)); const errorMsg = await _onMessageAsync(wsClient); const errorData = JSON.parse(errorMsg.data); - expect(errorData.action).to.be.eq('badAction'); - expect(errorData.success).to.be.eq(0); - expect(errorData.result.error).to.be.eq('Error: [Server] Invalid request action: badAction'); + // tslint:disable-next-line:no-unused-expression + expect(errorData.action).to.be.null; + expect(errorData.success).to.be.false(); + expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); }); - it('executes addOrderAsync and removeOrder requests correctly', async () => { + it('throws an error when we try to add an order without a signedOrder', async () => { + const noSignedOrderAddOrderPayload = { + action: 'ADD_ORDER', + orderHash: '0x0', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.action).to.be.null; + expect(errorData.success).to.be.false(); + expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when we try to add a bad signedOrder', async () => { + const invalidAddOrderPayload = { + action: 'ADD_ORDER', + signedOrder: { + makerAddress: '0x0', + }, + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.action).to.be.null; + expect(errorData.success).to.be.false(); + expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); + }); + + it('executes addOrder and removeOrder requests correctly', async () => { wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); const addOrderMsg = await _onMessageAsync(wsClient); const addOrderData = JSON.parse(addOrderMsg.data); - expect(addOrderData.action).to.be.eq('addOrderAsync'); - expect(addOrderData.success).to.be.eq(1); + expect(addOrderData.action).to.be.eq('ADD_ORDER'); + expect(addOrderData.success).to.be.true(); expect((wsServer._orderWatcher as any)._orderByOrderHash).to.deep.include({ [orderHash]: signedOrder, }); @@ -157,8 +186,8 @@ describe.only('OrderWatcherWebSocket', async () => { wsClient.send(JSON.stringify(removeOrderPayload)); const removeOrderMsg = await _onMessageAsync(wsClient); const removeOrderData = JSON.parse(removeOrderMsg.data); - expect(removeOrderData.action).to.be.eq('removeOrder'); - expect(removeOrderData.success).to.be.eq(1); + expect(removeOrderData.action).to.be.eq('REMOVE_ORDER'); + expect(removeOrderData.success).to.be.true(); expect((wsServer._orderWatcher as any)._orderByOrderHash).to.not.deep.include({ [orderHash]: signedOrder, }); @@ -175,8 +204,8 @@ describe.only('OrderWatcherWebSocket', async () => { // Ensure that orderStateInvalid message is received. const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); - expect(orderWatcherUpdateData.action).to.be.eq('orderWatcherUpdate'); - expect(orderWatcherUpdateData.success).to.be.eq(1); + expect(orderWatcherUpdateData.action).to.be.eq('UPDATE'); + expect(orderWatcherUpdateData.success).to.be.true(); const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; expect(invalidOrderState.isValid).to.be.false(); expect(invalidOrderState.orderHash).to.be.eq(orderHash); @@ -198,8 +227,8 @@ describe.only('OrderWatcherWebSocket', async () => { takerAddress, ); const nonZeroMakerFeeOrderPayload = { - action: 'addOrderAsync', - params: { nonZeroMakerFeeSignedOrder }, + action: 'ADD_ORDER', + signedOrder: nonZeroMakerFeeSignedOrder, }; // Set up a second client and have it add the order -- cgit v1.2.3 From 67422db4bd0b194296a1a584af7ead089e281715 Mon Sep 17 00:00:00 2001 From: Steve Klebanoff Date: Fri, 14 Dec 2018 15:58:23 -0800 Subject: fix semicolon and apply prettier --- packages/instant/src/components/order_details.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/instant/src/components/order_details.tsx b/packages/instant/src/components/order_details.tsx index 33a115044..9c10ef9e6 100644 --- a/packages/instant/src/components/order_details.tsx +++ b/packages/instant/src/components/order_details.tsx @@ -133,8 +133,16 @@ export class OrderDetails extends React.Component { } const pricePerTokenWei = this._pricePerTokenWei(); if (pricePerTokenWei) { - const atPriceDisplay = @ {this._displayAmount(baseCurrency, pricePerTokenWei)}; - numTokensWithSymbol = {numTokensWithSymbol} {atPriceDisplay} + const atPriceDisplay = ( + + @ {this._displayAmount(baseCurrency, pricePerTokenWei)} + + ); + numTokensWithSymbol = ( + + {numTokensWithSymbol} {atPriceDisplay} + + ); } return numTokensWithSymbol; } -- cgit v1.2.3 From 6bb2ef923894a3572c3fa824c3bf1a69759eb43a Mon Sep 17 00:00:00 2001 From: kao Date: Sat, 15 Dec 2018 01:23:08 -0800 Subject: Respond to CR --- packages/order-watcher/README.md | 37 +++++--- .../src/order_watcher/order_watcher_websocket.ts | 69 ++++++++------ .../order-watcher/src/schemas/websocket_schemas.ts | 53 ++++++++--- packages/order-watcher/src/types.ts | 50 ++++++++-- .../test/order_watcher_websocket_test.ts | 101 ++++++++++++++------- 5 files changed, 209 insertions(+), 101 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/README.md b/packages/order-watcher/README.md index 7eae0ae16..aad90a59a 100644 --- a/packages/order-watcher/README.md +++ b/packages/order-watcher/README.md @@ -5,7 +5,7 @@ An order watcher daemon that watches for order validity. #### Read the wiki [article](https://0xproject.com/wiki#0x-OrderWatcher). OrderWatcher also comes with a WebSocket server to provide language-agnostic access -to order watching functionality. We used the [WebSocket Client and Server Implementation for Node](https://www.npmjs.com/package/websocket). +to order watching functionality. We used the [WebSocket Client and Server Implementation for Node](https://www.npmjs.com/package/websocket). The server sends and receives messages that conform to the [JSON RPC specifications](https://www.jsonrpc.org/specification). ## Installation @@ -46,7 +46,7 @@ The first step for making a request is establishing a connection with the server ``` var W3CWebSocket = require('websocket').w3cwebsocket; -wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080'); +wsClient = new W3CWebSocket('ws://127.0.0.1:8080'); ``` In Python, you could use the [websocket-client library](http://pypi.python.org/pypi/websocket-client/) and run: @@ -56,19 +56,22 @@ from websocket import create_connection wsClient = create_connection("ws://127.0.0.1:8080") ``` -With the connection established, you prepare the payload for your request. The payload is a json object with the following structure: +With the connection established, you prepare the payload for your request. The payload is a json object with a format established by the [JSON RPC specification](https://www.jsonrpc.org/specification): -* For `GET_STATE`, the payload is `{ action: 'GET_STATS }`. -* For `ADD_ORDER`, use `{ action: 'ADD_ORDER', signedOrder: }`. -* For `REMOVE_ORDER`, use `{ action: 'REMOVE_ORDER', orderHash: }`. +* `id`: All requests require you to specify a string as an id. When the server responds to the request, it provides an id as well to allow you to determine which request it is responding to. +* `jsonrpc`: This is always the string `'2.0'`. +* `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'`, and `'GET_STATS'`. +* `params`: These contain the parameters needed by OrderWatcher to execute the method you called. For `ADD_ORDER`, provide `{ signedOrder: }`. For `REMOVE_ORDER`, provide `{ orderHash: }`. For `GET_STATS`, no parameters are needed, so you may leave this empty. Next, convert the payload to a string and send it through the connection. In Javascript: ``` const addOrderPayload = { - action: 'ADD_ORDER', - signedOrder: , + id: 'order32', + jsonrpc: '2.0', + method: 'ADD_ORDER', + params: { signedOrder: }, }; wsClient.send(JSON.stringify(addOrderPayload)); ``` @@ -78,8 +81,10 @@ In Python: ``` import json remove_order_payload = { - 'action': 'REMOVE_ORDER', - 'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e', + 'id': 'order33', + 'jsonrpc': '2.0', + 'method': 'REMOVE_ORDER', + 'params': {'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e'}, } wsClient.send(json.dumps(remove_order_payload)); ``` @@ -87,16 +92,18 @@ wsClient.send(json.dumps(remove_order_payload)); **Response** The server responds to all requests in a similar format. In the data field, you'll find another json object that has been converted into a string. This json object contains the following fields: -* `action`: The action the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, action will be listed as `UPDATE`. -* `success`: `true` or `false`; Indicates whether the server handled the request without problems. -* `result`: This field varies based on the action. `UPDATE` responses contained the new order state. `GET_STATS` responses contain the current order count. When there are errors, the error messages are stored in here. +* `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is `null`. +* `jsonrpc`: Always `'2.0'`. +* `method`: The method the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, method will be listed as `UPDATE`. +* `result`: This field varies based on the method. `UPDATE` responses contained the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is `null`. +* `error`: When there is an error executing a request, the error message is listed here. When the server responds successfully, this field is `null`. In Javascript, the responses can be parsed using the `onmessage` callback: ``` wsClient.onmessage = (msg) => { const responseData = JSON.parse(msg.data); - const action = responseData.action + const method = responseData.method }; ``` @@ -104,7 +111,7 @@ In Python, `recv` is a lightweight way to receive a response: ``` result = wsClient.recv() -action = result.action +method = result.method ``` ## Contributing diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts index 806c7c6a5..7a88597ef 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts @@ -7,9 +7,10 @@ import * as WebSocket from 'websocket'; import { webSocketRequestSchema, webSocketUtf8MessageSchema } from '../schemas/websocket_schemas'; import { + GetStatsResult, OnOrderStateChangeCallback, - OrderWatcherAction, OrderWatcherConfig, + OrderWatcherMethod, WebSocketRequest, WebSocketResponse, } from '../types'; @@ -18,12 +19,13 @@ import { assert } from '../utils/assert'; import { OrderWatcher } from './order_watcher'; const DEFAULT_HTTP_PORT = 8080; +const JSONRPC_VERSION = '2.0'; // Wraps the OrderWatcher functionality in a WebSocket server. Motivations: // 1) Users can watch orders via non-typescript programs. // 2) Better encapsulation so that users can work export class OrderWatcherWebSocketServer { - public readonly _orderWatcher: OrderWatcher; // public for testing + private readonly _orderWatcher: OrderWatcher; private readonly _httpServer: http.Server; private readonly _connectionStore: Set; private readonly _wsServer: WebSocket.server; @@ -66,7 +68,9 @@ export class OrderWatcherWebSocketServer { httpServer: this._httpServer, // Avoid setting autoAcceptConnections to true as it defeats all // standard cross-origin protection facilities built into the protocol - // and the browser. Also ensures that a request event is emitted by + // and the browser. + // Source: https://www.npmjs.com/package/websocket#server-example + // Also ensures that a request event is emitted by // the server whenever a new WebSocket request is made. autoAcceptConnections: false, }); @@ -76,7 +80,7 @@ export class OrderWatcherWebSocketServer { // machine by the same user. As such, no security checks are in place. const connection: WebSocket.connection = request.accept(null, request.origin); logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); - connection.on('message', await this._onMessageCallbackAsync.bind(this, connection)); + connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); connection.on('close', this._onCloseCallback.bind(this, connection)); this._connectionStore.add(connection); }); @@ -106,20 +110,25 @@ export class OrderWatcherWebSocketServer { } private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { - const response: WebSocketResponse = { - action: null, - success: false, - result: null, - }; + let response: WebSocketResponse; try { assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); const request: WebSocketRequest = JSON.parse(message.utf8Data); assert.doesConformToSchema('request', request, webSocketRequestSchema); - response.action = request.action; - response.success = true; - response.result = await this._routeRequestAsync(request); + assert.isString(request.jsonrpc, JSONRPC_VERSION); + response = { + id: request.id, + jsonrpc: JSONRPC_VERSION, + method: request.method, + result: await this._routeRequestAsync(request), + }; } catch (err) { - response.result = err.toString(); + response = { + id: null, + jsonrpc: JSONRPC_VERSION, + method: null, + error: err.toString(), + }; } logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); connection.sendUTF(JSON.stringify(response)); @@ -130,29 +139,28 @@ export class OrderWatcherWebSocketServer { logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); } - private async _routeRequestAsync(request: WebSocketRequest): Promise { - logUtils.log(`${new Date()} [Server] Request received: ${request.action}`); - let result = null; - switch (request.action) { - case OrderWatcherAction.AddOrder: { - const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder(request.signedOrder); + private async _routeRequestAsync(request: WebSocketRequest): Promise { + logUtils.log(`${new Date()} [Server] Request received: ${request.method}`); + switch (request.method) { + case OrderWatcherMethod.AddOrder: { + const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( + request.params.signedOrder, + ); await this._orderWatcher.addOrderAsync(signedOrder); break; } - case OrderWatcherAction.RemoveOrder: { - const orderHash = request.orderHash || '_'; - this._orderWatcher.removeOrder(orderHash); + case OrderWatcherMethod.RemoveOrder: { + this._orderWatcher.removeOrder(request.params.orderHash || 'undefined'); break; } - case OrderWatcherAction.GetStats: { - result = this._orderWatcher.getStats(); + case OrderWatcherMethod.GetStats: { + return this._orderWatcher.getStats(); break; } default: - // Should never reach here. Should be caught by JSON schema check. - throw new Error(`[Server] Invalid request action: ${request.action}`); + // Should never reach here. Should be caught by JSON schema check. } - return result; + return undefined; } /** @@ -163,9 +171,10 @@ export class OrderWatcherWebSocketServer { private _broadcastCallback(err: Error | null, orderState?: OrderState): void { this._connectionStore.forEach((connection: WebSocket.connection) => { const response: WebSocketResponse = { - action: OrderWatcherAction.Update, - success: true, - result: orderState || err, + id: null, + jsonrpc: JSONRPC_VERSION, + method: OrderWatcherMethod.Update, + result: orderState, }; connection.sendUTF(JSON.stringify(response)); }); diff --git a/packages/order-watcher/src/schemas/websocket_schemas.ts b/packages/order-watcher/src/schemas/websocket_schemas.ts index c250d12f1..5e4e1ab74 100644 --- a/packages/order-watcher/src/schemas/websocket_schemas.ts +++ b/packages/order-watcher/src/schemas/websocket_schemas.ts @@ -9,24 +9,53 @@ export const webSocketUtf8MessageSchema = { export const webSocketRequestSchema = { id: '/webSocketRequestSchema', - properties: { - action: { enum: ['GET_STATS', 'ADD_ORDER', 'REMOVE_ORDER'] }, - signedOrder: { $ref: '/signedOrderSchema' }, - orderHash: { type: 'string' }, + type: 'object', + definitions: { + signedOrderParam: { + type: 'object', + properties: { + signedOrder: { $ref: '/signedOrderSchema' }, + }, + required: ['signedOrder'], + }, + orderHashParam: { + type: 'object', + properties: { + orderHash: { $ref: '/hexSchema' }, + }, + required: ['orderHash'], + }, }, - anyOf: [ + oneOf: [ { - properties: { action: { enum: ['ADD_ORDER'] } }, - required: ['signedOrder'], + type: 'object', + properties: { + id: { type: 'string' }, + jsonrpc: { type: 'string' }, + method: { enum: ['ADD_ORDER'] }, + params: { $ref: '#/definitions/signedOrderParam' }, + }, + required: ['id', 'jsonrpc', 'method', 'params'], }, { - properties: { action: { enum: ['REMOVE_ORDER'] } }, - required: ['orderHash'], + type: 'object', + properties: { + id: { type: 'string' }, + jsonrpc: { type: 'string' }, + method: { enum: ['REMOVE_ORDER'] }, + params: { $ref: '#/definitions/orderHashParam' }, + }, + required: ['id', 'jsonrpc', 'method', 'params'], }, { - properties: { action: { enum: ['GET_STATS'] } }, - required: [], + type: 'object', + properties: { + id: { type: 'string' }, + jsonrpc: { type: 'string' }, + method: { enum: ['GET_STATS'] }, + params: {}, + }, + required: ['id', 'jsonrpc', 'method'], }, ], - type: 'object', }; diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index 7f6219732..90d383660 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -32,8 +32,8 @@ export enum InternalOrderWatcherError { WethNotInTokenRegistry = 'WETH_NOT_IN_TOKEN_REGISTRY', } -export enum OrderWatcherAction { - // Actions initiated by the user. +export enum OrderWatcherMethod { + // Methods initiated by the user. GetStats = 'GET_STATS', AddOrder = 'ADD_ORDER', RemoveOrder = 'REMOVE_ORDER', @@ -46,16 +46,46 @@ export enum OrderWatcherAction { // Users have to create a json object of this format and attach it to // the data field of their WebSocket message to interact with the server. -export interface WebSocketRequest { - action: OrderWatcherAction; - signedOrder?: SignedOrder; - orderHash?: string; +export type WebSocketRequest = AddOrderRequest | RemoveOrderRequest | GetStatsRequest; + +interface AddOrderRequest { + id: string; + jsonrpc: string; + method: OrderWatcherMethod.AddOrder; + params: { signedOrder: SignedOrder }; +} + +interface RemoveOrderRequest { + id: string; + jsonrpc: string; + method: OrderWatcherMethod.RemoveOrder; + params: { orderHash: string }; +} + +interface GetStatsRequest { + id: string; + jsonrpc: string; + method: OrderWatcherMethod.GetStats; } // Users should expect a json object of this format in the data field // of the WebSocket messages that the server sends out. -export interface WebSocketResponse { - action: OrderWatcherAction | null; - success: boolean; - result: any; +export type WebSocketResponse = SuccessfulWebSocketResponse | ErrorWebSocketResponse; + +interface SuccessfulWebSocketResponse { + id: string | null; // id is null for UPDATE + jsonrpc: string; + method: OrderWatcherMethod; + result: OrderState | GetStatsResult | undefined; // result is undefined for ADD_ORDER and REMOVE_ORDER +} + +interface ErrorWebSocketResponse { + id: null; + jsonrpc: string; + method: null; + error: string; +} + +export interface GetStatsResult { + orderCount: number; } diff --git a/packages/order-watcher/test/order_watcher_websocket_test.ts b/packages/order-watcher/test/order_watcher_websocket_test.ts index a9e72ce21..c4d1ede45 100644 --- a/packages/order-watcher/test/order_watcher_websocket_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_test.ts @@ -41,8 +41,10 @@ describe.only('OrderWatcherWebSocket', async () => { let zrxTokenAddress: string; let signedOrder: SignedOrder; let orderHash: string; - let addOrderPayload: { action: string; signedOrder: SignedOrder }; - let removeOrderPayload: { action: string; orderHash: string }; + // Manually encode types rather than use /src/types to mimick real data that user + // would input. Otherwise we would be forced to use enums, which hide problems. + let addOrderPayload: { id: string; jsonrpc: string; method: string; params: { signedOrder: SignedOrder } }; + let removeOrderPayload: { id: string; jsonrpc: string; method: string; params: { orderHash: string } }; const decimals = constants.ZRX_DECIMALS; const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); // HACK: createFillableSignedOrderAsync is Promise-based, which forces us @@ -88,12 +90,16 @@ describe.only('OrderWatcherWebSocket', async () => { ); orderHash = orderHashUtils.getOrderHashHex(signedOrder); addOrderPayload = { - action: 'ADD_ORDER', - signedOrder, + id: 'addOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + params: { signedOrder }, }; removeOrderPayload = { - action: 'REMOVE_ORDER', - orderHash, + id: 'removeOrderPayload', + jsonrpc: '2.0', + method: 'REMOVE_ORDER', + params: { orderHash }, }; // Prepare OrderWatcher WebSocket server @@ -118,48 +124,75 @@ describe.only('OrderWatcherWebSocket', async () => { it('responds to getStats requests correctly', (done: any) => { const payload = { - action: 'GET_STATS', + id: 'getStats', + jsonrpc: '2.0', + method: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); wsClient.onmessage = (msg: any) => { const responseData = JSON.parse(msg.data); - expect(responseData.action).to.be.eq('GET_STATS'); - expect(responseData.success).to.be.true(); + expect(responseData.id).to.be.eq('getStats'); + expect(responseData.jsonrpc).to.be.eq('2.0'); + expect(responseData.method).to.be.eq('GET_STATS'); expect(responseData.result.orderCount).to.be.eq(0); done(); }; }); - it('throws an error when an invalid action is attempted', async () => { - const invalidActionPayload = { - action: 'BAD_ACTION', + it('throws an error when an invalid method is attempted', async () => { + const invalidMethodPayload = { + id: 'invalidMethodPayload', + jsonrpc: '2.0', + method: 'BAD_METHOD', }; - wsClient.onopen = () => wsClient.send(JSON.stringify(invalidActionPayload)); + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); const errorMsg = await _onMessageAsync(wsClient); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression - expect(errorData.action).to.be.null; - expect(errorData.success).to.be.false(); - expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when jsonrpc field missing from request', async () => { + const noJsonRpcPayload = { + id: 'noJsonRpcPayload', + method: 'GET_STATS', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); }); it('throws an error when we try to add an order without a signedOrder', async () => { const noSignedOrderAddOrderPayload = { - action: 'ADD_ORDER', - orderHash: '0x0', + id: 'noSignedOrderAddOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); const errorMsg = await _onMessageAsync(wsClient); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression - expect(errorData.action).to.be.null; - expect(errorData.success).to.be.false(); - expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); }); it('throws an error when we try to add a bad signedOrder', async () => { const invalidAddOrderPayload = { - action: 'ADD_ORDER', + id: 'invalidAddOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', signedOrder: { makerAddress: '0x0', }, @@ -168,27 +201,26 @@ describe.only('OrderWatcherWebSocket', async () => { const errorMsg = await _onMessageAsync(wsClient); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression - expect(errorData.action).to.be.null; - expect(errorData.success).to.be.false(); - expect(errorData.result).to.match(/^Error: Expected request to conform to schema/); + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); }); it('executes addOrder and removeOrder requests correctly', async () => { wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); const addOrderMsg = await _onMessageAsync(wsClient); const addOrderData = JSON.parse(addOrderMsg.data); - expect(addOrderData.action).to.be.eq('ADD_ORDER'); - expect(addOrderData.success).to.be.true(); - expect((wsServer._orderWatcher as any)._orderByOrderHash).to.deep.include({ + expect(addOrderData.method).to.be.eq('ADD_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ [orderHash]: signedOrder, }); wsClient.send(JSON.stringify(removeOrderPayload)); const removeOrderMsg = await _onMessageAsync(wsClient); const removeOrderData = JSON.parse(removeOrderMsg.data); - expect(removeOrderData.action).to.be.eq('REMOVE_ORDER'); - expect(removeOrderData.success).to.be.true(); - expect((wsServer._orderWatcher as any)._orderByOrderHash).to.not.deep.include({ + expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ [orderHash]: signedOrder, }); }); @@ -204,8 +236,7 @@ describe.only('OrderWatcherWebSocket', async () => { // Ensure that orderStateInvalid message is received. const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); - expect(orderWatcherUpdateData.action).to.be.eq('UPDATE'); - expect(orderWatcherUpdateData.success).to.be.true(); + expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; expect(invalidOrderState.isValid).to.be.false(); expect(invalidOrderState.orderHash).to.be.eq(orderHash); @@ -227,7 +258,9 @@ describe.only('OrderWatcherWebSocket', async () => { takerAddress, ); const nonZeroMakerFeeOrderPayload = { - action: 'ADD_ORDER', + id: 'nonZeroMakerFeeOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', signedOrder: nonZeroMakerFeeSignedOrder, }; -- cgit v1.2.3 From 7cafe396de676cec3859c76d6407a0948a8e398e Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 15 Dec 2018 21:34:35 -0800 Subject: Ensure fileName matches class name, fix broadcast --- packages/order-watcher/src/index.ts | 1 + .../src/order_watcher/order_watcher_websocket.ts | 182 ------------- .../order_watcher_websocket_server.ts | 186 +++++++++++++ packages/order-watcher/src/types.ts | 8 +- .../test/order_watcher_websocket_server_test.ts | 288 +++++++++++++++++++++ .../test/order_watcher_websocket_test.ts | 288 --------------------- 6 files changed, 482 insertions(+), 471 deletions(-) delete mode 100644 packages/order-watcher/src/order_watcher/order_watcher_websocket.ts create mode 100644 packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts create mode 100644 packages/order-watcher/test/order_watcher_websocket_server_test.ts delete mode 100644 packages/order-watcher/test/order_watcher_websocket_test.ts (limited to 'packages') diff --git a/packages/order-watcher/src/index.ts b/packages/order-watcher/src/index.ts index 5eeba3e87..5bdef4504 100644 --- a/packages/order-watcher/src/index.ts +++ b/packages/order-watcher/src/index.ts @@ -1,4 +1,5 @@ export { OrderWatcher } from './order_watcher/order_watcher'; +export { OrderWatcherWebSocketServer } from './order_watcher/order_watcher_websocket_server'; export { ExpirationWatcher } from './order_watcher/expiration_watcher'; export { diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts deleted file mode 100644 index 7a88597ef..000000000 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { ContractAddresses } from '@0x/contract-addresses'; -import { OrderState, SignedOrder } from '@0x/types'; -import { BigNumber, logUtils } from '@0x/utils'; -import { Provider } from 'ethereum-types'; -import * as http from 'http'; -import * as WebSocket from 'websocket'; - -import { webSocketRequestSchema, webSocketUtf8MessageSchema } from '../schemas/websocket_schemas'; -import { - GetStatsResult, - OnOrderStateChangeCallback, - OrderWatcherConfig, - OrderWatcherMethod, - WebSocketRequest, - WebSocketResponse, -} from '../types'; -import { assert } from '../utils/assert'; - -import { OrderWatcher } from './order_watcher'; - -const DEFAULT_HTTP_PORT = 8080; -const JSONRPC_VERSION = '2.0'; - -// Wraps the OrderWatcher functionality in a WebSocket server. Motivations: -// 1) Users can watch orders via non-typescript programs. -// 2) Better encapsulation so that users can work -export class OrderWatcherWebSocketServer { - private readonly _orderWatcher: OrderWatcher; - private readonly _httpServer: http.Server; - private readonly _connectionStore: Set; - private readonly _wsServer: WebSocket.server; - /** - * Recover types lost when the payload is stringified. - */ - private static _parseSignedOrder(rawRequest: any): SignedOrder { - const bigNumberFields = [ - 'salt', - 'makerFee', - 'takerFee', - 'makerAssetAmount', - 'takerAssetAmount', - 'expirationTimeSeconds', - ]; - for (const field of bigNumberFields) { - rawRequest[field] = new BigNumber(rawRequest[field]); - } - return rawRequest; - } - - /** - * Instantiate a new WebSocket server which provides OrderWatcher functionality - * @param provider Web3 provider to use for JSON RPC calls. - * @param networkId NetworkId to watch orders on. - * @param contractAddresses Optional contract addresses. Defaults to known - * addresses based on networkId. - * @param partialConfig Optional configurations. - */ - constructor( - provider: Provider, - networkId: number, - contractAddresses?: ContractAddresses, - partialConfig?: Partial, - ) { - this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); - this._connectionStore = new Set(); - this._httpServer = http.createServer(); - this._wsServer = new WebSocket.server({ - httpServer: this._httpServer, - // Avoid setting autoAcceptConnections to true as it defeats all - // standard cross-origin protection facilities built into the protocol - // and the browser. - // Source: https://www.npmjs.com/package/websocket#server-example - // Also ensures that a request event is emitted by - // the server whenever a new WebSocket request is made. - autoAcceptConnections: false, - }); - - this._wsServer.on('request', async (request: any) => { - // Designed for usage pattern where client and server are run on the same - // machine by the same user. As such, no security checks are in place. - const connection: WebSocket.connection = request.accept(null, request.origin); - logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); - connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); - connection.on('close', this._onCloseCallback.bind(this, connection)); - this._connectionStore.add(connection); - }); - - // Have the WebSocket server subscribe to the OrderWatcher to receive updates. - // These updates are then broadcast to clients in the _connectionStore. - const broadcastCallback: OnOrderStateChangeCallback = this._broadcastCallback.bind(this); - this._orderWatcher.subscribe(broadcastCallback); - } - - /** - * Activates the WebSocket server by having its HTTP server start listening. - */ - public listen(): void { - const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; - this._httpServer.listen(port, () => { - logUtils.log(`${new Date()} [Server] Listening on port ${port}`); - }); - } - - /** - * Deactivates the WebSocket server by stopping the HTTP server from accepting - * new connections. - */ - public close(): void { - this._httpServer.close(); - } - - private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { - let response: WebSocketResponse; - try { - assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); - const request: WebSocketRequest = JSON.parse(message.utf8Data); - assert.doesConformToSchema('request', request, webSocketRequestSchema); - assert.isString(request.jsonrpc, JSONRPC_VERSION); - response = { - id: request.id, - jsonrpc: JSONRPC_VERSION, - method: request.method, - result: await this._routeRequestAsync(request), - }; - } catch (err) { - response = { - id: null, - jsonrpc: JSONRPC_VERSION, - method: null, - error: err.toString(), - }; - } - logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); - connection.sendUTF(JSON.stringify(response)); - } - - private _onCloseCallback(connection: WebSocket.connection): void { - this._connectionStore.delete(connection); - logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); - } - - private async _routeRequestAsync(request: WebSocketRequest): Promise { - logUtils.log(`${new Date()} [Server] Request received: ${request.method}`); - switch (request.method) { - case OrderWatcherMethod.AddOrder: { - const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( - request.params.signedOrder, - ); - await this._orderWatcher.addOrderAsync(signedOrder); - break; - } - case OrderWatcherMethod.RemoveOrder: { - this._orderWatcher.removeOrder(request.params.orderHash || 'undefined'); - break; - } - case OrderWatcherMethod.GetStats: { - return this._orderWatcher.getStats(); - break; - } - default: - // Should never reach here. Should be caught by JSON schema check. - } - return undefined; - } - - /** - * Broadcasts OrderState changes to ALL connected clients. At the moment, - * we do not support clients subscribing to only a subset of orders. As such, - * Client B will be notified of changes to an order that Client A added. - */ - private _broadcastCallback(err: Error | null, orderState?: OrderState): void { - this._connectionStore.forEach((connection: WebSocket.connection) => { - const response: WebSocketResponse = { - id: null, - jsonrpc: JSONRPC_VERSION, - method: OrderWatcherMethod.Update, - result: orderState, - }; - connection.sendUTF(JSON.stringify(response)); - }); - } -} diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts new file mode 100644 index 000000000..2d2d9e82e --- /dev/null +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -0,0 +1,186 @@ +import { ContractAddresses } from '@0x/contract-addresses'; +import { OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Provider } from 'ethereum-types'; +import * as http from 'http'; +import * as WebSocket from 'websocket'; + +import { webSocketRequestSchema, webSocketUtf8MessageSchema } from '../schemas/websocket_schemas'; +import { GetStatsResult, OrderWatcherConfig, OrderWatcherMethod, WebSocketRequest, WebSocketResponse } from '../types'; +import { assert } from '../utils/assert'; + +import { OrderWatcher } from './order_watcher'; + +const DEFAULT_HTTP_PORT = 8080; +const JSONRPC_VERSION = '2.0'; + +// Wraps the OrderWatcher functionality in a WebSocket server. Motivations: +// 1) Users can watch orders via non-typescript programs. +// 2) Better encapsulation so that users can work +export class OrderWatcherWebSocketServer { + private readonly _orderWatcher: OrderWatcher; + private readonly _httpServer: http.Server; + private readonly _connectionStore: Set; + private readonly _wsServer: WebSocket.server; + private _jsonRpcRequestId: number; + /** + * Recover types lost when the payload is stringified. + */ + private static _parseSignedOrder(rawRequest: any): SignedOrder { + const bigNumberFields = [ + 'salt', + 'makerFee', + 'takerFee', + 'makerAssetAmount', + 'takerAssetAmount', + 'expirationTimeSeconds', + ]; + for (const field of bigNumberFields) { + rawRequest[field] = new BigNumber(rawRequest[field]); + } + return rawRequest; + } + + /** + * Instantiate a new WebSocket server which provides OrderWatcher functionality + * @param provider Web3 provider to use for JSON RPC calls. + * @param networkId NetworkId to watch orders on. + * @param contractAddresses Optional contract addresses. Defaults to known + * addresses based on networkId. + * @param partialConfig Optional configurations. + */ + constructor( + provider: Provider, + networkId: number, + contractAddresses?: ContractAddresses, + partialConfig?: Partial, + ) { + this._jsonRpcRequestId = 1; + this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); + this._connectionStore = new Set(); + this._httpServer = http.createServer(); + this._wsServer = new WebSocket.server({ + httpServer: this._httpServer, + // Avoid setting autoAcceptConnections to true as it defeats all + // standard cross-origin protection facilities built into the protocol + // and the browser. + // Source: https://www.npmjs.com/package/websocket#server-example + // Also ensures that a request event is emitted by + // the server whenever a new WebSocket request is made. + autoAcceptConnections: false, + }); + + this._wsServer.on('request', async (request: any) => { + // Designed for usage pattern where client and server are run on the same + // machine by the same user. As such, no security checks are in place. + const connection: WebSocket.connection = request.accept(null, request.origin); + logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); + connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); + connection.on('close', this._onCloseCallback.bind(this, connection)); + this._connectionStore.add(connection); + }); + + // Have the WebSocket server subscribe to the OrderWatcher to receive updates. + // These updates are then broadcast to clients in the _connectionStore. + this._orderWatcher.subscribe(this._broadcastCallback.bind(this)); + } + + /** + * Activates the WebSocket server by having its HTTP server start listening. + */ + public listen(): void { + const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; + this._httpServer.listen(port, () => { + logUtils.log(`${new Date()} [Server] Listening on port ${port}`); + }); + } + + /** + * Deactivates the WebSocket server by stopping the HTTP server from accepting + * new connections. + */ + public close(): void { + this._httpServer.close(); + } + + private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { + let response: WebSocketResponse; + try { + assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); + const request: WebSocketRequest = JSON.parse(message.utf8Data); + assert.doesConformToSchema('request', request, webSocketRequestSchema); + assert.isString(request.jsonrpc, JSONRPC_VERSION); + response = { + id: request.id, + jsonrpc: JSONRPC_VERSION, + method: request.method, + result: await this._routeRequestAsync(request), + }; + } catch (err) { + response = { + id: null, + jsonrpc: JSONRPC_VERSION, + method: null, + error: err.toString(), + }; + } + logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); + connection.sendUTF(JSON.stringify(response)); + } + + private _onCloseCallback(connection: WebSocket.connection): void { + this._connectionStore.delete(connection); + logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); + } + + private async _routeRequestAsync(request: WebSocketRequest): Promise { + logUtils.log(`${new Date()} [Server] Request received: ${request.method}`); + switch (request.method) { + case OrderWatcherMethod.AddOrder: { + const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( + request.params.signedOrder, + ); + await this._orderWatcher.addOrderAsync(signedOrder); + break; + } + case OrderWatcherMethod.RemoveOrder: { + this._orderWatcher.removeOrder(request.params.orderHash || 'undefined'); + break; + } + case OrderWatcherMethod.GetStats: { + return this._orderWatcher.getStats(); + break; + } + default: + // Should never reach here. Should be caught by JSON schema check. + } + return undefined; + } + + /** + * Broadcasts OrderState changes to ALL connected clients. At the moment, + * we do not support clients subscribing to only a subset of orders. As such, + * Client B will be notified of changes to an order that Client A added. + */ + private _broadcastCallback(err: Error | null, orderState?: OrderStateValid | OrderStateInvalid | undefined): void { + const method = OrderWatcherMethod.Update; + const response = + err === null + ? { + jsonrpc: JSONRPC_VERSION, + method, + result: orderState, + } + : { + jsonrpc: JSONRPC_VERSION, + method, + error: { + code: -32000, + message: err.message, + }, + }; + this._connectionStore.forEach((connection: WebSocket.connection) => { + connection.sendUTF(JSON.stringify(response)); + }); + } +} diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index 90d383660..ecbebe305 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -83,7 +83,13 @@ interface ErrorWebSocketResponse { id: null; jsonrpc: string; method: null; - error: string; + error: JSONRPCError; +} + +interface JSONRPCError { + code: number; + message: string; + data?: string | object; } export interface GetStatsResult { diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts new file mode 100644 index 000000000..9f9db7b1f --- /dev/null +++ b/packages/order-watcher/test/order_watcher_websocket_server_test.ts @@ -0,0 +1,288 @@ +import { ContractWrappers } from '@0x/contract-wrappers'; +import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { FillScenarios } from '@0x/fill-scenarios'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import 'mocha'; +import * as WebSocket from 'websocket'; + +import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_websocket_server'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { migrateOnceAsync } from './utils/migrate'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +interface WsMessage { + data: string; +} + +describe.only('OrderWatcherWebSocketServer', async () => { + let contractWrappers: ContractWrappers; + let wsServer: OrderWatcherWebSocketServer; + let wsClient: WebSocket.w3cwebsocket; + let wsClientTwo: WebSocket.w3cwebsocket; + let fillScenarios: FillScenarios; + let userAddresses: string[]; + let makerAssetData: string; + let takerAssetData: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + let zrxTokenAddress: string; + let signedOrder: SignedOrder; + let orderHash: string; + // Manually encode types rather than use /src/types to mimick real data that user + // would input. Otherwise we would be forced to use enums, which hide problems. + let addOrderPayload: { id: string; jsonrpc: string; method: string; params: { signedOrder: SignedOrder } }; + let removeOrderPayload: { id: string; jsonrpc: string; method: string; params: { orderHash: string } }; + const decimals = constants.ZRX_DECIMALS; + const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); + // HACK: createFillableSignedOrderAsync is Promise-based, which forces us + // to use Promises instead of the done() callbacks for tests. + // onmessage callback must thus be wrapped as a Promise. + const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => + new Promise(resolve => { + client.onmessage = (msg: WsMessage) => resolve(msg); + }); + + before(async () => { + // Set up constants + const contractAddresses = await migrateOnceAsync(); + await blockchainLifecycle.startAsync(); + const networkId = constants.TESTRPC_NETWORK_ID; + const config = { + networkId, + contractAddresses, + }; + contractWrappers = new ContractWrappers(provider, config); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + zrxTokenAddress = contractAddresses.zrxToken; + [makerAddress, takerAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + contractAddresses.exchange, + contractAddresses.erc20Proxy, + contractAddresses.erc721Proxy, + ); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + orderHash = orderHashUtils.getOrderHashHex(signedOrder); + addOrderPayload = { + id: 'addOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + params: { signedOrder }, + }; + removeOrderPayload = { + id: 'removeOrderPayload', + jsonrpc: '2.0', + method: 'REMOVE_ORDER', + params: { orderHash }, + }; + + // Prepare OrderWatcher WebSocket server + const orderWatcherConfig = {}; + wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); + wsServer.listen(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + wsServer.close(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + wsClient.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); + + it('responds to getStats requests correctly', (done: any) => { + const payload = { + id: 'getStats', + jsonrpc: '2.0', + method: 'GET_STATS', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); + wsClient.onmessage = (msg: any) => { + const responseData = JSON.parse(msg.data); + expect(responseData.id).to.be.eq('getStats'); + expect(responseData.jsonrpc).to.be.eq('2.0'); + expect(responseData.method).to.be.eq('GET_STATS'); + expect(responseData.result.orderCount).to.be.eq(0); + done(); + }; + }); + + it('throws an error when an invalid method is attempted', async () => { + const invalidMethodPayload = { + id: 'invalidMethodPayload', + jsonrpc: '2.0', + method: 'BAD_METHOD', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when jsonrpc field missing from request', async () => { + const noJsonRpcPayload = { + id: 'noJsonRpcPayload', + method: 'GET_STATS', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when we try to add an order without a signedOrder', async () => { + const noSignedOrderAddOrderPayload = { + id: 'noSignedOrderAddOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when we try to add a bad signedOrder', async () => { + const invalidAddOrderPayload = { + id: 'invalidAddOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + signedOrder: { + makerAddress: '0x0', + }, + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('executes addOrder and removeOrder requests correctly', async () => { + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + const addOrderMsg = await _onMessageAsync(wsClient); + const addOrderData = JSON.parse(addOrderMsg.data); + expect(addOrderData.method).to.be.eq('ADD_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ + [orderHash]: signedOrder, + }); + + wsClient.send(JSON.stringify(removeOrderPayload)); + const removeOrderMsg = await _onMessageAsync(wsClient); + const removeOrderData = JSON.parse(removeOrderMsg.data); + expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ + [orderHash]: signedOrder, + }); + }); + + it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { + // Add the regular order + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + await _onMessageAsync(wsClient); + + // Set the allowance to 0 + await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); + + // Ensure that orderStateInvalid message is received. + const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); + const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); + expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); + const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; + expect(invalidOrderState.isValid).to.be.false(); + expect(invalidOrderState.orderHash).to.be.eq(orderHash); + expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance); + }); + + it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => { + // Prepare order + const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); + const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerAssetData, + takerAssetData, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + takerAddress, + ); + const nonZeroMakerFeeOrderPayload = { + id: 'nonZeroMakerFeeOrderPayload', + jsonrpc: '2.0', + method: 'ADD_ORDER', + signedOrder: nonZeroMakerFeeSignedOrder, + }; + + // Set up a second client and have it add the order + wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); + await _onMessageAsync(wsClientTwo); + + // Change the allowance + await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); + + // Check that both clients receive the emitted event + for (const client of [wsClient, wsClientTwo]) { + const updateMsg = await _onMessageAsync(client); + const updateData = JSON.parse(updateMsg.data); + const orderState = updateData.result as OrderStateValid; + expect(orderState.isValid).to.be.true(); + expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); + } + + wsClientTwo.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); +}); diff --git a/packages/order-watcher/test/order_watcher_websocket_test.ts b/packages/order-watcher/test/order_watcher_websocket_test.ts deleted file mode 100644 index c4d1ede45..000000000 --- a/packages/order-watcher/test/order_watcher_websocket_test.ts +++ /dev/null @@ -1,288 +0,0 @@ -import { ContractWrappers } from '@0x/contract-wrappers'; -import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { FillScenarios } from '@0x/fill-scenarios'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; -import { BigNumber, logUtils } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import 'mocha'; -import * as WebSocket from 'websocket'; - -import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_websocket'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { migrateOnceAsync } from './utils/migrate'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -interface WsMessage { - data: string; -} - -describe.only('OrderWatcherWebSocket', async () => { - let contractWrappers: ContractWrappers; - let wsServer: OrderWatcherWebSocketServer; - let wsClient: WebSocket.w3cwebsocket; - let wsClientTwo: WebSocket.w3cwebsocket; - let fillScenarios: FillScenarios; - let userAddresses: string[]; - let makerAssetData: string; - let takerAssetData: string; - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAddress: string; - let takerAddress: string; - let zrxTokenAddress: string; - let signedOrder: SignedOrder; - let orderHash: string; - // Manually encode types rather than use /src/types to mimick real data that user - // would input. Otherwise we would be forced to use enums, which hide problems. - let addOrderPayload: { id: string; jsonrpc: string; method: string; params: { signedOrder: SignedOrder } }; - let removeOrderPayload: { id: string; jsonrpc: string; method: string; params: { orderHash: string } }; - const decimals = constants.ZRX_DECIMALS; - const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); - // HACK: createFillableSignedOrderAsync is Promise-based, which forces us - // to use Promises instead of the done() callbacks for tests. - // onmessage callback must thus be wrapped as a Promise. - const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => - new Promise(resolve => { - client.onmessage = (msg: WsMessage) => resolve(msg); - }); - - before(async () => { - // Set up constants - const contractAddresses = await migrateOnceAsync(); - await blockchainLifecycle.startAsync(); - const networkId = constants.TESTRPC_NETWORK_ID; - const config = { - networkId, - contractAddresses, - }; - contractWrappers = new ContractWrappers(provider, config); - userAddresses = await web3Wrapper.getAvailableAddressesAsync(); - zrxTokenAddress = contractAddresses.zrxToken; - [makerAddress, takerAddress] = userAddresses; - [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); - [makerAssetData, takerAssetData] = [ - assetDataUtils.encodeERC20AssetData(makerTokenAddress), - assetDataUtils.encodeERC20AssetData(takerTokenAddress), - ]; - fillScenarios = new FillScenarios( - provider, - userAddresses, - zrxTokenAddress, - contractAddresses.exchange, - contractAddresses.erc20Proxy, - contractAddresses.erc721Proxy, - ); - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerAssetData, - takerAssetData, - makerAddress, - takerAddress, - fillableAmount, - ); - orderHash = orderHashUtils.getOrderHashHex(signedOrder); - addOrderPayload = { - id: 'addOrderPayload', - jsonrpc: '2.0', - method: 'ADD_ORDER', - params: { signedOrder }, - }; - removeOrderPayload = { - id: 'removeOrderPayload', - jsonrpc: '2.0', - method: 'REMOVE_ORDER', - params: { orderHash }, - }; - - // Prepare OrderWatcher WebSocket server - const orderWatcherConfig = {}; - wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); - wsServer.listen(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - wsServer.close(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); - logUtils.log(`${new Date()} [Client] Connected.`); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - wsClient.close(); - logUtils.log(`${new Date()} [Client] Closed.`); - }); - - it('responds to getStats requests correctly', (done: any) => { - const payload = { - id: 'getStats', - jsonrpc: '2.0', - method: 'GET_STATS', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); - wsClient.onmessage = (msg: any) => { - const responseData = JSON.parse(msg.data); - expect(responseData.id).to.be.eq('getStats'); - expect(responseData.jsonrpc).to.be.eq('2.0'); - expect(responseData.method).to.be.eq('GET_STATS'); - expect(responseData.result.orderCount).to.be.eq(0); - done(); - }; - }); - - it('throws an error when an invalid method is attempted', async () => { - const invalidMethodPayload = { - id: 'invalidMethodPayload', - jsonrpc: '2.0', - method: 'BAD_METHOD', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when jsonrpc field missing from request', async () => { - const noJsonRpcPayload = { - id: 'noJsonRpcPayload', - method: 'GET_STATS', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when we try to add an order without a signedOrder', async () => { - const noSignedOrderAddOrderPayload = { - id: 'noSignedOrderAddOrderPayload', - jsonrpc: '2.0', - method: 'ADD_ORDER', - orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when we try to add a bad signedOrder', async () => { - const invalidAddOrderPayload = { - id: 'invalidAddOrderPayload', - jsonrpc: '2.0', - method: 'ADD_ORDER', - signedOrder: { - makerAddress: '0x0', - }, - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('executes addOrder and removeOrder requests correctly', async () => { - wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - const addOrderMsg = await _onMessageAsync(wsClient); - const addOrderData = JSON.parse(addOrderMsg.data); - expect(addOrderData.method).to.be.eq('ADD_ORDER'); - expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ - [orderHash]: signedOrder, - }); - - wsClient.send(JSON.stringify(removeOrderPayload)); - const removeOrderMsg = await _onMessageAsync(wsClient); - const removeOrderData = JSON.parse(removeOrderMsg.data); - expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); - expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ - [orderHash]: signedOrder, - }); - }); - - it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { - // Add the regular order - wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - await _onMessageAsync(wsClient); - - // Set the allowance to 0 - await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); - - // Ensure that orderStateInvalid message is received. - const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); - const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); - expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); - const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; - expect(invalidOrderState.isValid).to.be.false(); - expect(invalidOrderState.orderHash).to.be.eq(orderHash); - expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance); - }); - - it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => { - // Prepare order - const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); - const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); - const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerAssetData, - takerAssetData, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - takerAddress, - ); - const nonZeroMakerFeeOrderPayload = { - id: 'nonZeroMakerFeeOrderPayload', - jsonrpc: '2.0', - method: 'ADD_ORDER', - signedOrder: nonZeroMakerFeeSignedOrder, - }; - - // Set up a second client and have it add the order - wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); - logUtils.log(`${new Date()} [Client] Connected.`); - wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); - await _onMessageAsync(wsClientTwo); - - // Change the allowance - await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); - - // Check that both clients receive the emitted event - for (const client of [wsClient, wsClientTwo]) { - const updateMsg = await _onMessageAsync(client); - const updateData = JSON.parse(updateMsg.data); - const orderState = updateData.result as OrderStateValid; - expect(orderState.isValid).to.be.true(); - expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); - } - - wsClientTwo.close(); - logUtils.log(`${new Date()} [Client] Closed.`); - }); -}); -- cgit v1.2.3 From f510f9df997633830e93e174ba598a45cae51f48 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sat, 15 Dec 2018 21:34:56 -0800 Subject: remove unused instance variable --- .../order-watcher/src/order_watcher/order_watcher_websocket_server.ts | 2 -- 1 file changed, 2 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index 2d2d9e82e..a1b63128f 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -22,7 +22,6 @@ export class OrderWatcherWebSocketServer { private readonly _httpServer: http.Server; private readonly _connectionStore: Set; private readonly _wsServer: WebSocket.server; - private _jsonRpcRequestId: number; /** * Recover types lost when the payload is stringified. */ @@ -55,7 +54,6 @@ export class OrderWatcherWebSocketServer { contractAddresses?: ContractAddresses, partialConfig?: Partial, ) { - this._jsonRpcRequestId = 1; this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); this._connectionStore = new Set(); this._httpServer = http.createServer(); -- cgit v1.2.3 From 7661cfc85ef9e267d15bd4d7bd06c3b6cc3f7931 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 16:21:27 -0800 Subject: Improve our compliance to the JSON RPC spec --- packages/order-watcher/README.md | 18 +++++------ .../order_watcher_websocket_server.ts | 36 ++++++++++++---------- .../order-watcher/src/schemas/websocket_schemas.ts | 2 ++ packages/order-watcher/src/types.ts | 10 +++--- .../test/order_watcher_websocket_server_test.ts | 4 +-- 5 files changed, 37 insertions(+), 33 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/README.md b/packages/order-watcher/README.md index aad90a59a..385fe4715 100644 --- a/packages/order-watcher/README.md +++ b/packages/order-watcher/README.md @@ -40,7 +40,7 @@ Several environmental variables can be set to configure the server: and accept connections from. When this is not set, we default to 8080. **Requests** -The server accepts three types of requests: `ADD_ORDER`, `REMOVE_ORDER` and `GET_STATS`. These mirror what the underlying OrderWatcher does. You can read more in the [wiki](https://0xproject.com/wiki#0x-OrderWatcher). Unlike the OrderWatcher, it does not expose any subscribe or unsubscribe functionality because the client implicitly subscribes and unsubscribes by connecting to the server. +The server accepts three types of requests: `ADD_ORDER`, `REMOVE_ORDER` and `GET_STATS`. These mirror what the underlying OrderWatcher does. You can read more in the [wiki](https://0xproject.com/wiki#0x-OrderWatcher). Unlike the OrderWatcher, it does not expose any `subscribe` or `unsubscribe` functionality because the WebSocket server keeps a single subscription open for all clients. The first step for making a request is establishing a connection with the server. In Javascript: @@ -58,9 +58,9 @@ wsClient = create_connection("ws://127.0.0.1:8080") With the connection established, you prepare the payload for your request. The payload is a json object with a format established by the [JSON RPC specification](https://www.jsonrpc.org/specification): -* `id`: All requests require you to specify a string as an id. When the server responds to the request, it provides an id as well to allow you to determine which request it is responding to. +* `id`: All requests require you to specify a numerical `id`. When the server responds to the request, the response will have the same `id` as the one supplied with your request. * `jsonrpc`: This is always the string `'2.0'`. -* `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'`, and `'GET_STATS'`. +* `method`: This specifies the OrderWatcher method you want to call. I.e., `'ADD_ORDER'`, `'REMOVE_ORDER'` or `'GET_STATS'`. * `params`: These contain the parameters needed by OrderWatcher to execute the method you called. For `ADD_ORDER`, provide `{ signedOrder: }`. For `REMOVE_ORDER`, provide `{ orderHash: }`. For `GET_STATS`, no parameters are needed, so you may leave this empty. Next, convert the payload to a string and send it through the connection. @@ -68,7 +68,7 @@ In Javascript: ``` const addOrderPayload = { - id: 'order32', + id: 1, jsonrpc: '2.0', method: 'ADD_ORDER', params: { signedOrder: }, @@ -81,7 +81,7 @@ In Python: ``` import json remove_order_payload = { - 'id': 'order33', + 'id': 1, 'jsonrpc': '2.0', 'method': 'REMOVE_ORDER', 'params': {'orderHash': '0x6edc16bf37fde79f5012088c33784c730e2f103d9ab1caf73060c386ad107b7e'}, @@ -90,13 +90,13 @@ wsClient.send(json.dumps(remove_order_payload)); ``` **Response** -The server responds to all requests in a similar format. In the data field, you'll find another json object that has been converted into a string. This json object contains the following fields: +The server responds to all requests in a similar format. In the data field, you'll find another object containing the following fields: -* `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is `null`. +* `id`: The id corresponding to the request that the server is responding to. `UPDATE` responses are not based on any requests so the `id` field is omitted`. * `jsonrpc`: Always `'2.0'`. * `method`: The method the server is responding to. Eg. `ADD_ORDER`. When order states change the server may also initiate a response. In this case, method will be listed as `UPDATE`. -* `result`: This field varies based on the method. `UPDATE` responses contained the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is `null`. -* `error`: When there is an error executing a request, the error message is listed here. When the server responds successfully, this field is `null`. +* `result`: This field varies based on the method. `UPDATE` responses contain the new order state. `GET_STATS` responses contain the current order count. When there are errors, this field is omitted. +* `error`: When there is an error executing a request, the [JSON RPC](https://www.jsonrpc.org/specification) error object is listed here. When the server responds successfully, this field is omitted. In Javascript, the responses can be parsed using the `onmessage` callback: diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index a1b63128f..eac48f849 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -12,7 +12,7 @@ import { assert } from '../utils/assert'; import { OrderWatcher } from './order_watcher'; const DEFAULT_HTTP_PORT = 8080; -const JSONRPC_VERSION = '2.0'; +const JSON_RPC_VERSION = '2.0'; // Wraps the OrderWatcher functionality in a WebSocket server. Motivations: // 1) Users can watch orders via non-typescript programs. @@ -77,16 +77,17 @@ export class OrderWatcherWebSocketServer { connection.on('close', this._onCloseCallback.bind(this, connection)); this._connectionStore.add(connection); }); + } + /** + * Activates the WebSocket server by subscribing to the OrderWatcher and + * starting the WebSocket's HTTP server + */ + public start(): void { // Have the WebSocket server subscribe to the OrderWatcher to receive updates. // These updates are then broadcast to clients in the _connectionStore. this._orderWatcher.subscribe(this._broadcastCallback.bind(this)); - } - /** - * Activates the WebSocket server by having its HTTP server start listening. - */ - public listen(): void { const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; this._httpServer.listen(port, () => { logUtils.log(`${new Date()} [Server] Listening on port ${port}`); @@ -95,29 +96,30 @@ export class OrderWatcherWebSocketServer { /** * Deactivates the WebSocket server by stopping the HTTP server from accepting - * new connections. + * new connections and unsubscribing from the OrderWatcher */ - public close(): void { + public stop(): void { this._httpServer.close(); + this._orderWatcher.unsubscribe(); } private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { let response: WebSocketResponse; + assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); + const request: WebSocketRequest = JSON.parse(message.utf8Data); + assert.doesConformToSchema('request', request, webSocketRequestSchema); + assert.isString(request.jsonrpc, JSON_RPC_VERSION); try { - assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); - const request: WebSocketRequest = JSON.parse(message.utf8Data); - assert.doesConformToSchema('request', request, webSocketRequestSchema); - assert.isString(request.jsonrpc, JSONRPC_VERSION); response = { id: request.id, - jsonrpc: JSONRPC_VERSION, + jsonrpc: JSON_RPC_VERSION, method: request.method, result: await this._routeRequestAsync(request), }; } catch (err) { response = { - id: null, - jsonrpc: JSONRPC_VERSION, + id: request.id, + jsonrpc: JSON_RPC_VERSION, method: null, error: err.toString(), }; @@ -165,12 +167,12 @@ export class OrderWatcherWebSocketServer { const response = err === null ? { - jsonrpc: JSONRPC_VERSION, + jsonrpc: JSON_RPC_VERSION, method, result: orderState, } : { - jsonrpc: JSONRPC_VERSION, + jsonrpc: JSON_RPC_VERSION, method, error: { code: -32000, diff --git a/packages/order-watcher/src/schemas/websocket_schemas.ts b/packages/order-watcher/src/schemas/websocket_schemas.ts index 5e4e1ab74..263dd45b3 100644 --- a/packages/order-watcher/src/schemas/websocket_schemas.ts +++ b/packages/order-watcher/src/schemas/websocket_schemas.ts @@ -1,3 +1,5 @@ +// TODO: Move these schemas to the `json-schemas` package and convert to JSON +// Rename to `OrderWatcherWebSocketRequestSchema`, etc... export const webSocketUtf8MessageSchema = { id: '/webSocketUtf8MessageSchema', properties: { diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index ecbebe305..536363d8a 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -49,21 +49,21 @@ export enum OrderWatcherMethod { export type WebSocketRequest = AddOrderRequest | RemoveOrderRequest | GetStatsRequest; interface AddOrderRequest { - id: string; + id: number; jsonrpc: string; method: OrderWatcherMethod.AddOrder; params: { signedOrder: SignedOrder }; } interface RemoveOrderRequest { - id: string; + id: number; jsonrpc: string; method: OrderWatcherMethod.RemoveOrder; params: { orderHash: string }; } interface GetStatsRequest { - id: string; + id: number; jsonrpc: string; method: OrderWatcherMethod.GetStats; } @@ -73,14 +73,14 @@ interface GetStatsRequest { export type WebSocketResponse = SuccessfulWebSocketResponse | ErrorWebSocketResponse; interface SuccessfulWebSocketResponse { - id: string | null; // id is null for UPDATE + id: number; jsonrpc: string; method: OrderWatcherMethod; result: OrderState | GetStatsResult | undefined; // result is undefined for ADD_ORDER and REMOVE_ORDER } interface ErrorWebSocketResponse { - id: null; + id: number; jsonrpc: string; method: null; error: JSONRPCError; diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts index 9f9db7b1f..8a6deede8 100644 --- a/packages/order-watcher/test/order_watcher_websocket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_server_test.ts @@ -105,11 +105,11 @@ describe.only('OrderWatcherWebSocketServer', async () => { // Prepare OrderWatcher WebSocket server const orderWatcherConfig = {}; wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); - wsServer.listen(); + wsServer.start(); }); after(async () => { await blockchainLifecycle.revertAsync(); - wsServer.close(); + wsServer.stop(); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); -- cgit v1.2.3 From ee4185ab465c76b64b65efefb92e11b0ca4ecad4 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 16:52:37 -0800 Subject: Move OrderWatcher Websocket schemas to json-schemas and convert to JSON so that they are language agnostic --- .../order_watcher_web_socket_request_schema.json | 52 ++++++++++++++++++ ...der_watcher_web_socket_utf8_message_schema.json | 10 ++++ packages/json-schemas/src/schemas.ts | 4 ++ packages/json-schemas/tsconfig.json | 2 + .../order_watcher_websocket_server.ts | 6 +-- .../order-watcher/src/schemas/websocket_schemas.ts | 61 ---------------------- 6 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json create mode 100644 packages/json-schemas/schemas/order_watcher_web_socket_utf8_message_schema.json (limited to 'packages') diff --git a/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json b/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json new file mode 100644 index 000000000..4666c6291 --- /dev/null +++ b/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json @@ -0,0 +1,52 @@ +{ + "id": "/orderWatcherWebSocketRequestSchema", + "type": "object", + "definitions": { + "signedOrderParam": { + "type": "object", + "properties": { + "signedOrder": { "$ref": "/signedOrderSchema" } + }, + "required": ["signedOrder"] + }, + "orderHashParam": { + "type": "object", + "properties": { + "orderHash": { "$ref": "/hexSchema" } + }, + "required": ["orderHash"] + } + }, + "oneOf": [ + { + "type": "object", + "properties": { + "id": { "type": "string" }, + "jsonrpc": { "type": "string" }, + "method": { "enum": ["ADD_ORDER"] }, + "params": { "$ref": "#/definitions/signedOrderParam" } + }, + "required": ["id", "jsonrpc", "method", "params"] + }, + { + "type": "object", + "properties": { + "id": { "type": "string" }, + "jsonrpc": { "type": "string" }, + "method": { "enum": ["REMOVE_ORDER"] }, + "params": { "$ref": "#/definitions/orderHashParam" } + }, + "required": ["id", "jsonrpc", "method", "params"] + }, + { + "type": "object", + "properties": { + "id": { "type": "string" }, + "jsonrpc": { "type": "string" }, + "method": { "enum": ["GET_STATS"] }, + "params": {} + }, + "required": ["id", "jsonrpc", "method"] + } + ] +} \ No newline at end of file diff --git a/packages/json-schemas/schemas/order_watcher_web_socket_utf8_message_schema.json b/packages/json-schemas/schemas/order_watcher_web_socket_utf8_message_schema.json new file mode 100644 index 000000000..154d6d754 --- /dev/null +++ b/packages/json-schemas/schemas/order_watcher_web_socket_utf8_message_schema.json @@ -0,0 +1,10 @@ +{ + "id": "/orderWatcherWebSocketUtf8MessageSchema", + "properties": { + "utf8Data": { "type": "string" } + }, + "required": [ + "utf8Data" + ], + "type": "object" +} diff --git a/packages/json-schemas/src/schemas.ts b/packages/json-schemas/src/schemas.ts index 21a6f424c..050f4e625 100644 --- a/packages/json-schemas/src/schemas.ts +++ b/packages/json-schemas/src/schemas.ts @@ -16,6 +16,8 @@ import * as orderFillOrKillRequestsSchema from '../schemas/order_fill_or_kill_re import * as orderFillRequestsSchema from '../schemas/order_fill_requests_schema.json'; import * as orderHashSchema from '../schemas/order_hash_schema.json'; import * as orderSchema from '../schemas/order_schema.json'; +import * as orderWatcherWebSocketRequestSchema from '../schemas/order_watcher_web_socket_request_schema.json'; +import * as orderWatcherWebSocketUtf8MessageSchema from '../schemas/order_watcher_web_socket_utf8_message_schema.json'; import * as orderBookRequestSchema from '../schemas/orderbook_request_schema.json'; import * as ordersRequestOptsSchema from '../schemas/orders_request_opts_schema.json'; import * as ordersSchema from '../schemas/orders_schema.json'; @@ -66,6 +68,8 @@ export const schemas = { jsNumber, requestOptsSchema, pagedRequestOptsSchema, + orderWatcherWebSocketRequestSchema, + orderWatcherWebSocketUtf8MessageSchema, ordersRequestOptsSchema, orderBookRequestSchema, orderConfigRequestSchema, diff --git a/packages/json-schemas/tsconfig.json b/packages/json-schemas/tsconfig.json index a79d54385..ec573290c 100644 --- a/packages/json-schemas/tsconfig.json +++ b/packages/json-schemas/tsconfig.json @@ -23,6 +23,8 @@ "./schemas/order_schema.json", "./schemas/signed_order_schema.json", "./schemas/orders_schema.json", + "./schemas/order_watcher_web_socket_request_schema.json", + "./schemas/order_watcher_web_socket_utf8_message_schema.json", "./schemas/paginated_collection_schema.json", "./schemas/relayer_api_asset_data_pairs_response_schema.json", "./schemas/relayer_api_asset_data_pairs_schema.json", diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index eac48f849..f90961cc8 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -1,11 +1,11 @@ import { ContractAddresses } from '@0x/contract-addresses'; +import { schemas } from '@0x/json-schemas'; import { OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; import { BigNumber, logUtils } from '@0x/utils'; import { Provider } from 'ethereum-types'; import * as http from 'http'; import * as WebSocket from 'websocket'; -import { webSocketRequestSchema, webSocketUtf8MessageSchema } from '../schemas/websocket_schemas'; import { GetStatsResult, OrderWatcherConfig, OrderWatcherMethod, WebSocketRequest, WebSocketResponse } from '../types'; import { assert } from '../utils/assert'; @@ -105,9 +105,9 @@ export class OrderWatcherWebSocketServer { private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { let response: WebSocketResponse; - assert.doesConformToSchema('message', message, webSocketUtf8MessageSchema); + assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema); const request: WebSocketRequest = JSON.parse(message.utf8Data); - assert.doesConformToSchema('request', request, webSocketRequestSchema); + assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema); assert.isString(request.jsonrpc, JSON_RPC_VERSION); try { response = { diff --git a/packages/order-watcher/src/schemas/websocket_schemas.ts b/packages/order-watcher/src/schemas/websocket_schemas.ts index 263dd45b3..df54a38e1 100644 --- a/packages/order-watcher/src/schemas/websocket_schemas.ts +++ b/packages/order-watcher/src/schemas/websocket_schemas.ts @@ -1,63 +1,2 @@ // TODO: Move these schemas to the `json-schemas` package and convert to JSON // Rename to `OrderWatcherWebSocketRequestSchema`, etc... -export const webSocketUtf8MessageSchema = { - id: '/webSocketUtf8MessageSchema', - properties: { - utf8Data: { type: 'string' }, - }, - type: 'object', - required: ['utf8Data'], -}; - -export const webSocketRequestSchema = { - id: '/webSocketRequestSchema', - type: 'object', - definitions: { - signedOrderParam: { - type: 'object', - properties: { - signedOrder: { $ref: '/signedOrderSchema' }, - }, - required: ['signedOrder'], - }, - orderHashParam: { - type: 'object', - properties: { - orderHash: { $ref: '/hexSchema' }, - }, - required: ['orderHash'], - }, - }, - oneOf: [ - { - type: 'object', - properties: { - id: { type: 'string' }, - jsonrpc: { type: 'string' }, - method: { enum: ['ADD_ORDER'] }, - params: { $ref: '#/definitions/signedOrderParam' }, - }, - required: ['id', 'jsonrpc', 'method', 'params'], - }, - { - type: 'object', - properties: { - id: { type: 'string' }, - jsonrpc: { type: 'string' }, - method: { enum: ['REMOVE_ORDER'] }, - params: { $ref: '#/definitions/orderHashParam' }, - }, - required: ['id', 'jsonrpc', 'method', 'params'], - }, - { - type: 'object', - properties: { - id: { type: 'string' }, - jsonrpc: { type: 'string' }, - method: { enum: ['GET_STATS'] }, - params: {}, - }, - required: ['id', 'jsonrpc', 'method'], - }, - ], -}; -- cgit v1.2.3 From 896c8d17c16c4f1e9670ab0747ae8934ce5400a5 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 17:31:38 -0800 Subject: Fix schemas and tests --- .../order_watcher_web_socket_request_schema.json | 6 ++--- .../order_watcher_websocket_server.ts | 14 ++++++----- packages/order-watcher/src/types.ts | 14 +++++------ .../test/order_watcher_websocket_server_test.ts | 27 +++++++++++----------- 4 files changed, 32 insertions(+), 29 deletions(-) (limited to 'packages') diff --git a/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json b/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json index 4666c6291..b0c419f94 100644 --- a/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json +++ b/packages/json-schemas/schemas/order_watcher_web_socket_request_schema.json @@ -21,7 +21,7 @@ { "type": "object", "properties": { - "id": { "type": "string" }, + "id": { "type": "number" }, "jsonrpc": { "type": "string" }, "method": { "enum": ["ADD_ORDER"] }, "params": { "$ref": "#/definitions/signedOrderParam" } @@ -31,7 +31,7 @@ { "type": "object", "properties": { - "id": { "type": "string" }, + "id": { "type": "number" }, "jsonrpc": { "type": "string" }, "method": { "enum": ["REMOVE_ORDER"] }, "params": { "$ref": "#/definitions/orderHashParam" } @@ -41,7 +41,7 @@ { "type": "object", "properties": { - "id": { "type": "string" }, + "id": { "type": "number" }, "jsonrpc": { "type": "string" }, "method": { "enum": ["GET_STATS"] }, "params": {} diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index f90961cc8..da5667db3 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -105,20 +105,22 @@ export class OrderWatcherWebSocketServer { private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { let response: WebSocketResponse; - assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema); - const request: WebSocketRequest = JSON.parse(message.utf8Data); - assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema); - assert.isString(request.jsonrpc, JSON_RPC_VERSION); + let id: number | null = null; try { + assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema); + const request: WebSocketRequest = JSON.parse(message.utf8Data); + id = request.id; + assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema); + assert.isString(request.jsonrpc, JSON_RPC_VERSION); response = { - id: request.id, + id, jsonrpc: JSON_RPC_VERSION, method: request.method, result: await this._routeRequestAsync(request), }; } catch (err) { response = { - id: request.id, + id, jsonrpc: JSON_RPC_VERSION, method: null, error: err.toString(), diff --git a/packages/order-watcher/src/types.ts b/packages/order-watcher/src/types.ts index 536363d8a..2b529a939 100644 --- a/packages/order-watcher/src/types.ts +++ b/packages/order-watcher/src/types.ts @@ -48,21 +48,21 @@ export enum OrderWatcherMethod { // the data field of their WebSocket message to interact with the server. export type WebSocketRequest = AddOrderRequest | RemoveOrderRequest | GetStatsRequest; -interface AddOrderRequest { +export interface AddOrderRequest { id: number; jsonrpc: string; method: OrderWatcherMethod.AddOrder; params: { signedOrder: SignedOrder }; } -interface RemoveOrderRequest { +export interface RemoveOrderRequest { id: number; jsonrpc: string; method: OrderWatcherMethod.RemoveOrder; params: { orderHash: string }; } -interface GetStatsRequest { +export interface GetStatsRequest { id: number; jsonrpc: string; method: OrderWatcherMethod.GetStats; @@ -72,21 +72,21 @@ interface GetStatsRequest { // of the WebSocket messages that the server sends out. export type WebSocketResponse = SuccessfulWebSocketResponse | ErrorWebSocketResponse; -interface SuccessfulWebSocketResponse { +export interface SuccessfulWebSocketResponse { id: number; jsonrpc: string; method: OrderWatcherMethod; result: OrderState | GetStatsResult | undefined; // result is undefined for ADD_ORDER and REMOVE_ORDER } -interface ErrorWebSocketResponse { - id: number; +export interface ErrorWebSocketResponse { + id: number | null; jsonrpc: string; method: null; error: JSONRPCError; } -interface JSONRPCError { +export interface JSONRPCError { code: number; message: string; data?: string | object; diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts index 8a6deede8..d21c676fc 100644 --- a/packages/order-watcher/test/order_watcher_websocket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_server_test.ts @@ -11,6 +11,7 @@ import 'mocha'; import * as WebSocket from 'websocket'; import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_websocket_server'; +import { AddOrderRequest, OrderWatcherMethod, RemoveOrderRequest } from '../src/types'; import { chaiSetup } from './utils/chai_setup'; import { constants } from './utils/constants'; @@ -43,8 +44,8 @@ describe.only('OrderWatcherWebSocketServer', async () => { let orderHash: string; // Manually encode types rather than use /src/types to mimick real data that user // would input. Otherwise we would be forced to use enums, which hide problems. - let addOrderPayload: { id: string; jsonrpc: string; method: string; params: { signedOrder: SignedOrder } }; - let removeOrderPayload: { id: string; jsonrpc: string; method: string; params: { orderHash: string } }; + let addOrderPayload: AddOrderRequest; + let removeOrderPayload: RemoveOrderRequest; const decimals = constants.ZRX_DECIMALS; const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); // HACK: createFillableSignedOrderAsync is Promise-based, which forces us @@ -90,15 +91,15 @@ describe.only('OrderWatcherWebSocketServer', async () => { ); orderHash = orderHashUtils.getOrderHashHex(signedOrder); addOrderPayload = { - id: 'addOrderPayload', + id: 1, jsonrpc: '2.0', - method: 'ADD_ORDER', + method: OrderWatcherMethod.AddOrder, params: { signedOrder }, }; removeOrderPayload = { - id: 'removeOrderPayload', + id: 1, jsonrpc: '2.0', - method: 'REMOVE_ORDER', + method: OrderWatcherMethod.RemoveOrder, params: { orderHash }, }; @@ -124,14 +125,14 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('responds to getStats requests correctly', (done: any) => { const payload = { - id: 'getStats', + id: 1, jsonrpc: '2.0', method: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); wsClient.onmessage = (msg: any) => { const responseData = JSON.parse(msg.data); - expect(responseData.id).to.be.eq('getStats'); + expect(responseData.id).to.be.eq(1); expect(responseData.jsonrpc).to.be.eq('2.0'); expect(responseData.method).to.be.eq('GET_STATS'); expect(responseData.result.orderCount).to.be.eq(0); @@ -141,7 +142,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('throws an error when an invalid method is attempted', async () => { const invalidMethodPayload = { - id: 'invalidMethodPayload', + id: 1, jsonrpc: '2.0', method: 'BAD_METHOD', }; @@ -158,7 +159,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('throws an error when jsonrpc field missing from request', async () => { const noJsonRpcPayload = { - id: 'noJsonRpcPayload', + id: 1, method: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); @@ -172,7 +173,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('throws an error when we try to add an order without a signedOrder', async () => { const noSignedOrderAddOrderPayload = { - id: 'noSignedOrderAddOrderPayload', + id: 1, jsonrpc: '2.0', method: 'ADD_ORDER', orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', @@ -190,7 +191,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('throws an error when we try to add a bad signedOrder', async () => { const invalidAddOrderPayload = { - id: 'invalidAddOrderPayload', + id: 1, jsonrpc: '2.0', method: 'ADD_ORDER', signedOrder: { @@ -258,7 +259,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { takerAddress, ); const nonZeroMakerFeeOrderPayload = { - id: 'nonZeroMakerFeeOrderPayload', + id: 1, jsonrpc: '2.0', method: 'ADD_ORDER', signedOrder: nonZeroMakerFeeSignedOrder, -- cgit v1.2.3 From 5d0e715d9ac9f358c1cdf23c9c96d622e0f1060c Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 17:46:28 -0800 Subject: Add isVerbose option to enable/disable logging --- .../order_watcher/order_watcher_websocket_server.ts | 20 +++++++++++++++----- .../test/order_watcher_websocket_server_test.ts | 9 ++++++++- 2 files changed, 23 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index da5667db3..2e29e775a 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -22,6 +22,7 @@ export class OrderWatcherWebSocketServer { private readonly _httpServer: http.Server; private readonly _connectionStore: Set; private readonly _wsServer: WebSocket.server; + private readonly _isVerbose: boolean; /** * Recover types lost when the payload is stringified. */ @@ -47,13 +48,16 @@ export class OrderWatcherWebSocketServer { * @param contractAddresses Optional contract addresses. Defaults to known * addresses based on networkId. * @param partialConfig Optional configurations. + * @param isVerbose Whether to enable verbose logging. Defaults to true. */ constructor( provider: Provider, networkId: number, contractAddresses?: ContractAddresses, + isVerbose: boolean = true, partialConfig?: Partial, ) { + this._isVerbose = isVerbose; this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); this._connectionStore = new Set(); this._httpServer = http.createServer(); @@ -72,7 +76,7 @@ export class OrderWatcherWebSocketServer { // Designed for usage pattern where client and server are run on the same // machine by the same user. As such, no security checks are in place. const connection: WebSocket.connection = request.accept(null, request.origin); - logUtils.log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); + this._log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); connection.on('close', this._onCloseCallback.bind(this, connection)); this._connectionStore.add(connection); @@ -90,7 +94,7 @@ export class OrderWatcherWebSocketServer { const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; this._httpServer.listen(port, () => { - logUtils.log(`${new Date()} [Server] Listening on port ${port}`); + this._log(`${new Date()} [Server] Listening on port ${port}`); }); } @@ -103,6 +107,12 @@ export class OrderWatcherWebSocketServer { this._orderWatcher.unsubscribe(); } + private _log(...args: any[]): void { + if (this._isVerbose) { + logUtils.log(...args); + } + } + private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { let response: WebSocketResponse; let id: number | null = null; @@ -126,17 +136,17 @@ export class OrderWatcherWebSocketServer { error: err.toString(), }; } - logUtils.log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); + this._log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); connection.sendUTF(JSON.stringify(response)); } private _onCloseCallback(connection: WebSocket.connection): void { this._connectionStore.delete(connection); - logUtils.log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); + this._log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); } private async _routeRequestAsync(request: WebSocketRequest): Promise { - logUtils.log(`${new Date()} [Server] Request received: ${request.method}`); + this._log(`${new Date()} [Server] Request received: ${request.method}`); switch (request.method) { case OrderWatcherMethod.AddOrder: { const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts index d21c676fc..d1a947105 100644 --- a/packages/order-watcher/test/order_watcher_websocket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_server_test.ts @@ -105,7 +105,14 @@ describe.only('OrderWatcherWebSocketServer', async () => { // Prepare OrderWatcher WebSocket server const orderWatcherConfig = {}; - wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); + const isVerbose = true; + wsServer = new OrderWatcherWebSocketServer( + provider, + networkId, + contractAddresses, + isVerbose, + orderWatcherConfig, + ); wsServer.start(); }); after(async () => { -- cgit v1.2.3 From a12b9e82f61ac136876f9d4b72b45aad266317cf Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 18:00:23 -0800 Subject: Consolidate use of isVerbose in orderWatcherConfig --- .../src/order_watcher/order_watcher_websocket_server.ts | 16 +++++++++------- .../test/order_watcher_websocket_server_test.ts | 17 +++++------------ 2 files changed, 14 insertions(+), 19 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts index 2e29e775a..b75b07603 100644 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts @@ -47,18 +47,20 @@ export class OrderWatcherWebSocketServer { * @param networkId NetworkId to watch orders on. * @param contractAddresses Optional contract addresses. Defaults to known * addresses based on networkId. - * @param partialConfig Optional configurations. + * @param orderWatcherConfig OrderWatcher configurations. isVerbose sets the verbosity for the WebSocket server aswell. * @param isVerbose Whether to enable verbose logging. Defaults to true. */ constructor( provider: Provider, networkId: number, contractAddresses?: ContractAddresses, - isVerbose: boolean = true, - partialConfig?: Partial, + orderWatcherConfig?: Partial, ) { - this._isVerbose = isVerbose; - this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, partialConfig); + this._isVerbose = + orderWatcherConfig !== undefined && orderWatcherConfig.isVerbose !== undefined + ? orderWatcherConfig.isVerbose + : true; + this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, orderWatcherConfig); this._connectionStore = new Set(); this._httpServer = http.createServer(); this._wsServer = new WebSocket.server({ @@ -161,10 +163,10 @@ export class OrderWatcherWebSocketServer { } case OrderWatcherMethod.GetStats: { return this._orderWatcher.getStats(); - break; } default: - // Should never reach here. Should be caught by JSON schema check. + // Should never reach here. Should be caught by JSON schema check. + throw new Error(`Unexpected default case hit for request.method`); } return undefined; } diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts index d1a947105..a66d2c6c2 100644 --- a/packages/order-watcher/test/order_watcher_websocket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_websocket_server_test.ts @@ -26,7 +26,7 @@ interface WsMessage { data: string; } -describe.only('OrderWatcherWebSocketServer', async () => { +describe('OrderWatcherWebSocketServer', async () => { let contractWrappers: ContractWrappers; let wsServer: OrderWatcherWebSocketServer; let wsClient: WebSocket.w3cwebsocket; @@ -42,8 +42,6 @@ describe.only('OrderWatcherWebSocketServer', async () => { let zrxTokenAddress: string; let signedOrder: SignedOrder; let orderHash: string; - // Manually encode types rather than use /src/types to mimick real data that user - // would input. Otherwise we would be forced to use enums, which hide problems. let addOrderPayload: AddOrderRequest; let removeOrderPayload: RemoveOrderRequest; const decimals = constants.ZRX_DECIMALS; @@ -104,15 +102,10 @@ describe.only('OrderWatcherWebSocketServer', async () => { }; // Prepare OrderWatcher WebSocket server - const orderWatcherConfig = {}; - const isVerbose = true; - wsServer = new OrderWatcherWebSocketServer( - provider, - networkId, - contractAddresses, - isVerbose, - orderWatcherConfig, - ); + const orderWatcherConfig = { + isVerbose: true, + }; + wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); wsServer.start(); }); after(async () => { -- cgit v1.2.3 From 6382f986086ee83374d3b78fed7f02e9d52b668f Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Sun, 16 Dec 2018 18:05:20 -0800 Subject: Fix file name --- packages/order-watcher/src/index.ts | 2 +- .../order_watcher_web_socket_server.ts | 200 ++++++++++++++ .../order_watcher_websocket_server.ts | 200 -------------- .../test/order_watcher_web_socket_server_test.ts | 289 +++++++++++++++++++++ .../test/order_watcher_websocket_server_test.ts | 289 --------------------- 5 files changed, 490 insertions(+), 490 deletions(-) create mode 100644 packages/order-watcher/src/order_watcher/order_watcher_web_socket_server.ts delete mode 100644 packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts create mode 100644 packages/order-watcher/test/order_watcher_web_socket_server_test.ts delete mode 100644 packages/order-watcher/test/order_watcher_websocket_server_test.ts (limited to 'packages') diff --git a/packages/order-watcher/src/index.ts b/packages/order-watcher/src/index.ts index 5bdef4504..e275a0c6a 100644 --- a/packages/order-watcher/src/index.ts +++ b/packages/order-watcher/src/index.ts @@ -1,5 +1,5 @@ export { OrderWatcher } from './order_watcher/order_watcher'; -export { OrderWatcherWebSocketServer } from './order_watcher/order_watcher_websocket_server'; +export { OrderWatcherWebSocketServer } from './order_watcher/order_watcher_web_socket_server'; export { ExpirationWatcher } from './order_watcher/expiration_watcher'; export { diff --git a/packages/order-watcher/src/order_watcher/order_watcher_web_socket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_web_socket_server.ts new file mode 100644 index 000000000..b75b07603 --- /dev/null +++ b/packages/order-watcher/src/order_watcher/order_watcher_web_socket_server.ts @@ -0,0 +1,200 @@ +import { ContractAddresses } from '@0x/contract-addresses'; +import { schemas } from '@0x/json-schemas'; +import { OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Provider } from 'ethereum-types'; +import * as http from 'http'; +import * as WebSocket from 'websocket'; + +import { GetStatsResult, OrderWatcherConfig, OrderWatcherMethod, WebSocketRequest, WebSocketResponse } from '../types'; +import { assert } from '../utils/assert'; + +import { OrderWatcher } from './order_watcher'; + +const DEFAULT_HTTP_PORT = 8080; +const JSON_RPC_VERSION = '2.0'; + +// Wraps the OrderWatcher functionality in a WebSocket server. Motivations: +// 1) Users can watch orders via non-typescript programs. +// 2) Better encapsulation so that users can work +export class OrderWatcherWebSocketServer { + private readonly _orderWatcher: OrderWatcher; + private readonly _httpServer: http.Server; + private readonly _connectionStore: Set; + private readonly _wsServer: WebSocket.server; + private readonly _isVerbose: boolean; + /** + * Recover types lost when the payload is stringified. + */ + private static _parseSignedOrder(rawRequest: any): SignedOrder { + const bigNumberFields = [ + 'salt', + 'makerFee', + 'takerFee', + 'makerAssetAmount', + 'takerAssetAmount', + 'expirationTimeSeconds', + ]; + for (const field of bigNumberFields) { + rawRequest[field] = new BigNumber(rawRequest[field]); + } + return rawRequest; + } + + /** + * Instantiate a new WebSocket server which provides OrderWatcher functionality + * @param provider Web3 provider to use for JSON RPC calls. + * @param networkId NetworkId to watch orders on. + * @param contractAddresses Optional contract addresses. Defaults to known + * addresses based on networkId. + * @param orderWatcherConfig OrderWatcher configurations. isVerbose sets the verbosity for the WebSocket server aswell. + * @param isVerbose Whether to enable verbose logging. Defaults to true. + */ + constructor( + provider: Provider, + networkId: number, + contractAddresses?: ContractAddresses, + orderWatcherConfig?: Partial, + ) { + this._isVerbose = + orderWatcherConfig !== undefined && orderWatcherConfig.isVerbose !== undefined + ? orderWatcherConfig.isVerbose + : true; + this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, orderWatcherConfig); + this._connectionStore = new Set(); + this._httpServer = http.createServer(); + this._wsServer = new WebSocket.server({ + httpServer: this._httpServer, + // Avoid setting autoAcceptConnections to true as it defeats all + // standard cross-origin protection facilities built into the protocol + // and the browser. + // Source: https://www.npmjs.com/package/websocket#server-example + // Also ensures that a request event is emitted by + // the server whenever a new WebSocket request is made. + autoAcceptConnections: false, + }); + + this._wsServer.on('request', async (request: any) => { + // Designed for usage pattern where client and server are run on the same + // machine by the same user. As such, no security checks are in place. + const connection: WebSocket.connection = request.accept(null, request.origin); + this._log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); + connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); + connection.on('close', this._onCloseCallback.bind(this, connection)); + this._connectionStore.add(connection); + }); + } + + /** + * Activates the WebSocket server by subscribing to the OrderWatcher and + * starting the WebSocket's HTTP server + */ + public start(): void { + // Have the WebSocket server subscribe to the OrderWatcher to receive updates. + // These updates are then broadcast to clients in the _connectionStore. + this._orderWatcher.subscribe(this._broadcastCallback.bind(this)); + + const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; + this._httpServer.listen(port, () => { + this._log(`${new Date()} [Server] Listening on port ${port}`); + }); + } + + /** + * Deactivates the WebSocket server by stopping the HTTP server from accepting + * new connections and unsubscribing from the OrderWatcher + */ + public stop(): void { + this._httpServer.close(); + this._orderWatcher.unsubscribe(); + } + + private _log(...args: any[]): void { + if (this._isVerbose) { + logUtils.log(...args); + } + } + + private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { + let response: WebSocketResponse; + let id: number | null = null; + try { + assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema); + const request: WebSocketRequest = JSON.parse(message.utf8Data); + id = request.id; + assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema); + assert.isString(request.jsonrpc, JSON_RPC_VERSION); + response = { + id, + jsonrpc: JSON_RPC_VERSION, + method: request.method, + result: await this._routeRequestAsync(request), + }; + } catch (err) { + response = { + id, + jsonrpc: JSON_RPC_VERSION, + method: null, + error: err.toString(), + }; + } + this._log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); + connection.sendUTF(JSON.stringify(response)); + } + + private _onCloseCallback(connection: WebSocket.connection): void { + this._connectionStore.delete(connection); + this._log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); + } + + private async _routeRequestAsync(request: WebSocketRequest): Promise { + this._log(`${new Date()} [Server] Request received: ${request.method}`); + switch (request.method) { + case OrderWatcherMethod.AddOrder: { + const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( + request.params.signedOrder, + ); + await this._orderWatcher.addOrderAsync(signedOrder); + break; + } + case OrderWatcherMethod.RemoveOrder: { + this._orderWatcher.removeOrder(request.params.orderHash || 'undefined'); + break; + } + case OrderWatcherMethod.GetStats: { + return this._orderWatcher.getStats(); + } + default: + // Should never reach here. Should be caught by JSON schema check. + throw new Error(`Unexpected default case hit for request.method`); + } + return undefined; + } + + /** + * Broadcasts OrderState changes to ALL connected clients. At the moment, + * we do not support clients subscribing to only a subset of orders. As such, + * Client B will be notified of changes to an order that Client A added. + */ + private _broadcastCallback(err: Error | null, orderState?: OrderStateValid | OrderStateInvalid | undefined): void { + const method = OrderWatcherMethod.Update; + const response = + err === null + ? { + jsonrpc: JSON_RPC_VERSION, + method, + result: orderState, + } + : { + jsonrpc: JSON_RPC_VERSION, + method, + error: { + code: -32000, + message: err.message, + }, + }; + this._connectionStore.forEach((connection: WebSocket.connection) => { + connection.sendUTF(JSON.stringify(response)); + }); + } +} diff --git a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts b/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts deleted file mode 100644 index b75b07603..000000000 --- a/packages/order-watcher/src/order_watcher/order_watcher_websocket_server.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { ContractAddresses } from '@0x/contract-addresses'; -import { schemas } from '@0x/json-schemas'; -import { OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; -import { BigNumber, logUtils } from '@0x/utils'; -import { Provider } from 'ethereum-types'; -import * as http from 'http'; -import * as WebSocket from 'websocket'; - -import { GetStatsResult, OrderWatcherConfig, OrderWatcherMethod, WebSocketRequest, WebSocketResponse } from '../types'; -import { assert } from '../utils/assert'; - -import { OrderWatcher } from './order_watcher'; - -const DEFAULT_HTTP_PORT = 8080; -const JSON_RPC_VERSION = '2.0'; - -// Wraps the OrderWatcher functionality in a WebSocket server. Motivations: -// 1) Users can watch orders via non-typescript programs. -// 2) Better encapsulation so that users can work -export class OrderWatcherWebSocketServer { - private readonly _orderWatcher: OrderWatcher; - private readonly _httpServer: http.Server; - private readonly _connectionStore: Set; - private readonly _wsServer: WebSocket.server; - private readonly _isVerbose: boolean; - /** - * Recover types lost when the payload is stringified. - */ - private static _parseSignedOrder(rawRequest: any): SignedOrder { - const bigNumberFields = [ - 'salt', - 'makerFee', - 'takerFee', - 'makerAssetAmount', - 'takerAssetAmount', - 'expirationTimeSeconds', - ]; - for (const field of bigNumberFields) { - rawRequest[field] = new BigNumber(rawRequest[field]); - } - return rawRequest; - } - - /** - * Instantiate a new WebSocket server which provides OrderWatcher functionality - * @param provider Web3 provider to use for JSON RPC calls. - * @param networkId NetworkId to watch orders on. - * @param contractAddresses Optional contract addresses. Defaults to known - * addresses based on networkId. - * @param orderWatcherConfig OrderWatcher configurations. isVerbose sets the verbosity for the WebSocket server aswell. - * @param isVerbose Whether to enable verbose logging. Defaults to true. - */ - constructor( - provider: Provider, - networkId: number, - contractAddresses?: ContractAddresses, - orderWatcherConfig?: Partial, - ) { - this._isVerbose = - orderWatcherConfig !== undefined && orderWatcherConfig.isVerbose !== undefined - ? orderWatcherConfig.isVerbose - : true; - this._orderWatcher = new OrderWatcher(provider, networkId, contractAddresses, orderWatcherConfig); - this._connectionStore = new Set(); - this._httpServer = http.createServer(); - this._wsServer = new WebSocket.server({ - httpServer: this._httpServer, - // Avoid setting autoAcceptConnections to true as it defeats all - // standard cross-origin protection facilities built into the protocol - // and the browser. - // Source: https://www.npmjs.com/package/websocket#server-example - // Also ensures that a request event is emitted by - // the server whenever a new WebSocket request is made. - autoAcceptConnections: false, - }); - - this._wsServer.on('request', async (request: any) => { - // Designed for usage pattern where client and server are run on the same - // machine by the same user. As such, no security checks are in place. - const connection: WebSocket.connection = request.accept(null, request.origin); - this._log(`${new Date()} [Server] Accepted connection from origin ${request.origin}.`); - connection.on('message', this._onMessageCallbackAsync.bind(this, connection)); - connection.on('close', this._onCloseCallback.bind(this, connection)); - this._connectionStore.add(connection); - }); - } - - /** - * Activates the WebSocket server by subscribing to the OrderWatcher and - * starting the WebSocket's HTTP server - */ - public start(): void { - // Have the WebSocket server subscribe to the OrderWatcher to receive updates. - // These updates are then broadcast to clients in the _connectionStore. - this._orderWatcher.subscribe(this._broadcastCallback.bind(this)); - - const port = process.env.ORDER_WATCHER_HTTP_PORT || DEFAULT_HTTP_PORT; - this._httpServer.listen(port, () => { - this._log(`${new Date()} [Server] Listening on port ${port}`); - }); - } - - /** - * Deactivates the WebSocket server by stopping the HTTP server from accepting - * new connections and unsubscribing from the OrderWatcher - */ - public stop(): void { - this._httpServer.close(); - this._orderWatcher.unsubscribe(); - } - - private _log(...args: any[]): void { - if (this._isVerbose) { - logUtils.log(...args); - } - } - - private async _onMessageCallbackAsync(connection: WebSocket.connection, message: any): Promise { - let response: WebSocketResponse; - let id: number | null = null; - try { - assert.doesConformToSchema('message', message, schemas.orderWatcherWebSocketUtf8MessageSchema); - const request: WebSocketRequest = JSON.parse(message.utf8Data); - id = request.id; - assert.doesConformToSchema('request', request, schemas.orderWatcherWebSocketRequestSchema); - assert.isString(request.jsonrpc, JSON_RPC_VERSION); - response = { - id, - jsonrpc: JSON_RPC_VERSION, - method: request.method, - result: await this._routeRequestAsync(request), - }; - } catch (err) { - response = { - id, - jsonrpc: JSON_RPC_VERSION, - method: null, - error: err.toString(), - }; - } - this._log(`${new Date()} [Server] OrderWatcher output: ${JSON.stringify(response)}`); - connection.sendUTF(JSON.stringify(response)); - } - - private _onCloseCallback(connection: WebSocket.connection): void { - this._connectionStore.delete(connection); - this._log(`${new Date()} [Server] Client ${connection.remoteAddress} disconnected.`); - } - - private async _routeRequestAsync(request: WebSocketRequest): Promise { - this._log(`${new Date()} [Server] Request received: ${request.method}`); - switch (request.method) { - case OrderWatcherMethod.AddOrder: { - const signedOrder: SignedOrder = OrderWatcherWebSocketServer._parseSignedOrder( - request.params.signedOrder, - ); - await this._orderWatcher.addOrderAsync(signedOrder); - break; - } - case OrderWatcherMethod.RemoveOrder: { - this._orderWatcher.removeOrder(request.params.orderHash || 'undefined'); - break; - } - case OrderWatcherMethod.GetStats: { - return this._orderWatcher.getStats(); - } - default: - // Should never reach here. Should be caught by JSON schema check. - throw new Error(`Unexpected default case hit for request.method`); - } - return undefined; - } - - /** - * Broadcasts OrderState changes to ALL connected clients. At the moment, - * we do not support clients subscribing to only a subset of orders. As such, - * Client B will be notified of changes to an order that Client A added. - */ - private _broadcastCallback(err: Error | null, orderState?: OrderStateValid | OrderStateInvalid | undefined): void { - const method = OrderWatcherMethod.Update; - const response = - err === null - ? { - jsonrpc: JSON_RPC_VERSION, - method, - result: orderState, - } - : { - jsonrpc: JSON_RPC_VERSION, - method, - error: { - code: -32000, - message: err.message, - }, - }; - this._connectionStore.forEach((connection: WebSocket.connection) => { - connection.sendUTF(JSON.stringify(response)); - }); - } -} diff --git a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts new file mode 100644 index 000000000..fd388e907 --- /dev/null +++ b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts @@ -0,0 +1,289 @@ +import { ContractWrappers } from '@0x/contract-wrappers'; +import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { FillScenarios } from '@0x/fill-scenarios'; +import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; +import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; +import { BigNumber, logUtils } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import 'mocha'; +import * as WebSocket from 'websocket'; + +import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_web_socket_server'; +import { AddOrderRequest, OrderWatcherMethod, RemoveOrderRequest } from '../src/types'; + +import { chaiSetup } from './utils/chai_setup'; +import { constants } from './utils/constants'; +import { migrateOnceAsync } from './utils/migrate'; +import { provider, web3Wrapper } from './utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); + +interface WsMessage { + data: string; +} + +describe('OrderWatcherWebSocketServer', async () => { + let contractWrappers: ContractWrappers; + let wsServer: OrderWatcherWebSocketServer; + let wsClient: WebSocket.w3cwebsocket; + let wsClientTwo: WebSocket.w3cwebsocket; + let fillScenarios: FillScenarios; + let userAddresses: string[]; + let makerAssetData: string; + let takerAssetData: string; + let makerTokenAddress: string; + let takerTokenAddress: string; + let makerAddress: string; + let takerAddress: string; + let zrxTokenAddress: string; + let signedOrder: SignedOrder; + let orderHash: string; + let addOrderPayload: AddOrderRequest; + let removeOrderPayload: RemoveOrderRequest; + const decimals = constants.ZRX_DECIMALS; + const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); + // HACK: createFillableSignedOrderAsync is Promise-based, which forces us + // to use Promises instead of the done() callbacks for tests. + // onmessage callback must thus be wrapped as a Promise. + const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => + new Promise(resolve => { + client.onmessage = (msg: WsMessage) => resolve(msg); + }); + + before(async () => { + // Set up constants + const contractAddresses = await migrateOnceAsync(); + await blockchainLifecycle.startAsync(); + const networkId = constants.TESTRPC_NETWORK_ID; + const config = { + networkId, + contractAddresses, + }; + contractWrappers = new ContractWrappers(provider, config); + userAddresses = await web3Wrapper.getAvailableAddressesAsync(); + zrxTokenAddress = contractAddresses.zrxToken; + [makerAddress, takerAddress] = userAddresses; + [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); + [makerAssetData, takerAssetData] = [ + assetDataUtils.encodeERC20AssetData(makerTokenAddress), + assetDataUtils.encodeERC20AssetData(takerTokenAddress), + ]; + fillScenarios = new FillScenarios( + provider, + userAddresses, + zrxTokenAddress, + contractAddresses.exchange, + contractAddresses.erc20Proxy, + contractAddresses.erc721Proxy, + ); + signedOrder = await fillScenarios.createFillableSignedOrderAsync( + makerAssetData, + takerAssetData, + makerAddress, + takerAddress, + fillableAmount, + ); + orderHash = orderHashUtils.getOrderHashHex(signedOrder); + addOrderPayload = { + id: 1, + jsonrpc: '2.0', + method: OrderWatcherMethod.AddOrder, + params: { signedOrder }, + }; + removeOrderPayload = { + id: 1, + jsonrpc: '2.0', + method: OrderWatcherMethod.RemoveOrder, + params: { orderHash }, + }; + + // Prepare OrderWatcher WebSocket server + const orderWatcherConfig = { + isVerbose: true, + }; + wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); + wsServer.start(); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + wsServer.stop(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + wsClient.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); + + it('responds to getStats requests correctly', (done: any) => { + const payload = { + id: 1, + jsonrpc: '2.0', + method: 'GET_STATS', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); + wsClient.onmessage = (msg: any) => { + const responseData = JSON.parse(msg.data); + expect(responseData.id).to.be.eq(1); + expect(responseData.jsonrpc).to.be.eq('2.0'); + expect(responseData.method).to.be.eq('GET_STATS'); + expect(responseData.result.orderCount).to.be.eq(0); + done(); + }; + }); + + it('throws an error when an invalid method is attempted', async () => { + const invalidMethodPayload = { + id: 1, + jsonrpc: '2.0', + method: 'BAD_METHOD', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when jsonrpc field missing from request', async () => { + const noJsonRpcPayload = { + id: 1, + method: 'GET_STATS', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when we try to add an order without a signedOrder', async () => { + const noSignedOrderAddOrderPayload = { + id: 1, + jsonrpc: '2.0', + method: 'ADD_ORDER', + orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.jsonrpc).to.be.eq('2.0'); + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('throws an error when we try to add a bad signedOrder', async () => { + const invalidAddOrderPayload = { + id: 1, + jsonrpc: '2.0', + method: 'ADD_ORDER', + signedOrder: { + makerAddress: '0x0', + }, + }; + wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); + const errorMsg = await _onMessageAsync(wsClient); + const errorData = JSON.parse(errorMsg.data); + // tslint:disable-next-line:no-unused-expression + expect(errorData.id).to.be.null; + // tslint:disable-next-line:no-unused-expression + expect(errorData.method).to.be.null; + expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); + }); + + it('executes addOrder and removeOrder requests correctly', async () => { + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + const addOrderMsg = await _onMessageAsync(wsClient); + const addOrderData = JSON.parse(addOrderMsg.data); + expect(addOrderData.method).to.be.eq('ADD_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ + [orderHash]: signedOrder, + }); + + wsClient.send(JSON.stringify(removeOrderPayload)); + const removeOrderMsg = await _onMessageAsync(wsClient); + const removeOrderData = JSON.parse(removeOrderMsg.data); + expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); + expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ + [orderHash]: signedOrder, + }); + }); + + it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { + // Add the regular order + wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); + await _onMessageAsync(wsClient); + + // Set the allowance to 0 + await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); + + // Ensure that orderStateInvalid message is received. + const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); + const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); + expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); + const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; + expect(invalidOrderState.isValid).to.be.false(); + expect(invalidOrderState.orderHash).to.be.eq(orderHash); + expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance); + }); + + it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => { + // Prepare order + const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); + const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); + const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( + makerAssetData, + takerAssetData, + makerFee, + takerFee, + makerAddress, + takerAddress, + fillableAmount, + takerAddress, + ); + const nonZeroMakerFeeOrderPayload = { + id: 1, + jsonrpc: '2.0', + method: 'ADD_ORDER', + signedOrder: nonZeroMakerFeeSignedOrder, + }; + + // Set up a second client and have it add the order + wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); + logUtils.log(`${new Date()} [Client] Connected.`); + wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); + await _onMessageAsync(wsClientTwo); + + // Change the allowance + await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); + + // Check that both clients receive the emitted event + for (const client of [wsClient, wsClientTwo]) { + const updateMsg = await _onMessageAsync(client); + const updateData = JSON.parse(updateMsg.data); + const orderState = updateData.result as OrderStateValid; + expect(orderState.isValid).to.be.true(); + expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); + } + + wsClientTwo.close(); + logUtils.log(`${new Date()} [Client] Closed.`); + }); +}); diff --git a/packages/order-watcher/test/order_watcher_websocket_server_test.ts b/packages/order-watcher/test/order_watcher_websocket_server_test.ts deleted file mode 100644 index a66d2c6c2..000000000 --- a/packages/order-watcher/test/order_watcher_websocket_server_test.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { ContractWrappers } from '@0x/contract-wrappers'; -import { tokenUtils } from '@0x/contract-wrappers/lib/test/utils/token_utils'; -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { FillScenarios } from '@0x/fill-scenarios'; -import { assetDataUtils, orderHashUtils } from '@0x/order-utils'; -import { ExchangeContractErrs, OrderStateInvalid, OrderStateValid, SignedOrder } from '@0x/types'; -import { BigNumber, logUtils } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import 'mocha'; -import * as WebSocket from 'websocket'; - -import { OrderWatcherWebSocketServer } from '../src/order_watcher/order_watcher_websocket_server'; -import { AddOrderRequest, OrderWatcherMethod, RemoveOrderRequest } from '../src/types'; - -import { chaiSetup } from './utils/chai_setup'; -import { constants } from './utils/constants'; -import { migrateOnceAsync } from './utils/migrate'; -import { provider, web3Wrapper } from './utils/web3_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); - -interface WsMessage { - data: string; -} - -describe('OrderWatcherWebSocketServer', async () => { - let contractWrappers: ContractWrappers; - let wsServer: OrderWatcherWebSocketServer; - let wsClient: WebSocket.w3cwebsocket; - let wsClientTwo: WebSocket.w3cwebsocket; - let fillScenarios: FillScenarios; - let userAddresses: string[]; - let makerAssetData: string; - let takerAssetData: string; - let makerTokenAddress: string; - let takerTokenAddress: string; - let makerAddress: string; - let takerAddress: string; - let zrxTokenAddress: string; - let signedOrder: SignedOrder; - let orderHash: string; - let addOrderPayload: AddOrderRequest; - let removeOrderPayload: RemoveOrderRequest; - const decimals = constants.ZRX_DECIMALS; - const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); - // HACK: createFillableSignedOrderAsync is Promise-based, which forces us - // to use Promises instead of the done() callbacks for tests. - // onmessage callback must thus be wrapped as a Promise. - const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => - new Promise(resolve => { - client.onmessage = (msg: WsMessage) => resolve(msg); - }); - - before(async () => { - // Set up constants - const contractAddresses = await migrateOnceAsync(); - await blockchainLifecycle.startAsync(); - const networkId = constants.TESTRPC_NETWORK_ID; - const config = { - networkId, - contractAddresses, - }; - contractWrappers = new ContractWrappers(provider, config); - userAddresses = await web3Wrapper.getAvailableAddressesAsync(); - zrxTokenAddress = contractAddresses.zrxToken; - [makerAddress, takerAddress] = userAddresses; - [makerTokenAddress, takerTokenAddress] = tokenUtils.getDummyERC20TokenAddresses(); - [makerAssetData, takerAssetData] = [ - assetDataUtils.encodeERC20AssetData(makerTokenAddress), - assetDataUtils.encodeERC20AssetData(takerTokenAddress), - ]; - fillScenarios = new FillScenarios( - provider, - userAddresses, - zrxTokenAddress, - contractAddresses.exchange, - contractAddresses.erc20Proxy, - contractAddresses.erc721Proxy, - ); - signedOrder = await fillScenarios.createFillableSignedOrderAsync( - makerAssetData, - takerAssetData, - makerAddress, - takerAddress, - fillableAmount, - ); - orderHash = orderHashUtils.getOrderHashHex(signedOrder); - addOrderPayload = { - id: 1, - jsonrpc: '2.0', - method: OrderWatcherMethod.AddOrder, - params: { signedOrder }, - }; - removeOrderPayload = { - id: 1, - jsonrpc: '2.0', - method: OrderWatcherMethod.RemoveOrder, - params: { orderHash }, - }; - - // Prepare OrderWatcher WebSocket server - const orderWatcherConfig = { - isVerbose: true, - }; - wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); - wsServer.start(); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); - wsServer.stop(); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); - logUtils.log(`${new Date()} [Client] Connected.`); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - wsClient.close(); - logUtils.log(`${new Date()} [Client] Closed.`); - }); - - it('responds to getStats requests correctly', (done: any) => { - const payload = { - id: 1, - jsonrpc: '2.0', - method: 'GET_STATS', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(payload)); - wsClient.onmessage = (msg: any) => { - const responseData = JSON.parse(msg.data); - expect(responseData.id).to.be.eq(1); - expect(responseData.jsonrpc).to.be.eq('2.0'); - expect(responseData.method).to.be.eq('GET_STATS'); - expect(responseData.result.orderCount).to.be.eq(0); - done(); - }; - }); - - it('throws an error when an invalid method is attempted', async () => { - const invalidMethodPayload = { - id: 1, - jsonrpc: '2.0', - method: 'BAD_METHOD', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when jsonrpc field missing from request', async () => { - const noJsonRpcPayload = { - id: 1, - method: 'GET_STATS', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when we try to add an order without a signedOrder', async () => { - const noSignedOrderAddOrderPayload = { - id: 1, - jsonrpc: '2.0', - method: 'ADD_ORDER', - orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.jsonrpc).to.be.eq('2.0'); - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('throws an error when we try to add a bad signedOrder', async () => { - const invalidAddOrderPayload = { - id: 1, - jsonrpc: '2.0', - method: 'ADD_ORDER', - signedOrder: { - makerAddress: '0x0', - }, - }; - wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); - const errorData = JSON.parse(errorMsg.data); - // tslint:disable-next-line:no-unused-expression - expect(errorData.id).to.be.null; - // tslint:disable-next-line:no-unused-expression - expect(errorData.method).to.be.null; - expect(errorData.error).to.match(/^Error: Expected request to conform to schema/); - }); - - it('executes addOrder and removeOrder requests correctly', async () => { - wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - const addOrderMsg = await _onMessageAsync(wsClient); - const addOrderData = JSON.parse(addOrderMsg.data); - expect(addOrderData.method).to.be.eq('ADD_ORDER'); - expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ - [orderHash]: signedOrder, - }); - - wsClient.send(JSON.stringify(removeOrderPayload)); - const removeOrderMsg = await _onMessageAsync(wsClient); - const removeOrderData = JSON.parse(removeOrderMsg.data); - expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); - expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ - [orderHash]: signedOrder, - }); - }); - - it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { - // Add the regular order - wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - await _onMessageAsync(wsClient); - - // Set the allowance to 0 - await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); - - // Ensure that orderStateInvalid message is received. - const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); - const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); - expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); - const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; - expect(invalidOrderState.isValid).to.be.false(); - expect(invalidOrderState.orderHash).to.be.eq(orderHash); - expect(invalidOrderState.error).to.be.eq(ExchangeContractErrs.InsufficientMakerAllowance); - }); - - it('broadcasts to multiple clients when an order backing ZRX allowance changes', async () => { - // Prepare order - const makerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(2), decimals); - const takerFee = Web3Wrapper.toBaseUnitAmount(new BigNumber(0), decimals); - const nonZeroMakerFeeSignedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync( - makerAssetData, - takerAssetData, - makerFee, - takerFee, - makerAddress, - takerAddress, - fillableAmount, - takerAddress, - ); - const nonZeroMakerFeeOrderPayload = { - id: 1, - jsonrpc: '2.0', - method: 'ADD_ORDER', - signedOrder: nonZeroMakerFeeSignedOrder, - }; - - // Set up a second client and have it add the order - wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); - logUtils.log(`${new Date()} [Client] Connected.`); - wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); - await _onMessageAsync(wsClientTwo); - - // Change the allowance - await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); - - // Check that both clients receive the emitted event - for (const client of [wsClient, wsClientTwo]) { - const updateMsg = await _onMessageAsync(client); - const updateData = JSON.parse(updateMsg.data); - const orderState = updateData.result as OrderStateValid; - expect(orderState.isValid).to.be.true(); - expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); - } - - wsClientTwo.close(); - logUtils.log(`${new Date()} [Client] Closed.`); - }); -}); -- cgit v1.2.3 From 28713bdb3859e24b78e784722813b03e95788e1f Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 17 Dec 2018 11:36:03 -0800 Subject: Pull approval events for ZRX and DAI (#1430) --- packages/pipeline/src/scripts/pull_erc20_events.ts | 68 ++++++++++++++++------ 1 file changed, 49 insertions(+), 19 deletions(-) (limited to 'packages') diff --git a/packages/pipeline/src/scripts/pull_erc20_events.ts b/packages/pipeline/src/scripts/pull_erc20_events.ts index 0ad12c97a..bd520c610 100644 --- a/packages/pipeline/src/scripts/pull_erc20_events.ts +++ b/packages/pipeline/src/scripts/pull_erc20_events.ts @@ -1,10 +1,10 @@ -// tslint:disable:no-console import { getContractAddressesForNetworkOrThrow } from '@0x/contract-addresses'; import { web3Factory } from '@0x/dev-utils'; import { Web3ProviderEngine } from '@0x/subproviders'; +import { logUtils } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import 'reflect-metadata'; -import { Connection, ConnectionOptions, createConnection, Repository } from 'typeorm'; +import { Connection, ConnectionOptions, createConnection } from 'typeorm'; import { ERC20EventsSource } from '../data_sources/contract-wrappers/erc20_events'; import { ERC20ApprovalEvent } from '../entities'; @@ -16,33 +16,63 @@ const NETWORK_ID = 1; const START_BLOCK_OFFSET = 100; // Number of blocks before the last known block to consider when updating fill events. const BATCH_SAVE_SIZE = 1000; // Number of events to save at once. const BLOCK_FINALITY_THRESHOLD = 10; // When to consider blocks as final. Used to compute default endBlock. -const WETH_START_BLOCK = 4719568; // Block number when the WETH contract was deployed. let connection: Connection; +interface Token { + // name is used for logging only. + name: string; + address: string; + defaultStartBlock: number; +} + +const tokensToGetApprovalEvents: Token[] = [ + { + name: 'WETH', + address: getContractAddressesForNetworkOrThrow(NETWORK_ID).etherToken, + defaultStartBlock: 4719568, // Block when the WETH contract was deployed. + }, + { + name: 'ZRX', + address: getContractAddressesForNetworkOrThrow(NETWORK_ID).zrxToken, + defaultStartBlock: 4145415, // Block when the ZRX contract was deployed. + }, + { + name: 'DAI', + address: '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', + defaultStartBlock: 4752008, // Block when the DAI contract was deployed. + }, +]; + (async () => { connection = await createConnection(ormConfig as ConnectionOptions); const provider = web3Factory.getRpcProvider({ rpcUrl: INFURA_ROOT_URL, }); const endBlock = await calculateEndBlockAsync(provider); - await getAndSaveWETHApprovalEventsAsync(provider, endBlock); + for (const token of tokensToGetApprovalEvents) { + await getAndSaveApprovalEventsAsync(provider, token, endBlock); + } process.exit(0); })().catch(handleError); -async function getAndSaveWETHApprovalEventsAsync(provider: Web3ProviderEngine, endBlock: number): Promise { - console.log('Checking existing approval events...'); +async function getAndSaveApprovalEventsAsync( + provider: Web3ProviderEngine, + token: Token, + endBlock: number, +): Promise { + logUtils.log(`Getting approval events for ${token.name}...`); + logUtils.log('Checking existing approval events...'); const repository = connection.getRepository(ERC20ApprovalEvent); - const startBlock = (await getStartBlockAsync(repository)) || WETH_START_BLOCK; + const startBlock = (await getStartBlockAsync(token)) || token.defaultStartBlock; - console.log(`Getting WETH approval events starting at ${startBlock}...`); - const wethTokenAddress = getContractAddressesForNetworkOrThrow(NETWORK_ID).etherToken; - const eventsSource = new ERC20EventsSource(provider, NETWORK_ID, wethTokenAddress); + logUtils.log(`Getting approval events starting at ${startBlock}...`); + const eventsSource = new ERC20EventsSource(provider, NETWORK_ID, token.address); const eventLogs = await eventsSource.getApprovalEventsAsync(startBlock, endBlock); - console.log(`Parsing ${eventLogs.length} WETH approval events...`); + logUtils.log(`Parsing ${eventLogs.length} approval events...`); const events = parseERC20ApprovalEvents(eventLogs); - console.log(`Retrieved and parsed ${events.length} total WETH approval events.`); + logUtils.log(`Retrieved and parsed ${events.length} total approval events.`); await repository.save(events, { chunk: Math.ceil(events.length / BATCH_SAVE_SIZE) }); } @@ -52,15 +82,15 @@ async function calculateEndBlockAsync(provider: Web3ProviderEngine): Promise): Promise { - const fillEventCount = await repository.count(); - if (fillEventCount === 0) { - console.log(`No existing approval events found.`); - return null; - } +async function getStartBlockAsync(token: Token): Promise { const queryResult = await connection.query( - `SELECT block_number FROM raw.erc20_approval_events ORDER BY block_number DESC LIMIT 1`, + `SELECT block_number FROM raw.erc20_approval_events WHERE token_address = $1 ORDER BY block_number DESC LIMIT 1`, + [token.address], ); + if (queryResult.length === 0) { + logUtils.log(`No existing approval events found for ${token.name}.`); + return null; + } const lastKnownBlock = queryResult[0].block_number; return lastKnownBlock - START_BLOCK_OFFSET; } -- cgit v1.2.3 From 44d9cc53b87c5a19586c05030292b5b21351dc74 Mon Sep 17 00:00:00 2001 From: Alex Browne Date: Mon, 17 Dec 2018 17:07:22 -0800 Subject: Fix bug in pull_missing_blocks with incorrect start block (#1438) --- packages/pipeline/src/scripts/pull_missing_blocks.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/pipeline/src/scripts/pull_missing_blocks.ts b/packages/pipeline/src/scripts/pull_missing_blocks.ts index a5203824c..bb5385126 100644 --- a/packages/pipeline/src/scripts/pull_missing_blocks.ts +++ b/packages/pipeline/src/scripts/pull_missing_blocks.ts @@ -9,7 +9,7 @@ import { Web3Source } from '../data_sources/web3'; import { Block } from '../entities'; import * as ormConfig from '../ormconfig'; import { parseBlock } from '../parsers/web3'; -import { EXCHANGE_START_BLOCK, handleError, INFURA_ROOT_URL } from '../utils'; +import { handleError, INFURA_ROOT_URL } from '../utils'; // Number of blocks to save at once. const BATCH_SAVE_SIZE = 1000; @@ -37,22 +37,19 @@ interface MissingBlocksResponse { async function getAllMissingBlocksAsync(web3Source: Web3Source): Promise { const blocksRepository = connection.getRepository(Block); - let fromBlock = EXCHANGE_START_BLOCK; while (true) { - const blockNumbers = await getMissingBlockNumbersAsync(fromBlock); + const blockNumbers = await getMissingBlockNumbersAsync(); if (blockNumbers.length === 0) { // There are no more missing blocks. We're done. break; } await getAndSaveBlocksAsync(web3Source, blocksRepository, blockNumbers); - fromBlock = Math.max(...blockNumbers) + 1; } const totalBlocks = await blocksRepository.count(); console.log(`Done saving blocks. There are now ${totalBlocks} total blocks.`); } -async function getMissingBlockNumbersAsync(fromBlock: number): Promise { - console.log(`Checking for missing blocks starting at ${fromBlock}...`); +async function getMissingBlockNumbersAsync(): Promise { // Note(albrow): The easiest way to get all the blocks we need is to // consider all the events tables together in a single query. If this query // gets too slow, we should consider re-architecting so that we can work on @@ -66,13 +63,12 @@ async function getMissingBlockNumbersAsync(fromBlock: number): Promise ) SELECT DISTINCT(block_number) FROM all_events WHERE block_number NOT IN (SELECT number FROM raw.blocks) - AND block_number >= $1 - ORDER BY block_number ASC LIMIT $2`, - [fromBlock, MAX_BLOCKS_PER_QUERY], + ORDER BY block_number ASC LIMIT $1`, + [MAX_BLOCKS_PER_QUERY], )) as MissingBlocksResponse[]; const blockNumberStrings = R.pluck('block_number', response); const blockNumbers = R.map(parseInt, blockNumberStrings); - console.log(`Found ${blockNumbers.length} missing blocks in the given range.`); + console.log(`Found ${blockNumbers.length} missing blocks.`); return blockNumbers; } -- cgit v1.2.3 From 18e55830b5816c342d8e9d9f64ae035f9284ea80 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 18 Dec 2018 15:31:58 +0000 Subject: Fix version picker so it doesn't overflow onto two lines --- .../ts/components/documentation/sidebar_header.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/website/ts/components/documentation/sidebar_header.tsx b/packages/website/ts/components/documentation/sidebar_header.tsx index 9ced52c74..0ab24ab5e 100644 --- a/packages/website/ts/components/documentation/sidebar_header.tsx +++ b/packages/website/ts/components/documentation/sidebar_header.tsx @@ -24,7 +24,7 @@ export const SidebarHeader: React.StatelessComponent = ({ return ( - + = ({ {!_.isUndefined(docsVersion) && !_.isUndefined(availableDocVersions) && !_.isUndefined(onVersionSelected) && ( -
- +
+ + +
)} -- cgit v1.2.3 From 67df5a433d68a2af1a3a03a8bf431629a534dc97 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 18 Dec 2018 15:32:11 +0000 Subject: Fix OrderWatcher title to fix sidebar top --- packages/website/ts/containers/order_watcher_documentation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/website/ts/containers/order_watcher_documentation.ts b/packages/website/ts/containers/order_watcher_documentation.ts index ac92e6a22..683e1fe9f 100644 --- a/packages/website/ts/containers/order_watcher_documentation.ts +++ b/packages/website/ts/containers/order_watcher_documentation.ts @@ -24,7 +24,7 @@ const docsInfoConfig: DocsInfoConfig = { id: DocPackages.OrderWatcher, packageName: '@0x/order-watcher', type: SupportedDocJson.TypeDoc, - displayName: 'OrderWatcher', + displayName: 'Order Watcher', packageUrl: 'https://github.com/0xProject/0x-monorepo', markdownMenu: { 'getting-started': [markdownSections.introduction, markdownSections.installation], -- cgit v1.2.3 From e295eeb8938468b1527d5d81f212766cef40bc81 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Tue, 18 Dec 2018 16:25:26 +0000 Subject: Remove unused file --- packages/order-watcher/src/schemas/websocket_schemas.ts | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 packages/order-watcher/src/schemas/websocket_schemas.ts (limited to 'packages') diff --git a/packages/order-watcher/src/schemas/websocket_schemas.ts b/packages/order-watcher/src/schemas/websocket_schemas.ts deleted file mode 100644 index df54a38e1..000000000 --- a/packages/order-watcher/src/schemas/websocket_schemas.ts +++ /dev/null @@ -1,2 +0,0 @@ -// TODO: Move these schemas to the `json-schemas` package and convert to JSON -// Rename to `OrderWatcherWebSocketRequestSchema`, etc... -- cgit v1.2.3 From 07c1a0121f7be48dfe1a70ba0dae08f78e7a8b02 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 26 Nov 2018 14:32:55 -0800 Subject: Untested - Compliant Forwarder with Wyre "Yes Compliance" Token --- .../CompliantForwarder/CompliantForwarder.sol | 101 +++++++++++++++++ .../YesComplianceToken/YesComplianceToken.sol | 119 +++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol new file mode 100644 index 000000000..646a9f3b8 --- /dev/null +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -0,0 +1,101 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/interfaces/IExchange.sol"; +import "../../tokens/ERC721Token/IERC721Token.sol"; +import "../../utils/LibBytes/LibBytes.sol"; + +contract CompliantForwarder { + + using LibBytes for bytes; + + bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR = bytes4(keccak256("fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)")); + IExchange internal EXCHANGE; + IERC721Token internal COMPLIANCE_TOKEN; + + constructor(address exchange, address complianceToken) + public + { + EXCHANGE = IExchange(exchange); + COMPLIANCE_TOKEN = IERC721Token(complianceToken); + } + + function fillOrder( + uint256 salt, + address signerAddress, + bytes signedFillOrderTransaction, + bytes signature + ) + public + { + // Validate `signedFillOrderTransaction` + bytes4 selector = signedFillOrderTransaction.readBytes4(0); + if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { + revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); + } + + // Extract maker address from fill order transaction + // Below is the table of calldata offsets into a fillOrder transaction. + /** + ### parameters + 0x00 ptr + 0x20 takerAssetFillAmount + 0x40 ptr + ### order + 0x60 makerAddress + 0x80 takerAddress + 0xa0 feeRecipientAddress + 0xc0 senderAddress + 0xe0 makerAssetAmount + 0x100 takerAssetAmount + 0x120 makerFee + 0x140 takerFee + 0x160 expirationTimeSeconds + 0x180 salt + 0x1a0 ptr + 0x1c0 ptr + 0x1e0 makerAssetData + * takerAssetData + * signature + ------------------------------ + * Context-dependent offsets, unknown at compile time. + */ + // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. + // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. + // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 + address makerAddress = signedFillOrderTransaction.readAddress(0x70); + + // Verify maker/taker have been verified by the compliance token. + if (COMPLIANCE_TOKEN.balanceOf(makerAddress) == 0) { + revert("MAKER_UNVERIFIED"); + } else if (COMPLIANCE_TOKEN.balanceOf(signerAddress) == 0) { + revert("TAKER_UNVERIFIED"); + } + + // All entities are verified. Execute fillOrder. + EXCHANGE.executeTransaction( + salt, + signerAddress, + signedFillOrderTransaction, + signature + ); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol new file mode 100644 index 000000000..abd04219d --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol @@ -0,0 +1,119 @@ +pragma solidity ^0.4.24; + +import "../ERC721Token/ERC721Token.sol"; + +/** + * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific + * compliance-related queries with YES. (attestations) + * + * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful + * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to + * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. + * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. + * + * the financial systems these users authenticate against have a different set of API requirements. they need + * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic + * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. + * + * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) + * + * notes: + * - no address can be associated with more than one identity (though addresses may have more than token). issuance + * in this circumstance will fail + * - one person or business = one entity + * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will + * - two token types: control & non-control. both carry compliance proof + * - control tokens let their holders mint and burn (within the same entity) + * - non-control tokens are solely for compliance queries + * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to + * redistribute a fleet of coins + * - all country codes should be via ISO-3166-1 + * + * any (non-view) methods not explicitly marked idempotent are not idempotent. + */ +contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { + + uint256 public constant OWNER_ENTITY_ID = 1; + + uint8 public constant YESMARK_OWNER = 128; + uint8 public constant YESMARK_VALIDATOR = 129; + + /* + todo events: entity updated, destroyed, ???? + Finalized + Attested + + */ + + /** + * @notice query api: returns true if the specified address has the given country/yes attestation. this + * is the primary method partners will use to query the active qualifications of any particular + * address. + */ + function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; + + /** @notice same as isYes except as an imperative */ + function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; + + /** + * @notice retrieve all YES marks for an address in a particular country + * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them + * @param _address the validator ID to consider, or 0 for any of them + * @param _countryCode the ISO-3166-1 country code + * @return (non-duplicate) array of YES marks present + */ + function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); + + // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); + + /** + * @notice create new tokens. fail if _to already + * belongs to a different entity and caller is not validator + * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. + * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller + * @return the newly created token ID + */ + function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); + + /** @notice shortcut to mint() + setYes() in one call, for a single country */ + function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); + + /** @notice destroys a specific token */ + function burn(uint256 _tokenId) external; + + /** @notice destroys the entire entity and all tokens */ + function burnEntity(uint256 _entityId) external; + + /** + * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark + * was already set by this validator + */ + function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** + * @notice removes a attestation(s) from a specific validator for an entity. idempotent + */ + function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** @notice removes all attestations in a given country for a particular entity. idempotent */ + function clearYes(uint256 _entityId, uint16 _countryCode) external; + + /** @notice removes all attestations for a particular entity. idempotent */ + function clearYes(uint256 _entityId) external; + + /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ + function setLocked(uint256 _entityId, bool _lock) external; + + /** @notice checks whether or not a particular entity is locked */ + function isLocked(uint256 _entityId) external view returns(bool); + + /** @notice returns true if the specified token has been finalized (cannot be moved) */ + function isFinalized(uint256 _tokenId) external view returns(bool); + + /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ + function finalize(uint256 _tokenId) external; + + /** @return the entity ID associated with an address (or fail if there is not one) */ + function getEntityId(address _address) external view returns(uint256); + +} \ No newline at end of file -- cgit v1.2.3 From f104d91595397a4e7ce82a36de92b09e76d9d507 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 26 Nov 2018 17:29:32 -0800 Subject: Broken commit -- saving current state of getting wyre compliance token contract setup --- .../YesComplianceToken/IYesComplianceToken.sol | 118 ++++ .../YesComplianceToken/WyreERC721Token/ERC721.sol | 40 ++ .../WyreERC721Token/ERC721Basic.sol | 47 ++ .../WyreERC721Token/ERC721BasicToken.sol | 353 +++++++++++ .../WyreERC721Token/ERC721Token.sol | 218 +++++++ .../YesComplianceToken/YesComplianceToken.sol | 693 ++++++++++++++++++--- .../test/extensions/compliant_forwarder.ts | 191 ++++++ 7 files changed, 1579 insertions(+), 81 deletions(-) create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol create mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol create mode 100644 packages/contracts/test/extensions/compliant_forwarder.ts (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol new file mode 100644 index 000000000..1573c6bac --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol @@ -0,0 +1,118 @@ +pragma solidity ^0.4.24; + + +/** + * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific + * compliance-related queries with YES. (attestations) + * + * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful + * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to + * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. + * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. + * + * the financial systems these users authenticate against have a different set of API requirements. they need + * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic + * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. + * + * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) + * + * notes: + * - no address can be associated with more than one identity (though addresses may have more than token). issuance + * in this circumstance will fail + * - one person or business = one entity + * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will + * - two token types: control & non-control. both carry compliance proof + * - control tokens let their holders mint and burn (within the same entity) + * - non-control tokens are solely for compliance queries + * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to + * redistribute a fleet of coins + * - all country codes should be via ISO-3166-1 + * + * any (non-view) methods not explicitly marked idempotent are not idempotent. + */ +contract YesComplianceTokenV1 /*is ERC721Token*/ /*, ERC165 :should: */ { + + uint256 public constant OWNER_ENTITY_ID = 1; + + uint8 public constant YESMARK_OWNER = 128; + uint8 public constant YESMARK_VALIDATOR = 129; + + /* + todo events: entity updated, destroyed, ???? + Finalized + Attested + + */ + + /** + * @notice query api: returns true if the specified address has the given country/yes attestation. this + * is the primary method partners will use to query the active qualifications of any particular + * address. + */ + function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; + + /** @notice same as isYes except as an imperative */ + function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; + + /** + * @notice retrieve all YES marks for an address in a particular country + * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them + * @param _address the validator ID to consider, or 0 for any of them + * @param _countryCode the ISO-3166-1 country code + * @return (non-duplicate) array of YES marks present + */ + function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); + + // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); + + /** + * @notice create new tokens. fail if _to already + * belongs to a different entity and caller is not validator + * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. + * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller + * @return the newly created token ID + */ + function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); + + /** @notice shortcut to mint() + setYes() in one call, for a single country */ + function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); + + /** @notice destroys a specific token */ + function burn(uint256 _tokenId) external; + + /** @notice destroys the entire entity and all tokens */ + function burnEntity(uint256 _entityId) external; + + /** + * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark + * was already set by this validator + */ + function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** + * @notice removes a attestation(s) from a specific validator for an entity. idempotent + */ + function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + + /** @notice removes all attestations in a given country for a particular entity. idempotent */ + function clearYes(uint256 _entityId, uint16 _countryCode) external; + + /** @notice removes all attestations for a particular entity. idempotent */ + function clearYes(uint256 _entityId) external; + + /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ + function setLocked(uint256 _entityId, bool _lock) external; + + /** @notice checks whether or not a particular entity is locked */ + function isLocked(uint256 _entityId) external view returns(bool); + + /** @notice returns true if the specified token has been finalized (cannot be moved) */ + function isFinalized(uint256 _tokenId) external view returns(bool); + + /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ + function finalize(uint256 _tokenId) external; + + /** @return the entity ID associated with an address (or fail if there is not one) */ + function getEntityId(address _address) external view returns(uint256); + +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol new file mode 100644 index 000000000..5b4907f13 --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol @@ -0,0 +1,40 @@ +pragma solidity ^0.4.21; + +import "./ERC721Basic.sol"; + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Enumerable is ERC721Basic { + function totalSupply() public view returns (uint256); + function tokenOfOwnerByIndex( + address _owner, + uint256 _index + ) + public + view + returns (uint256 _tokenId); + + function tokenByIndex(uint256 _index) public view returns (uint256); +} + + +/** + * @title ERC-721 Non-Fungible Token Standard, optional metadata extension + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Metadata is ERC721Basic { + function name() external view returns (string _name); + function symbol() external view returns (string _symbol); + function tokenURI(uint256 _tokenId) public view returns (string); +} + + +/** + * @title ERC-721 Non-Fungible Token Standard, full implementation interface + * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721 is ERC721Basic, ERC721Enumerable, ERC721Metadata { +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol new file mode 100644 index 000000000..20f3c5812 --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol @@ -0,0 +1,47 @@ +pragma solidity ^0.4.21; + +/** + * @title ERC721 Non-Fungible Token Standard basic interface + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Basic { + event Transfer( + address indexed _from, + address indexed _to, + uint256 indexed _tokenId + ); + event Approval( + address indexed _owner, + address indexed _approved, + uint256 indexed _tokenId + ); + event ApprovalForAll( + address indexed _owner, + address indexed _operator, + bool _approved + ); + + function balanceOf(address _owner) public view returns (uint256 _balance); + function ownerOf(uint256 _tokenId) public view returns (address _owner); + function exists(uint256 _tokenId) public view returns (bool _exists); + + function approve(address _to, uint256 _tokenId) public; + function getApproved(uint256 _tokenId) + public view returns (address _operator); + + function setApprovalForAll(address _operator, bool _approved) public; + function isApprovedForAll(address _owner, address _operator) + public view returns (bool); + + function transferFrom(address _from, address _to, uint256 _tokenId) public; + function safeTransferFrom(address _from, address _to, uint256 _tokenId) + public; + + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId, + bytes _data + ) + public; +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol new file mode 100644 index 000000000..1d3fa37b8 --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol @@ -0,0 +1,353 @@ +pragma solidity ^0.4.21; + +import "./ERC721Basic.sol"; +import "./ERC721Receiver.sol"; +import "../../math/SafeMath.sol"; +import "../../AddressUtils.sol"; +import "../../introspection/ERC165Support.sol"; + + +/** + * @title ERC721 Non-Fungible Token Standard basic implementation + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721BasicToken is ERC165Support, ERC721Basic { + + bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; + /* + * 0x80ac58cd === + * bytes4(keccak256('balanceOf(address)')) ^ + * bytes4(keccak256('ownerOf(uint256)')) ^ + * bytes4(keccak256('approve(address,uint256)')) ^ + * bytes4(keccak256('getApproved(uint256)')) ^ + * bytes4(keccak256('setApprovalForAll(address,bool)')) ^ + * bytes4(keccak256('isApprovedForAll(address,address)')) ^ + * bytes4(keccak256('transferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^ + * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) + */ + + bytes4 private constant InterfaceId_ERC721Exists = 0x4f558e79; + /* + * 0x4f558e79 === + * bytes4(keccak256('exists(uint256)')) + */ + + using SafeMath for uint256; + using AddressUtils for address; + + // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` + bytes4 private constant ERC721_RECEIVED = 0x150b7a02; + + // Mapping from token ID to owner + mapping (uint256 => address) internal tokenOwner; + + // Mapping from token ID to approved address + mapping (uint256 => address) internal tokenApprovals; + + // Mapping from owner to number of owned token + mapping (address => uint256) internal ownedTokensCount; + + // Mapping from owner to operator approvals + mapping (address => mapping (address => bool)) internal operatorApprovals; + + /** + * @dev Guarantees msg.sender is owner of the given token + * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender + */ + modifier onlyOwnerOf(uint256 _tokenId) { + require(ownerOf(_tokenId) == msg.sender); + _; + } + + /** + * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator + * @param _tokenId uint256 ID of the token to validate + */ + modifier canTransfer(uint256 _tokenId) { + require(isApprovedOrOwner(msg.sender, _tokenId)); + _; + } + + function _supportsInterface(bytes4 _interfaceId) + internal + view + returns (bool) + { + return super._supportsInterface(_interfaceId) || + _interfaceId == InterfaceId_ERC721 || _interfaceId == InterfaceId_ERC721Exists; + } + + /** + * @dev Gets the balance of the specified address + * @param _owner address to query the balance of + * @return uint256 representing the amount owned by the passed address + */ + function balanceOf(address _owner) public view returns (uint256) { + require(_owner != address(0)); + return ownedTokensCount[_owner]; + } + + /** + * @dev Gets the owner of the specified token ID + * @param _tokenId uint256 ID of the token to query the owner of + * @return owner address currently marked as the owner of the given token ID + */ + function ownerOf(uint256 _tokenId) public view returns (address) { + address owner = tokenOwner[_tokenId]; + require(owner != address(0)); + return owner; + } + + /** + * @dev Returns whether the specified token exists + * @param _tokenId uint256 ID of the token to query the existence of + * @return whether the token exists + */ + function exists(uint256 _tokenId) public view returns (bool) { + address owner = tokenOwner[_tokenId]; + return owner != address(0); + } + + /** + * @dev Approves another address to transfer the given token ID + * The zero address indicates there is no approved address. + * There can only be one approved address per token at a given time. + * Can only be called by the token owner or an approved operator. + * @param _to address to be approved for the given token ID + * @param _tokenId uint256 ID of the token to be approved + */ + function approve(address _to, uint256 _tokenId) public { + address owner = ownerOf(_tokenId); + require(_to != owner); + require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); + + tokenApprovals[_tokenId] = _to; + emit Approval(owner, _to, _tokenId); + } + + /** + * @dev Gets the approved address for a token ID, or zero if no address set + * @param _tokenId uint256 ID of the token to query the approval of + * @return address currently approved for the given token ID + */ + function getApproved(uint256 _tokenId) public view returns (address) { + return tokenApprovals[_tokenId]; + } + + /** + * @dev Sets or unsets the approval of a given operator + * An operator is allowed to transfer all tokens of the sender on their behalf + * @param _to operator address to set the approval + * @param _approved representing the status of the approval to be set + */ + function setApprovalForAll(address _to, bool _approved) public { + require(_to != msg.sender); + operatorApprovals[msg.sender][_to] = _approved; + emit ApprovalForAll(msg.sender, _to, _approved); + } + + /** + * @dev Tells whether an operator is approved by a given owner + * @param _owner owner address which you want to query the approval of + * @param _operator operator address which you want to query the approval of + * @return bool whether the given operator is approved by the given owner + */ + function isApprovedForAll( + address _owner, + address _operator + ) + public + view + returns (bool) + { + return operatorApprovals[_owner][_operator]; + } + + /** + * @dev Transfers the ownership of a given token ID to another address + * Usage of this method is discouraged, use `safeTransferFrom` whenever possible + * Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + */ + function transferFrom( + address _from, + address _to, + uint256 _tokenId + ) + public + canTransfer(_tokenId) + { + require(_from != address(0)); + require(_to != address(0)); + + clearApproval(_from, _tokenId); + removeTokenFrom(_from, _tokenId); + addTokenTo(_to, _tokenId); + + emit Transfer(_from, _to, _tokenId); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * + * Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + */ + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId + ) + public + canTransfer(_tokenId) + { + // solium-disable-next-line arg-overflow + safeTransferFrom(_from, _to, _tokenId, ""); + } + + /** + * @dev Safely transfers the ownership of a given token ID to another address + * If the target address is a contract, it must implement `onERC721Received`, + * which is called upon a safe transfer, and return the magic value + * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, + * the transfer is reverted. + * Requires the msg sender to be the owner, approved, or operator + * @param _from current owner of the token + * @param _to address to receive the ownership of the given token ID + * @param _tokenId uint256 ID of the token to be transferred + * @param _data bytes data to send along with a safe transfer check + */ + function safeTransferFrom( + address _from, + address _to, + uint256 _tokenId, + bytes _data + ) + public + canTransfer(_tokenId) + { + transferFrom(_from, _to, _tokenId); + // solium-disable-next-line arg-overflow + require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data)); + } + + /** + * @dev Returns whether the given spender can transfer a given token ID + * @param _spender address of the spender to query + * @param _tokenId uint256 ID of the token to be transferred + * @return bool whether the msg.sender is approved for the given token ID, + * is an operator of the owner, or is the owner of the token + */ + function isApprovedOrOwner( + address _spender, + uint256 _tokenId + ) + internal + view + returns (bool) + { + address owner = ownerOf(_tokenId); + // Disable solium check because of + // https://github.com/duaraghav8/Solium/issues/175 + // solium-disable-next-line operator-whitespace + return ( + _spender == owner || + getApproved(_tokenId) == _spender || + isApprovedForAll(owner, _spender) + ); + } + + /** + * @dev Internal function to mint a new token + * Reverts if the given token ID already exists + * @param _to The address that will own the minted token + * @param _tokenId uint256 ID of the token to be minted by the msg.sender + */ + function _mint(address _to, uint256 _tokenId) internal { + require(_to != address(0)); + addTokenTo(_to, _tokenId); + emit Transfer(address(0), _to, _tokenId); + } + + /** + * @dev Internal function to burn a specific token + * Reverts if the token does not exist + * @param _tokenId uint256 ID of the token being burned by the msg.sender + */ + function _burn(address _owner, uint256 _tokenId) internal { + clearApproval(_owner, _tokenId); + removeTokenFrom(_owner, _tokenId); + emit Transfer(_owner, address(0), _tokenId); + } + + /** + * @dev Internal function to clear current approval of a given token ID + * Reverts if the given address is not indeed the owner of the token + * @param _owner owner of the token + * @param _tokenId uint256 ID of the token to be transferred + */ + function clearApproval(address _owner, uint256 _tokenId) internal { + require(ownerOf(_tokenId) == _owner); + if (tokenApprovals[_tokenId] != address(0)) { + tokenApprovals[_tokenId] = address(0); + } + } + + /** + * @dev Internal function to add a token ID to the list of a given address + * @param _to address representing the new owner of the given token ID + * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function addTokenTo(address _to, uint256 _tokenId) internal { + require(tokenOwner[_tokenId] == address(0)); + tokenOwner[_tokenId] = _to; + ownedTokensCount[_to] = ownedTokensCount[_to].add(1); + } + + /** + * @dev Internal function to remove a token ID from the list of a given address + * @param _from address representing the previous owner of the given token ID + * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function removeTokenFrom(address _from, uint256 _tokenId) internal { + require(ownerOf(_tokenId) == _from); + ownedTokensCount[_from] = ownedTokensCount[_from].sub(1); + tokenOwner[_tokenId] = address(0); + } + + /** + * @dev Internal function to invoke `onERC721Received` on a target address + * The call is not executed if the target address is not a contract + * @param _from address representing the previous owner of the given token ID + * @param _to target address that will receive the tokens + * @param _tokenId uint256 ID of the token to be transferred + * @param _data bytes optional data to send along with the call + * @return whether the call correctly returned the expected magic value + */ + function checkAndCallSafeTransfer( + address _from, + address _to, + uint256 _tokenId, + bytes _data + ) + internal + returns (bool) + { + if (!_to.isContract()) { + return true; + } + bytes4 retval = ERC721Receiver(_to).onERC721Received( + msg.sender, _from, _tokenId, _data); + return (retval == ERC721_RECEIVED); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol new file mode 100644 index 000000000..c7acee6df --- /dev/null +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol @@ -0,0 +1,218 @@ +pragma solidity ^0.4.21; + +import "./ERC721.sol"; +import "./ERC721BasicToken.sol"; + + +/** + * @title Full ERC721 Token + * This implementation includes all the required and some optional functionality of the ERC721 standard + * Moreover, it includes approve all functionality using operator terminology + * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md + */ +contract ERC721Token is ERC721BasicToken, ERC721 { + + bytes4 private constant InterfaceId_ERC721Enumerable = 0x780e9d63; + /** + * 0x780e9d63 === + * bytes4(keccak256('totalSupply()')) ^ + * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^ + * bytes4(keccak256('tokenByIndex(uint256)')) + */ + + bytes4 private constant InterfaceId_ERC721Metadata = 0x5b5e139f; + /** + * 0x5b5e139f === + * bytes4(keccak256('name()')) ^ + * bytes4(keccak256('symbol()')) ^ + * bytes4(keccak256('tokenURI(uint256)')) + */ + + // Token name + string internal name_; + + // Token symbol + string internal symbol_; + + // Mapping from owner to list of owned token IDs + mapping(address => uint256[]) internal ownedTokens; + + // Mapping from token ID to index of the owner tokens list + mapping(uint256 => uint256) internal ownedTokensIndex; + + // Array with all token ids, used for enumeration + uint256[] internal allTokens; + + // Mapping from token id to position in the allTokens array + mapping(uint256 => uint256) internal allTokensIndex; + + // Optional mapping for token URIs + mapping(uint256 => string) internal tokenURIs; + + /** + * @dev Constructor function + */ + function initialize(string _name, string _symbol) public isInitializer("ERC721Token", "1.9.0") { + name_ = _name; + symbol_ = _symbol; + } + + function _supportsInterface(bytes4 _interfaceId) + internal + view + returns (bool) + { + return super._supportsInterface(_interfaceId) || + _interfaceId == InterfaceId_ERC721Enumerable || _interfaceId == InterfaceId_ERC721Metadata; + } + + /** + * @dev Gets the token name + * @return string representing the token name + */ + function name() external view returns (string) { + return name_; + } + + /** + * @dev Gets the token symbol + * @return string representing the token symbol + */ + function symbol() external view returns (string) { + return symbol_; + } + + /** + * @dev Returns an URI for a given token ID + * Throws if the token ID does not exist. May return an empty string. + * @param _tokenId uint256 ID of the token to query + */ + function tokenURI(uint256 _tokenId) public view returns (string) { + require(exists(_tokenId)); + return tokenURIs[_tokenId]; + } + + /** + * @dev Gets the token ID at a given index of the tokens list of the requested owner + * @param _owner address owning the tokens list to be accessed + * @param _index uint256 representing the index to be accessed of the requested tokens list + * @return uint256 token ID at the given index of the tokens list owned by the requested address + */ + function tokenOfOwnerByIndex( + address _owner, + uint256 _index + ) + public + view + returns (uint256) + { + require(_index < balanceOf(_owner)); + return ownedTokens[_owner][_index]; + } + + /** + * @dev Gets the total amount of tokens stored by the contract + * @return uint256 representing the total amount of tokens + */ + function totalSupply() public view returns (uint256) { + return allTokens.length; + } + + /** + * @dev Gets the token ID at a given index of all the tokens in this contract + * Reverts if the index is greater or equal to the total number of tokens + * @param _index uint256 representing the index to be accessed of the tokens list + * @return uint256 token ID at the given index of the tokens list + */ + function tokenByIndex(uint256 _index) public view returns (uint256) { + require(_index < totalSupply()); + return allTokens[_index]; + } + + /** + * @dev Internal function to set the token URI for a given token + * Reverts if the token ID does not exist + * @param _tokenId uint256 ID of the token to set its URI + * @param _uri string URI to assign + */ + function _setTokenURI(uint256 _tokenId, string _uri) internal { + require(exists(_tokenId)); + tokenURIs[_tokenId] = _uri; + } + + /** + * @dev Internal function to add a token ID to the list of a given address + * @param _to address representing the new owner of the given token ID + * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function addTokenTo(address _to, uint256 _tokenId) internal { + super.addTokenTo(_to, _tokenId); + uint256 length = ownedTokens[_to].length; + ownedTokens[_to].push(_tokenId); + ownedTokensIndex[_tokenId] = length; + } + + /** + * @dev Internal function to remove a token ID from the list of a given address + * @param _from address representing the previous owner of the given token ID + * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function removeTokenFrom(address _from, uint256 _tokenId) internal { + super.removeTokenFrom(_from, _tokenId); + + uint256 tokenIndex = ownedTokensIndex[_tokenId]; + uint256 lastTokenIndex = ownedTokens[_from].length.sub(1); + uint256 lastToken = ownedTokens[_from][lastTokenIndex]; + + ownedTokens[_from][tokenIndex] = lastToken; + ownedTokens[_from][lastTokenIndex] = 0; + // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to + // be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping + // the lastToken to the first position, and then dropping the element placed in the last position of the list + + ownedTokens[_from].length--; + ownedTokensIndex[_tokenId] = 0; + ownedTokensIndex[lastToken] = tokenIndex; + } + + /** + * @dev Internal function to mint a new token + * Reverts if the given token ID already exists + * @param _to address the beneficiary that will own the minted token + * @param _tokenId uint256 ID of the token to be minted by the msg.sender + */ + function _mint(address _to, uint256 _tokenId) internal { + super._mint(_to, _tokenId); + + allTokensIndex[_tokenId] = allTokens.length; + allTokens.push(_tokenId); + } + + /** + * @dev Internal function to burn a specific token + * Reverts if the token does not exist + * @param _owner owner of the token to burn + * @param _tokenId uint256 ID of the token being burned by the msg.sender + */ + function _burn(address _owner, uint256 _tokenId) internal { + super._burn(_owner, _tokenId); + + // Clear metadata (if any) + if (bytes(tokenURIs[_tokenId]).length != 0) { + delete tokenURIs[_tokenId]; + } + + // Reorg all tokens array + uint256 tokenIndex = allTokensIndex[_tokenId]; + uint256 lastTokenIndex = allTokens.length.sub(1); + uint256 lastToken = allTokens[lastTokenIndex]; + + allTokens[tokenIndex] = lastToken; + allTokens[lastTokenIndex] = 0; + + allTokens.length--; + allTokensIndex[_tokenId] = 0; + allTokensIndex[lastToken] = tokenIndex; + } + +} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol index abd04219d..b810d9f31 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol @@ -1,119 +1,650 @@ pragma solidity ^0.4.24; -import "../ERC721Token/ERC721Token.sol"; +import "./IYesComplianceToken.sol"; /** - * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific - * compliance-related queries with YES. (attestations) + * draft implementation of YES compliance token * - * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful - * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to - * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. - * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. + * NOTE: i have done relatively few gas optimization tweaks (beyond using the sturctures necessary to avoid any + * linear time procedures). + * in some cases i am using a call structure which replicates some checks. this is for code clarity/security - + * i marked a few obvious ones which could be optimized for gas, but :meh: * - * the financial systems these users authenticate against have a different set of API requirements. they need - * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic - * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. - * - * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) - * - * notes: - * - no address can be associated with more than one identity (though addresses may have more than token). issuance - * in this circumstance will fail - * - one person or business = one entity - * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will - * - two token types: control & non-control. both carry compliance proof - * - control tokens let their holders mint and burn (within the same entity) - * - non-control tokens are solely for compliance queries - * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to - * redistribute a fleet of coins - * - all country codes should be via ISO-3166-1 - * - * any (non-view) methods not explicitly marked idempotent are not idempotent. + * todo static owner should follow owner token? remove static owner? :security: :should: + * @author Tyson Malchow */ -contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { +contract YesComplianceToken is YesComplianceTokenV1 { - uint256 public constant OWNER_ENTITY_ID = 1; + uint64 private constant MAX_TOKENS_PER_ENTITY = 10240; // completely arbitrary limit + uint64 private constant MAX_ENTITIES = 2**32-1; // bc using 32 bit index tracking + uint64 private constant MAX_VALIDATORS_PER_MARK = 2**32-1; // bc using 32 bit index tracking + uint64 private constant TOTAL_YES_MARKS = 255; // bc 'uint8 yes' - uint8 public constant YESMARK_OWNER = 128; - uint8 public constant YESMARK_VALIDATOR = 129; + // todo could shorten the entity IDs to anything 160+ to make this cheaper? - /* - todo events: entity updated, destroyed, ???? - Finalized - Attested + /** @notice a single YES attestation */ + struct YesMark { - */ + /** @notice ISO-3166-1 country codes */ + uint16 countryCode; - /** - * @notice query api: returns true if the specified address has the given country/yes attestation. this - * is the primary method partners will use to query the active qualifications of any particular - * address. - */ - function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; + /** @notice the possibly-country-speicifc YES being marked. */ + uint8 yes; + + // 8 bits more space in this slot.. could upgrade yes to uint16? + + /** @notice the index of this mark in EntityRecord.yesMarks */ + uint32 yesMarkIdx; + + /** a list of the validator entities which have attested to this mark */ + uint256[] validatorEntityIds; - /** @notice same as isYes except as an imperative */ - function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; + /** @notice index of each validator entity ID in validatorEntityIds */ + mapping(uint256 => uint32) validatorEntityIdIdx; + + // uint8 entityListIdx; + } /** - * @notice retrieve all YES marks for an address in a particular country - * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them - * @param _address the validator ID to consider, or 0 for any of them - * @param _countryCode the ISO-3166-1 country code - * @return (non-duplicate) array of YES marks present + * tracks the state for a single recognized entity */ - function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); + struct EntityRecord { + + /** true marking this entity ID has been encountered */ + bool init; + + /** when true, this entity is effectively useless */ + bool locked; + + // 30 bits more space in this slot + + /** position of the entityId in allEntityIds */ + uint32 entityIdIdx; + + /** used for creating reliable token IDs, monotonically increasing */ + uint64 tokenIdCounter; + + /** indexed YES mark lookups */ + mapping(bytes4 => YesMark) yesMarkByKey; + + /** raw collection of all marks keys */ + bytes4[] yesMarkKeys; - // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); + /** all tokens associated with this identity */ + uint256[] tokenIds; + + // trellis/tower connection ? + // civic connection ? + // erc725/735 connection ? + } /** - * @notice create new tokens. fail if _to already - * belongs to a different entity and caller is not validator - * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. - * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller - * @return the newly created token ID + * @notice all fields we want to add per-token. + * + * there may never be more than just control flag, in which case it may make sense to collapse this + * to just a mapping(uint256 => bool) ? */ - function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); + struct TokenRecord { + + /** position of the tokenId in EntityRecord.tokenIds */ + uint32 tokenIdIdx; + + /** true if this token has administrative superpowers (aka is _not_ limited) */ + bool control; + + /** true if this token cannot move */ + bool finalized; + + // 30 bits more in this slot - /** @notice shortcut to mint() + setYes() in one call, for a single country */ - function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); + // limitations: in/out? + } - /** @notice destroys a specific token */ - function burn(uint256 _tokenId) external; + address public ownerAddress; - /** @notice destroys the entire entity and all tokens */ - function burnEntity(uint256 _entityId) external; + mapping(uint256 => TokenRecord) public tokenRecordById; + mapping(uint256 => EntityRecord) public entityRecordById; + mapping(uint256 => uint256) public entityIdByTokenId; + + /** for entity enumeration. maximum of 2^256-1 total entities (i think we'll be ok) */ + uint256[] entityIds; + + constructor() public { + /* this space intentionally left blank */ + } /** - * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark - * was already set by this validator + * constructor alternative: first-time initialization the contract/token (required because of upgradeability) */ - function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + function initialize(string _name, string _symbol) { + // require(super._symbol.length == 0 || _symbol == super._symbol); // cannot change symbol after first init bc that could fuck shit up + _upgradeable_initialize(); // basically for security + super.initialize(_name, _symbol); // init token info + + // grant the owner token + mint_I(ownerAddress, OWNER_ENTITY_ID, true); + + // ecosystem owner gets both owner and validator marks (self-attested) + setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_OWNER); + setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_VALIDATOR); + } /** - * @notice removes a attestation(s) from a specific validator for an entity. idempotent + * executed in lieu of a constructor in a delegated context */ - function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; + function _upgradeable_initialize() public { + super._upgradeable_initialize(); // provides require(msg.sender == _upgradeable_delegate_owner); + + // some things are still tied to the owner (instead of the yesmark_owner :notsureif:) + ownerAddress = msg.sender; + } + + // YesComplianceTokenV1 Interface Methods -------------------------------------------------------------------------- + + function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) { + return isYes_I(_validatorEntityId, _address, _countryCode, _yes); + } + + function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view { + require(isYes_I(_validatorEntityId, _address, _countryCode, _yes)); + } + + function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] memory) { + if(balanceOf(_address) == 0) + return new uint8[](0); + + uint256 entityId = entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; + EntityRecord storage e = entityRecordById[entityId]; + uint256 j = 0; + uint256 i; + + // locked always bails + if(e.locked) + return new uint8[](0); + + uint8[] memory r = new uint8[](e.yesMarkKeys.length); + + for(i = 0; i < e.yesMarkKeys.length; i++) { + YesMark storage m = e.yesMarkByKey[e.yesMarkKeys[i]]; + + // filter country code + if(m.countryCode != _countryCode) + continue; + + // filter explicit validator entity + if(_validatorEntityId > 0 + && m.validatorEntityIdIdx[_validatorEntityId] == 0 + && (m.validatorEntityIds.length == 0 || m.validatorEntityIds[0] == _validatorEntityId)) + continue; + + // matched, chyess + r[j++] = m.yes; + } + + // reduce array length + assembly { mstore(r, j) } + + return r; + } + + function mint(address _to, uint256 _entityId, bool _control) external returns (uint256) /* internally protected */{ + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + + // make sure caller has a control token, at the least + require(tokenRecordById[callerTokenId].control, 'control token required'); + + // determine/validate the entity being minted for + uint256 realEntityId; + if(_entityId == 0 || _entityId == callerEntityId) { + // unspecified entity, or caller entity, can do! + realEntityId = callerEntityId; + + } else { + // otherwise make sure caller is a VALIDATOR, else fail + require(senderIsControlValidator(), 'illegal entity id'); // some duplicate checks/lookups, gas leak + realEntityId = _entityId; + } + + return mint_I(_to, realEntityId, _control); + } + + function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256) /* internally protected */ { + // lazy warning: this is a 90% copy/paste job from the mint directly above this + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + + // make sure caller has a control token, at the least + require(tokenRecordById[callerTokenId].control, 'control token required'); + + // determine/validate the entity being minted for + uint256 realEntityId; + if(_entityId == 0 || _entityId == callerEntityId) { + // unspecified entity, or caller entity, can do! + realEntityId = callerEntityId; + + } else { + // otherwise make sure caller is a VALIDATOR, else fail + require(senderIsControlValidator()); // some duplicate checks/lookups, gas leak + realEntityId = _entityId; + } + + // mint the coin + uint256 tokenId = mint_I(_to, realEntityId, _control); + + // now set the attestations + require(_yes.length <= TOTAL_YES_MARKS); // safety + for(uint256 i = 0; i<_yes.length; i++) { + setYes_I(_entityId, _countryCode, _yes[i]); + } + + return tokenId; + } + + function getEntityId(address _address) external view returns (uint256) { + return entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; + } + + function burn(uint256 _tokenId) external permission_control_tokenId(_tokenId) { + uint256 entityId = entityIdByTokenId[_tokenId]; + + EntityRecord storage e = entity(entityId); + TokenRecord storage t = tokenRecordById[_tokenId]; + + // remove token from entity + e.tokenIds[t.tokenIdIdx] = e.tokenIds[e.tokenIds.length - 1]; + e.tokenIds.length--; + + // update tracked index (of swapped, if present) + if(e.tokenIds.length > t.tokenIdIdx) + tokenRecordById[e.tokenIds[t.tokenIdIdx]].tokenIdIdx = t.tokenIdIdx; + + // remove token record + delete tokenRecordById[_tokenId]; + + // burn the actual token + super._burn(tokenOwner[_tokenId], _tokenId); + } + + function burnEntity(uint256 _entityId) external permission_control_entityId(_entityId) { // self-burn allowed + EntityRecord storage e = entity(_entityId); + + // burn all the tokens + for(uint256 i = 0; i < e.tokenIds.length; i++) { + uint256 tokenId = e.tokenIds[i]; + super._burn(tokenOwner[tokenId], tokenId); + } + + // clear all the marks + clearYes_I(_entityId); + + // clear out entity record + e.init = false; + e.locked = false; + e.entityIdIdx = 0; + e.tokenIdCounter = 0; + + assert(e.yesMarkKeys.length == 0); + assert(e.tokenIds.length == 0); + } + + function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { + setYes_I(_entityId, _countryCode, _yes); + } + + function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { + require(_yes > 0); + require(_yes != 128); + + // special check against reserved country code 0 + if(_countryCode == 0) + require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); // this is duplicating some things, gas leak + + EntityRecord storage e = entity(_entityId); + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + bytes4 key = yesKey(_countryCode, _yes); + + YesMark storage mark = e.yesMarkByKey[key]; + if(mark.yes == 0) + return; // not set by anyone, bail happily + + if(mark.validatorEntityIdIdx[callerEntityId] == 0 && + (mark.validatorEntityIds.length == 0 || mark.validatorEntityIds[0] != callerEntityId)) { + // set, but not by this validator, bail happily + return; + } + + clearYes_I(mark, e, callerEntityId); + } + + function clearYes(uint256 _entityId, uint16 _countryCode) external permission_validator { + // special check against 129 validator mark + if(_countryCode == 0) + require(senderIsEcosystemControl(), 'not authorized as ecosystem control (129)'); // this is duplicating some things, gas leak + + EntityRecord storage e = entity(_entityId); + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + uint256 i; + + for(i =0; i idx) + mark.validatorEntityIdIdx[mark.validatorEntityIds[idx]] = idx; + + // check if the entire mark needs deleting + if(mark.validatorEntityIds.length == 0) { + // yes, it does. swap/delete + idx = mark.yesMarkIdx; + e.yesMarkKeys[idx] = e.yesMarkKeys[e.yesMarkKeys.length - 1]; + e.yesMarkKeys.length--; + + // remap + if(e.yesMarkKeys.length > idx) + e.yesMarkByKey[e.yesMarkKeys[idx]].yesMarkIdx = idx; + + // delete mark + mark.countryCode = 0; + mark.yes = 0; + mark.yesMarkIdx = 0; + // assert(mark.validatorEntityIds.length == 0); + + return true; + } + + return false; + } + + function clearYes_I(uint256 _entityId) internal { + require(_entityId != OWNER_ENTITY_ID); + + EntityRecord storage e = entity(_entityId); + + // only ecosystem control can touch validators + if(!senderIsEcosystemControl()) + require(e.yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes == 0); + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + uint256 i; + + for(i =0; i 0 + || m.validatorEntityIds.length > 0 && m.validatorEntityIds[0] == _validatorEntityId; + } + + function setYes_I(uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { + require(_yes > 0); + require(_yes != 128); + + // special check against 129 validator mark + if(_yes == 129) + require(senderIsEcosystemControl()); // this is duplicating some checks, gas leak + + uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 callerEntityId = entityIdByTokenId[callerTokenId]; + + setYes_I(callerEntityId, _entityId, _countryCode, _yes); + } + + function setYes_I(uint256 _validatorEntityId, uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { + // assert(_yes > 0); + EntityRecord storage targetEntity = entity(_entityId); + + // locate existing mark + bytes4 key = yesKey(_countryCode, _yes); + YesMark storage mark = targetEntity.yesMarkByKey[key]; + + if(mark.yes == 0) { + require(targetEntity.yesMarkKeys.length < TOTAL_YES_MARKS); + + // new mark on the entity + mark.countryCode = _countryCode; + mark.yes = _yes; + mark.yesMarkIdx = uint32(targetEntity.yesMarkKeys.length); + targetEntity.yesMarkKeys.push(key); + + } else if(mark.validatorEntityIdIdx[_validatorEntityId] > 0 || + (mark.validatorEntityIds.length > 0 && mark.validatorEntityIds[0] == _validatorEntityId)) { + + // existing mark and the caller is already on it + /* + i'm inclined to make it do nothing in this case (instead of failing) since i'm not at this point positive how best + to distinguish error types to a caller, which would be required for a caller to know wtf to do in this case + (otherwise they need to query blockchain again) + (but that costs gas... :notsureif:) + */ + return; + } + + require(mark.validatorEntityIds.length < MAX_VALIDATORS_PER_MARK); + + // add this validator to the mark + mark.validatorEntityIdIdx[_validatorEntityId] = uint32(mark.validatorEntityIds.length); + mark.validatorEntityIds.push(_validatorEntityId); + } + + /** non-permissed internal minting impl */ + function mint_I(address _to, uint256 _entityId, bool _control) internal returns (uint256) { + EntityRecord storage e = entity(_entityId); + require(e.tokenIds.length < MAX_TOKENS_PER_ENTITY, 'token limit reached'); + require(e.tokenIdCounter < 2**64-1); // kind of ridiculous but whatever, safety first! + uint256 tokenId = uint256(keccak256(abi.encodePacked(_entityId, e.tokenIdCounter++))); + super._mint(_to, tokenId); + tokenRecordById[tokenId].tokenIdIdx = uint32(e.tokenIds.length); + tokenRecordById[tokenId].control = _control; + e.tokenIds.push(tokenId); + entityIdByTokenId[tokenId] = _entityId; + return tokenId; + } + + /** entity resolution (creation when needed) */ + function entity(uint256 _entityId) internal returns (EntityRecord storage) { + require(_entityId > 0); + EntityRecord storage e = entityRecordById[_entityId]; + if(e.init) return e; + require(entityIds.length < MAX_ENTITIES); + e.init = true; + e.entityIdIdx = uint32(entityIds.length); + entityIds.push(_entityId); + return e; + } + + /** override default addTokenTo for additional transaction limitations */ + function addTokenTo(address _to, uint256 _tokenId) internal { + uint256 entityId = entityIdByTokenId[_tokenId]; + + // ensure one owner cannot be associated with multiple entities + // NOTE: this breaks hotwallet integrations, at this point necessarily so + if(balanceOf(_to) > 0) { + uint256 prevEntityId = entityIdByTokenId[tokenOfOwnerByIndex(_to, 0)]; + require(prevEntityId == entityId, 'conflicting entities'); + } + + require(!tokenRecordById[_tokenId].finalized, 'token is finalized'); + + super.addTokenTo(_to, _tokenId); + } + + /** the sender is the same entity as the one specified */ + function senderIsEntity_ByEntityId(uint256 _entityId) internal view returns (bool) { + return _entityId == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; + } + + /** the sender is the same entity as the one specified, and the sender is a control for that entity */ + function senderIsControl_ByEntityId(uint256 _entityId) internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 senderEntityId = entityIdByTokenId[tokenId]; + return _entityId == senderEntityId && tokenRecordById[tokenId].control; + } + + /** the sender is a non-locked validator via control token */ + function senderIsControlValidator() internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); + uint256 senderEntityId = entityIdByTokenId[tokenId]; + EntityRecord storage e = entityRecordById[senderEntityId]; + return tokenRecordById[tokenId].control + && !e.locked + && entityRecordById[senderEntityId].yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes > 0; + } + + /** the sender is the same entity as the one tied to the token specified */ + function senderIsEntity_ByTokenId(uint256 _tokenId) internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + return entityIdByTokenId[_tokenId] == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; + } + + /** the sender is the same entity as the one tied to the token specified, and the sender is a control for that entity */ + function senderIsControl_ByTokenId(uint256 _tokenId) internal view returns (bool) { + if(balanceOf(msg.sender) == 0) + return false; + uint256 senderEntityId = entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; + return entityIdByTokenId[_tokenId] == senderEntityId && tokenRecordById[_tokenId].control; + } + + /** checks if sender is the singular ecosystem owner */ + function senderIsEcosystemControl() internal view returns (bool) { + // todo deprecate ownerAddress ?! + return msg.sender == ownerAddress || senderIsControl_ByEntityId(OWNER_ENTITY_ID); + } + + /** a key for a YES attestation mark */ + function yesKey(uint16 _countryCode, uint8 _yes) internal pure returns(bytes4) { + return bytes4(keccak256(abi.encodePacked(_countryCode, _yes))); + } - /** @notice removes all attestations in a given country for a particular entity. idempotent */ - function clearYes(uint256 _entityId, uint16 _countryCode) external; + // PERMISSIONS MODIFIERS ---------------------------------------------------------------- - /** @notice removes all attestations for a particular entity. idempotent */ - function clearYes(uint256 _entityId) external; + modifier permission_validator { + require(senderIsControlValidator(), 'not authorized as validator'); + _; + } - /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ - function setLocked(uint256 _entityId, bool _lock) external; + modifier permission_super { + require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); + _; + } - /** @notice checks whether or not a particular entity is locked */ - function isLocked(uint256 _entityId) external view returns(bool); +// modifier permission_access_entityId(uint256 _entityId) { +// require(senderIsEcosystemControl() || senderIsEntity_ByEntityId(_entityId)); +// _; +// } - /** @notice returns true if the specified token has been finalized (cannot be moved) */ - function isFinalized(uint256 _tokenId) external view returns(bool); + modifier permission_control_entityId(uint256 _entityId) { + require(senderIsEcosystemControl() || senderIsControl_ByEntityId(_entityId), 'not authorized entity controller'); + _; + } - /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ - function finalize(uint256 _tokenId) external; + modifier permission_access_tokenId(uint256 _tokenId) { + require(senderIsEcosystemControl() || senderIsEntity_ByTokenId(_tokenId)); + _; + } - /** @return the entity ID associated with an address (or fail if there is not one) */ - function getEntityId(address _address) external view returns(uint256); + modifier permission_control_tokenId(uint256 _tokenId) { + require(senderIsEcosystemControl() || senderIsControl_ByTokenId(_tokenId), 'not authorized token controller'); + _; + } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts new file mode 100644 index 000000000..41603e3c2 --- /dev/null +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -0,0 +1,191 @@ +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { RevertReason, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; + +import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; +import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { DummyYesComplianceContract } from '../../generated-wrappers/forwarder'; +import { WETH9Contract } from '../../generated-wrappers/weth9'; +import { artifacts } from '../../src/artifacts'; +import { + expectContractCreationFailedAsync, + expectTransactionFailedAsync, + sendTransactionResult, +} from '../utils/assertions'; +import { chaiSetup } from '../utils/chai_setup'; +import { constants } from '../utils/constants'; +import { ERC20Wrapper } from '../utils/erc20_wrapper'; +import { ERC721Wrapper } from '../utils/erc721_wrapper'; +import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { ForwarderWrapper } from '../utils/forwarder_wrapper'; +import { OrderFactory } from '../utils/order_factory'; +import { ContractName, ERC20BalancesByOwner } from '../utils/types'; +import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +const DECIMALS_DEFAULT = 18; +const MAX_WETH_FILL_PERCENTAGE = 95; + +describe(ContractName.Forwarder, () => { + let makerAddress: string; + let owner: string; + let takerAddress: string; + let feeRecipientAddress: string; + let otherAddress: string; + let defaultMakerAssetAddress: string; + let zrxAssetData: string; + let wethAssetData: string; + + let weth: DummyERC20TokenContract; + let zrxToken: DummyERC20TokenContract; + let erc20TokenA: DummyERC20TokenContract; + let erc721Token: DummyERC721TokenContract; + let forwarderContract: ForwarderContract; + let wethContract: WETH9Contract; + let forwarderWrapper: ForwarderWrapper; + let exchangeWrapper: ExchangeWrapper; + + let orderWithoutFee: SignedOrder; + let orderWithFee: SignedOrder; + let feeOrder: SignedOrder; + let orderFactory: OrderFactory; + let erc20Wrapper: ERC20Wrapper; + let erc20Balances: ERC20BalancesByOwner; + let tx: TransactionReceiptWithDecodedLogs; + + let erc721MakerAssetIds: BigNumber[]; + let takerEthBalanceBefore: BigNumber; + let feePercentage: BigNumber; + let gasPrice: BigNumber; + + before(async () => { + await blockchainLifecycle.startAsync(); + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts); + + const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 }); + const transaction = await web3Wrapper.getTransactionByHashAsync(txHash); + gasPrice = new BigNumber(transaction.gasPrice); + + const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + + const numDummyErc20ToDeploy = 3; + [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + const erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + + [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); + const erc721Proxy = await erc721Wrapper.deployProxyAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const erc721Balances = await erc721Wrapper.getBalancesAsync(); + erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; + + wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); + weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); + erc20Wrapper.addDummyTokenContract(weth); + + wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address); + zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); + + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { + from: owner, + }); + await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { + from: owner, + }); + + defaultMakerAssetAddress = erc20TokenA.address; + const defaultTakerAssetAddress = wethContract.address; + const defaultOrderParams = { + exchangeAddress: exchangeInstance.address, + makerAddress, + feeRecipientAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT), + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT), + }; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + orderFactory = new OrderFactory(privateKey, defaultOrderParams); + + const forwarderInstance = await ForwarderContract.deployFrom0xArtifactAsync( + artifacts.Forwarder, + provider, + txDefaults, + exchangeInstance.address, + zrxAssetData, + wethAssetData, + ); + forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider); + forwarderWrapper = new ForwarderWrapper(forwarderContract, provider); + const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18); + await web3Wrapper.awaitTransactionSuccessAsync( + await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount), + ); + erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address); + }); + after(async () => { + await blockchainLifecycle.revertAsync(); + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + erc20Balances = await erc20Wrapper.getBalancesAsync(); + takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress); + orderWithoutFee = await orderFactory.newSignedOrderAsync(); + feeOrder = await orderFactory.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + }); + orderWithFee = await orderFactory.newSignedOrderAsync({ + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + }); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + + describe('constructor', () => { + it('should revert if assetProxy is unregistered', async () => { + const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + return expectContractCreationFailedAsync( + (ForwarderContract.deployFrom0xArtifactAsync( + artifacts.Forwarder, + provider, + txDefaults, + exchangeInstance.address, + zrxAssetData, + wethAssetData, + ) as any) as sendTransactionResult, + RevertReason.UnregisteredAssetProxy, + ); + }); + }); +}); +// tslint:disable:max-file-line-count +// tslint:enable:no-unnecessary-type-assertion -- cgit v1.2.3 From 88595718c3f78d3facbd4ac67ba8328ce9b2bc8a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 15:37:11 -0800 Subject: Yes Compliance Token --- packages/contracts/contracts/tokens/README.md | 2 ++ .../YesComplianceToken/IYesComplianceToken.sol | 3 +- .../WyreERC721Token/ERC721BasicToken.sol | 32 ++++++++-------------- .../WyreERC721Token/ERC721Token.sol | 15 ++-------- .../YesComplianceToken/YesComplianceToken.sol | 2 -- .../test/extensions/compliant_forwarder.ts | 4 ++- 6 files changed, 21 insertions(+), 37 deletions(-) create mode 100644 packages/contracts/contracts/tokens/README.md (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/README.md b/packages/contracts/contracts/tokens/README.md new file mode 100644 index 000000000..b54f0e046 --- /dev/null +++ b/packages/contracts/contracts/tokens/README.md @@ -0,0 +1,2 @@ +Contracts from https://github.com/sendwyre/yes-compliance-token +Modified to compile in our codebase. diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol index 1573c6bac..a1c9b9671 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol @@ -1,5 +1,6 @@ pragma solidity ^0.4.24; +import "./WyreERC721Token/ERC721Token.sol"; /** * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific @@ -30,7 +31,7 @@ pragma solidity ^0.4.24; * * any (non-view) methods not explicitly marked idempotent are not idempotent. */ -contract YesComplianceTokenV1 /*is ERC721Token*/ /*, ERC165 :should: */ { +contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { uint256 public constant OWNER_ENTITY_ID = 1; diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol index 1d3fa37b8..788e31580 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol @@ -1,17 +1,15 @@ pragma solidity ^0.4.21; import "./ERC721Basic.sol"; -import "./ERC721Receiver.sol"; -import "../../math/SafeMath.sol"; -import "../../AddressUtils.sol"; -import "../../introspection/ERC165Support.sol"; +import "../../ERC721Token/IERC721Receiver.sol"; +import "../../../utils/SafeMath/SafeMath.sol"; /** * @title ERC721 Non-Fungible Token Standard basic implementation * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ -contract ERC721BasicToken is ERC165Support, ERC721Basic { +contract ERC721BasicToken is ERC721Basic, SafeMath { bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; /* @@ -33,9 +31,6 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { * bytes4(keccak256('exists(uint256)')) */ - using SafeMath for uint256; - using AddressUtils for address; - // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` bytes4 private constant ERC721_RECEIVED = 0x150b7a02; @@ -70,15 +65,6 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { _; } - function _supportsInterface(bytes4 _interfaceId) - internal - view - returns (bool) - { - return super._supportsInterface(_interfaceId) || - _interfaceId == InterfaceId_ERC721 || _interfaceId == InterfaceId_ERC721Exists; - } - /** * @dev Gets the balance of the specified address * @param _owner address to query the balance of @@ -311,7 +297,7 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { function addTokenTo(address _to, uint256 _tokenId) internal { require(tokenOwner[_tokenId] == address(0)); tokenOwner[_tokenId] = _to; - ownedTokensCount[_to] = ownedTokensCount[_to].add(1); + ownedTokensCount[_to] = safeAdd(ownedTokensCount[_to], 1); } /** @@ -321,7 +307,7 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { */ function removeTokenFrom(address _from, uint256 _tokenId) internal { require(ownerOf(_tokenId) == _from); - ownedTokensCount[_from] = ownedTokensCount[_from].sub(1); + ownedTokensCount[_from] = safeSub(ownedTokensCount[_from], 1); tokenOwner[_tokenId] = address(0); } @@ -343,10 +329,14 @@ contract ERC721BasicToken is ERC165Support, ERC721Basic { internal returns (bool) { - if (!_to.isContract()) { + uint256 receiverCodeSize; + assembly { + receiverCodeSize := extcodesize(_to) + } + if (receiverCodeSize == 0) { return true; } - bytes4 retval = ERC721Receiver(_to).onERC721Received( + bytes4 retval = IERC721Receiver(_to).onERC721Received( msg.sender, _from, _tokenId, _data); return (retval == ERC721_RECEIVED); } diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol index c7acee6df..832ff3784 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol @@ -52,20 +52,11 @@ contract ERC721Token is ERC721BasicToken, ERC721 { /** * @dev Constructor function */ - function initialize(string _name, string _symbol) public isInitializer("ERC721Token", "1.9.0") { + function initialize(string _name, string _symbol) public { name_ = _name; symbol_ = _symbol; } - function _supportsInterface(bytes4 _interfaceId) - internal - view - returns (bool) - { - return super._supportsInterface(_interfaceId) || - _interfaceId == InterfaceId_ERC721Enumerable || _interfaceId == InterfaceId_ERC721Metadata; - } - /** * @dev Gets the token name * @return string representing the token name @@ -161,7 +152,7 @@ contract ERC721Token is ERC721BasicToken, ERC721 { super.removeTokenFrom(_from, _tokenId); uint256 tokenIndex = ownedTokensIndex[_tokenId]; - uint256 lastTokenIndex = ownedTokens[_from].length.sub(1); + uint256 lastTokenIndex = safeSub(ownedTokens[_from].length, 1); uint256 lastToken = ownedTokens[_from][lastTokenIndex]; ownedTokens[_from][tokenIndex] = lastToken; @@ -204,7 +195,7 @@ contract ERC721Token is ERC721BasicToken, ERC721 { // Reorg all tokens array uint256 tokenIndex = allTokensIndex[_tokenId]; - uint256 lastTokenIndex = allTokens.length.sub(1); + uint256 lastTokenIndex = safeSub(allTokens.length, 1); uint256 lastToken = allTokens[lastTokenIndex]; allTokens[tokenIndex] = lastToken; diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol index b810d9f31..65ea99d0c 100644 --- a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol +++ b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol @@ -118,7 +118,6 @@ contract YesComplianceToken is YesComplianceTokenV1 { */ function initialize(string _name, string _symbol) { // require(super._symbol.length == 0 || _symbol == super._symbol); // cannot change symbol after first init bc that could fuck shit up - _upgradeable_initialize(); // basically for security super.initialize(_name, _symbol); // init token info // grant the owner token @@ -133,7 +132,6 @@ contract YesComplianceToken is YesComplianceTokenV1 { * executed in lieu of a constructor in a delegated context */ function _upgradeable_initialize() public { - super._upgradeable_initialize(); // provides require(msg.sender == _upgradeable_delegate_owner); // some things are still tied to the owner (instead of the yesmark_owner :notsureif:) ownerAddress = msg.sender; diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 41603e3c2..d26bbd8ec 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -1,3 +1,4 @@ +/* import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; import { RevertReason, SignedOrder } from '@0x/types'; @@ -8,7 +9,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { DummyYesComplianceContract } from '../../generated-wrappers/forwarder'; + import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; import { @@ -189,3 +190,4 @@ describe(ContractName.Forwarder, () => { }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion +*/ \ No newline at end of file -- cgit v1.2.3 From 0e0e05e0e07aca3cfbfd13b3fdc00183b7ac5a87 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 16:05:18 -0800 Subject: Compile Compliant Forwarder contract --- .../contracts/extensions/CompliantForwarder/CompliantForwarder.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 646a9f3b8..2febc5cce 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -76,7 +76,7 @@ contract CompliantForwarder { * takerAssetData * signature ------------------------------ - * Context-dependent offsets, unknown at compile time. + * Context-dependent offsets; unknown at compile time. */ // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. -- cgit v1.2.3 From c854c99f20e399bd36f86f69738913c8e5819a9f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 16:16:55 -0800 Subject: template for Compliant Forwarder tests --- .../test/extensions/compliant_forwarder.ts | 59 +++++++--------------- .../test/utils/compliant_forwarder_wrapper.ts | 0 2 files changed, 18 insertions(+), 41 deletions(-) create mode 100644 packages/contracts/test/utils/compliant_forwarder_wrapper.ts (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index d26bbd8ec..bb9493aaa 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -1,4 +1,3 @@ -/* import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; import { RevertReason, SignedOrder } from '@0x/types'; @@ -9,6 +8,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; @@ -46,8 +46,7 @@ describe(ContractName.Forwarder, () => { let weth: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; let erc20TokenA: DummyERC20TokenContract; - let erc721Token: DummyERC721TokenContract; - let forwarderContract: ForwarderContract; + let compliantForwarderContract: CompliantForwarderContract; let wethContract: WETH9Contract; let forwarderWrapper: ForwarderWrapper; let exchangeWrapper: ExchangeWrapper; @@ -85,12 +84,6 @@ describe(ContractName.Forwarder, () => { const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - [erc721Token] = await erc721Wrapper.deployDummyTokensAsync(); - const erc721Proxy = await erc721Wrapper.deployProxyAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - const erc721Balances = await erc721Wrapper.getBalancesAsync(); - erc721MakerAssetIds = erc721Balances[makerAddress][erc721Token.address]; - wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); erc20Wrapper.addDummyTokenContract(weth); @@ -105,14 +98,10 @@ describe(ContractName.Forwarder, () => { ); exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await exchangeWrapper.registerAssetProxyAsync(erc721Proxy.address, owner); await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner, }); - await erc721Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); defaultMakerAssetAddress = erc20TokenA.address; const defaultTakerAssetAddress = wethContract.address; @@ -130,21 +119,28 @@ describe(ContractName.Forwarder, () => { const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); - const forwarderInstance = await ForwarderContract.deployFrom0xArtifactAsync( - artifacts.Forwarder, + const compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( + artifacts.CompliantForwarder, provider, txDefaults, exchangeInstance.address, - zrxAssetData, - wethAssetData, + exchangeInstance.address, // @TODO CHANGE to Yes Token ); - forwarderContract = new ForwarderContract(forwarderInstance.abi, forwarderInstance.address, provider); - forwarderWrapper = new ForwarderWrapper(forwarderContract, provider); + + compliantForwarderContract = new CompliantForwarderContract( + compliantForwarderInstance.abi, + compliantForwarderInstance.address, + provider, + ); + /* + forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); + */ + const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18); await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.transfer.sendTransactionAsync(forwarderContract.address, zrxDepositAmount), + await zrxToken.transfer.sendTransactionAsync(compliantForwarderContract.address, zrxDepositAmount), ); - erc20Wrapper.addTokenOwnerAddress(forwarderInstance.address); + erc20Wrapper.addTokenOwnerAddress(compliantForwarderInstance.address); }); after(async () => { await blockchainLifecycle.revertAsync(); @@ -167,27 +163,8 @@ describe(ContractName.Forwarder, () => { }); describe('constructor', () => { - it('should revert if assetProxy is unregistered', async () => { - const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - return expectContractCreationFailedAsync( - (ForwarderContract.deployFrom0xArtifactAsync( - artifacts.Forwarder, - provider, - txDefaults, - exchangeInstance.address, - zrxAssetData, - wethAssetData, - ) as any) as sendTransactionResult, - RevertReason.UnregisteredAssetProxy, - ); - }); + it('should revert if assetProxy is unregistered', async () => {}); }); }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion -*/ \ No newline at end of file diff --git a/packages/contracts/test/utils/compliant_forwarder_wrapper.ts b/packages/contracts/test/utils/compliant_forwarder_wrapper.ts new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3 From b4aca370defb9dfe6c01b60d1b522d4a7b731f43 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Wed, 28 Nov 2018 18:09:50 -0800 Subject: Writing tests for Compliant Forwarder --- .../test/extensions/compliant_forwarder.ts | 172 +++++++++++++++------ 1 file changed, 125 insertions(+), 47 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index bb9493aaa..9100c32f8 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -9,6 +9,7 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; +import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; @@ -24,7 +25,9 @@ import { ERC721Wrapper } from '../utils/erc721_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; import { ForwarderWrapper } from '../utils/forwarder_wrapper'; import { OrderFactory } from '../utils/order_factory'; -import { ContractName, ERC20BalancesByOwner } from '../utils/types'; +import { orderUtils } from '../utils/order_utils'; +import { TransactionFactory } from '../utils/transaction_factory'; +import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; chaiSetup.configure(); @@ -33,12 +36,12 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; const MAX_WETH_FILL_PERCENTAGE = 95; -describe(ContractName.Forwarder, () => { - let makerAddress: string; +describe.only(ContractName.Forwarder, () => { + let compliantMakerAddress: string; let owner: string; - let takerAddress: string; + let compliantTakerAddress: string; let feeRecipientAddress: string; - let otherAddress: string; + let noncompliantAddress: string; let defaultMakerAssetAddress: string; let zrxAssetData: string; let wethAssetData: string; @@ -46,6 +49,7 @@ describe(ContractName.Forwarder, () => { let weth: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; let erc20TokenA: DummyERC20TokenContract; + let yesComplianceToken: YesComplianceTokenContract; let compliantForwarderContract: CompliantForwarderContract; let wethContract: WETH9Contract; let forwarderWrapper: ForwarderWrapper; @@ -59,37 +63,73 @@ describe(ContractName.Forwarder, () => { let erc20Balances: ERC20BalancesByOwner; let tx: TransactionReceiptWithDecodedLogs; + let makerAssetAddress: string; + let takerAssetAddress: string; + let erc721MakerAssetIds: BigNumber[]; let takerEthBalanceBefore: BigNumber; let feePercentage: BigNumber; - let gasPrice: BigNumber; + + let compliantSignedOrder: SignedOrder; + let compliantSignedFillOrderTx: SignedTransaction; + let noncompliantSignedFillOrderTx: SignedTransaction; + + const compliantMakerCountryCode = new BigNumber(519); + const compliantMakerYesMark = new BigNumber(1); + const compliantMakerEntityId = new BigNumber(2); + let compliantMakerYesTokenId; + + const compliantTakerCountryCode = new BigNumber(519); + const compliantTakerYesMark = new BigNumber(1); + const compliantTakerEntityId = new BigNumber(2); + let compliantTakerYesTokenId; + + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); + const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); + const salt = new BigNumber(0); + + let compliantForwarderInstance: CompliantForwarderContract; before(async () => { + // Create accounts await blockchainLifecycle.startAsync(); const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, makerAddress, takerAddress, feeRecipientAddress, otherAddress] = accounts); - - const txHash = await web3Wrapper.sendTransactionAsync({ from: accounts[0], to: accounts[0], value: 0 }); - const transaction = await web3Wrapper.getTransactionByHashAsync(txHash); - gasPrice = new BigNumber(transaction.gasPrice); - + const usedAddresses = ([owner, compliantMakerAddress, compliantTakerAddress, feeRecipientAddress, noncompliantAddress] = accounts); + // Create wrappers const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - + // Deploy ERC20 tokens const numDummyErc20ToDeploy = 3; - [erc20TokenA, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); + makerAssetAddress = erc20TokenA.address; + takerAssetAddress = erc20TokenB.address; + // Deploy Yes Token + const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( + artifacts.YesComplianceToken, + provider, + txDefaults, + ); + compliantForwarderContract = new CompliantForwarderContract( + yesTokenInstance.abi, + yesTokenInstance.address, + provider, + ); + // Create proxies const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - + // Deploy tokens & set asset data wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); erc20Wrapper.addDummyTokenContract(weth); - wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address); zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + // Deploy Exchange congtract const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, @@ -97,36 +137,35 @@ describe(ContractName.Forwarder, () => { zrxAssetData, ); exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); + // Register proxies await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner, }); - + // Default order parameters defaultMakerAssetAddress = erc20TokenA.address; const defaultTakerAssetAddress = wethContract.address; const defaultOrderParams = { exchangeAddress: exchangeInstance.address, - makerAddress, + compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(200), DECIMALS_DEFAULT), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(10), DECIMALS_DEFAULT), + makerAssetAmount: makerAssetAmount, + takerAssetAmount: takerAssetAmount, makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); - - const compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( + // Deploy Compliant Forwarder + compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( artifacts.CompliantForwarder, provider, txDefaults, exchangeInstance.address, - exchangeInstance.address, // @TODO CHANGE to Yes Token + yesTokenInstance.address, ); - compliantForwarderContract = new CompliantForwarderContract( compliantForwarderInstance.abi, compliantForwarderInstance.address, @@ -135,35 +174,74 @@ describe(ContractName.Forwarder, () => { /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ - - const zrxDepositAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(10000), 18); - await web3Wrapper.awaitTransactionSuccessAsync( - await zrxToken.transfer.sendTransactionAsync(compliantForwarderContract.address, zrxDepositAmount), + // Verify Maker / Taker + const addressesCanControlTheirToken = true; + compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark]); + compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark]); + // Create Valid/Invalid orders + const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; + const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); + compliantSignedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderContract.address, + }); + const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(compliantSignedOrder); + const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + compliantSignedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, ); - erc20Wrapper.addTokenOwnerAddress(compliantForwarderInstance.address); - }); - after(async () => { - await blockchainLifecycle.revertAsync(); + compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction(compliantSignedOrderWithoutExchangeAddressData); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); - erc20Balances = await erc20Wrapper.getBalancesAsync(); - takerEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(takerAddress); - orderWithoutFee = await orderFactory.newSignedOrderAsync(); - feeOrder = await orderFactory.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(zrxToken.address), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); - orderWithFee = await orderFactory.newSignedOrderAsync({ - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - }); }); afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('constructor', () => { - it('should revert if assetProxy is unregistered', async () => {}); + describe.only('fillOrder', () => { + let takerAssetFillAmount: BigNumber; + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + + // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract + // @TODO: Should fail if the + + it.only('should transfer the correct amounts when maker and taker are verified', async () => { + await compliantForwarderInstance.fillOrder.sendTransactionAsync(compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][makerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][makerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][takerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][takerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][takerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][takerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][makerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][makerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); }); }); // tslint:disable:max-file-line-count -- cgit v1.2.3 From 003075a8a594060817f2ddcb2124580e27b6b2a8 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 11:11:06 -0800 Subject: WIP - Tests for compliant forwarder --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 1 + .../contracts/test/extensions/compliant_forwarder.ts | 16 +++++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2febc5cce..0ecf44006 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -51,6 +51,7 @@ contract CompliantForwarder { if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); } + // Extract maker address from fill order transaction // Below is the table of calldata offsets into a fillOrder transaction. diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 9100c32f8..da023a192 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -147,7 +147,7 @@ describe.only(ContractName.Forwarder, () => { const defaultTakerAssetAddress = wethContract.address; const defaultOrderParams = { exchangeAddress: exchangeInstance.address, - compliantMakerAddress, + makerAddress: compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), @@ -174,10 +174,15 @@ describe.only(ContractName.Forwarder, () => { /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ + // Initialize Yes Token + await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({from: owner}); + const yesTokenName = "YesToken"; + const yesTokenTicker = "YEET"; + await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, {from: owner}); // Verify Maker / Taker const addressesCanControlTheirToken = true; - compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark]); - compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark]); + compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark], {from: owner}); + compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark], {from: owner}); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); @@ -200,13 +205,14 @@ describe.only(ContractName.Forwarder, () => { }); describe.only('fillOrder', () => { - let takerAssetFillAmount: BigNumber; beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract - // @TODO: Should fail if the + // @TODO: Should fail if the signed transaction is not intended for fillOrder + // @TODO: Should fail if maker is not verified + // @TODO: Should fail it taker is not verified it.only('should transfer the correct amounts when maker and taker are verified', async () => { await compliantForwarderInstance.fillOrder.sendTransactionAsync(compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature); -- cgit v1.2.3 From e81f92bffaf7748e710b342224004007261d2991 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 12:58:52 -0800 Subject: End-to-end test for compliant forwarder - works --- .../test/extensions/compliant_forwarder.ts | 56 +++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index da023a192..3e8b6a776 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -30,19 +30,24 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; +import { AbiEncoder } from '@0x/utils'; +import { MethodAbi } from 'ethereum-types'; + + chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; const MAX_WETH_FILL_PERCENTAGE = 95; -describe.only(ContractName.Forwarder, () => { +describe.only(ContractName.CompliantForwarder, () => { let compliantMakerAddress: string; let owner: string; let compliantTakerAddress: string; let feeRecipientAddress: string; let noncompliantAddress: string; let defaultMakerAssetAddress: string; + let defaultTakerAssetAddress: string; let zrxAssetData: string; let wethAssetData: string; @@ -52,19 +57,11 @@ describe.only(ContractName.Forwarder, () => { let yesComplianceToken: YesComplianceTokenContract; let compliantForwarderContract: CompliantForwarderContract; let wethContract: WETH9Contract; - let forwarderWrapper: ForwarderWrapper; let exchangeWrapper: ExchangeWrapper; - let orderWithoutFee: SignedOrder; - let orderWithFee: SignedOrder; - let feeOrder: SignedOrder; let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let tx: TransactionReceiptWithDecodedLogs; - - let makerAssetAddress: string; - let takerAssetAddress: string; let erc721MakerAssetIds: BigNumber[]; let takerEthBalanceBefore: BigNumber; @@ -107,8 +104,8 @@ describe.only(ContractName.Forwarder, () => { numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); - makerAssetAddress = erc20TokenA.address; - takerAssetAddress = erc20TokenB.address; + defaultMakerAssetAddress = erc20TokenA.address; + defaultTakerAssetAddress = erc20TokenB.address; // Deploy Yes Token const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( artifacts.YesComplianceToken, @@ -143,18 +140,16 @@ describe.only(ContractName.Forwarder, () => { from: owner, }); // Default order parameters - defaultMakerAssetAddress = erc20TokenA.address; - const defaultTakerAssetAddress = wethContract.address; const defaultOrderParams = { exchangeAddress: exchangeInstance.address, makerAddress: compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount: makerAssetAmount, - takerAssetAmount: takerAssetAmount, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), DECIMALS_DEFAULT), + makerAssetAmount, + takerAssetAmount, + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), }; const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); @@ -187,7 +182,7 @@ describe.only(ContractName.Forwarder, () => { const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderContract.address, + senderAddress: compliantForwarderInstance.address, }); const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(compliantSignedOrder); const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( @@ -215,8 +210,13 @@ describe.only(ContractName.Forwarder, () => { // @TODO: Should fail it taker is not verified it.only('should transfer the correct amounts when maker and taker are verified', async () => { - await compliantForwarderInstance.fillOrder.sendTransactionAsync(compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature); - const newBalances = await erc20Wrapper.getBalancesAsync(); + await compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature + ); + const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -226,20 +226,20 @@ describe.only(ContractName.Forwarder, () => { const takerFeePaid = compliantSignedOrder.takerFee .times(makerAssetFillAmount) .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][makerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][makerAssetAddress].minus(makerAssetFillAmount), + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - expect(newBalances[compliantMakerAddress][takerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][takerAssetAddress].add(takerAssetFillAmount), + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), ); expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), ); - expect(newBalances[compliantTakerAddress][takerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][takerAssetAddress].minus(takerAssetFillAmount), + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), ); - expect(newBalances[compliantTakerAddress][makerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][makerAssetAddress].add(makerAssetFillAmount), + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), ); expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), -- cgit v1.2.3 From a4ab038aa850f53539d0e4652297e52ecd45a8d3 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 13:07:59 -0800 Subject: Ran prettier --- .../test/extensions/compliant_forwarder.ts | 83 +++++++++++++--------- 1 file changed, 50 insertions(+), 33 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 3e8b6a776..91729d158 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -30,15 +30,10 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; -import { AbiEncoder } from '@0x/utils'; -import { MethodAbi } from 'ethereum-types'; - - chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; -const MAX_WETH_FILL_PERCENTAGE = 95; describe.only(ContractName.CompliantForwarder, () => { let compliantMakerAddress: string; @@ -81,9 +76,9 @@ describe.only(ContractName.CompliantForwarder, () => { const compliantTakerEntityId = new BigNumber(2); let compliantTakerYesTokenId; - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); - const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); + const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); const salt = new BigNumber(0); let compliantForwarderInstance: CompliantForwarderContract; @@ -92,7 +87,13 @@ describe.only(ContractName.CompliantForwarder, () => { // Create accounts await blockchainLifecycle.startAsync(); const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([owner, compliantMakerAddress, compliantTakerAddress, feeRecipientAddress, noncompliantAddress] = accounts); + const usedAddresses = ([ + owner, + compliantMakerAddress, + compliantTakerAddress, + feeRecipientAddress, + noncompliantAddress, + ] = accounts); // Create wrappers const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); @@ -107,7 +108,7 @@ describe.only(ContractName.CompliantForwarder, () => { defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; // Deploy Yes Token - const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( + const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( artifacts.YesComplianceToken, provider, txDefaults, @@ -169,28 +170,46 @@ describe.only(ContractName.CompliantForwarder, () => { /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ - // Initialize Yes Token - await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({from: owner}); - const yesTokenName = "YesToken"; - const yesTokenTicker = "YEET"; - await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, {from: owner}); - // Verify Maker / Taker - const addressesCanControlTheirToken = true; - compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, compliantMakerCountryCode, [compliantMakerYesMark], {from: owner}); - compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync(compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, compliantTakerCountryCode, [compliantTakerYesMark], {from: owner}); + // Initialize Yes Token + await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); + const yesTokenName = 'YesToken'; + const yesTokenTicker = 'YEET'; + await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); + // Verify Maker / Taker + const addressesCanControlTheirToken = true; + compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + compliantMakerAddress, + compliantMakerEntityId, + addressesCanControlTheirToken, + compliantMakerCountryCode, + [compliantMakerYesMark], + { from: owner }, + ); + compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + compliantTakerAddress, + compliantTakerEntityId, + addressesCanControlTheirToken, + compliantTakerCountryCode, + [compliantTakerYesMark], + { from: owner }, + ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, }); - const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress(compliantSignedOrder); + const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + compliantSignedOrder, + ); const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( compliantSignedOrderWithoutExchangeAddress, takerAssetFillAmount, compliantSignedOrder.signature, ); - compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction(compliantSignedOrderWithoutExchangeAddressData); + compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( + compliantSignedOrderWithoutExchangeAddressData, + ); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -198,25 +217,19 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract - // @TODO: Should fail if the signed transaction is not intended for fillOrder - // @TODO: Should fail if maker is not verified - // @TODO: Should fail it taker is not verified - it.only('should transfer the correct amounts when maker and taker are verified', async () => { - await compliantForwarderInstance.fillOrder.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature + await compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, ); - const newBalances = await erc20Wrapper.getBalancesAsync(); + const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -248,6 +261,10 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), ); }); + // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract + // @TODO: Should fail if the signed transaction is not intended for fillOrder + // @TODO: Should fail if maker is not verified + // @TODO: Should fail it taker is not verified }); }); // tslint:disable:max-file-line-count -- cgit v1.2.3 From 989b5b0a98c578321e37439066f9a45287824a6d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 13:13:52 -0800 Subject: Ran linter --- .../CompliantForwarder/CompliantForwarder.sol | 1 - .../test/extensions/compliant_forwarder.ts | 53 +++++----------------- 2 files changed, 11 insertions(+), 43 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 0ecf44006..2febc5cce 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -51,7 +51,6 @@ contract CompliantForwarder { if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); } - // Extract maker address from fill order transaction // Below is the table of calldata offsets into a fillOrder transaction. diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 91729d158..932012c0d 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -4,14 +4,12 @@ import { RevertReason, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; -import { WETH9Contract } from '../../generated-wrappers/weth9'; import { artifacts } from '../../src/artifacts'; import { expectContractCreationFailedAsync, @@ -21,9 +19,7 @@ import { import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { ForwarderWrapper } from '../utils/forwarder_wrapper'; import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; @@ -44,42 +40,20 @@ describe.only(ContractName.CompliantForwarder, () => { let defaultMakerAssetAddress: string; let defaultTakerAssetAddress: string; let zrxAssetData: string; - let wethAssetData: string; - - let weth: DummyERC20TokenContract; let zrxToken: DummyERC20TokenContract; - let erc20TokenA: DummyERC20TokenContract; - let yesComplianceToken: YesComplianceTokenContract; - let compliantForwarderContract: CompliantForwarderContract; - let wethContract: WETH9Contract; let exchangeWrapper: ExchangeWrapper; let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let erc721MakerAssetIds: BigNumber[]; - let takerEthBalanceBefore: BigNumber; - let feePercentage: BigNumber; - let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let noncompliantSignedFillOrderTx: SignedTransaction; - const compliantMakerCountryCode = new BigNumber(519); - const compliantMakerYesMark = new BigNumber(1); - const compliantMakerEntityId = new BigNumber(2); - let compliantMakerYesTokenId; - - const compliantTakerCountryCode = new BigNumber(519); - const compliantTakerYesMark = new BigNumber(1); - const compliantTakerEntityId = new BigNumber(2); - let compliantTakerYesTokenId; - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - const salt = new BigNumber(0); let compliantForwarderInstance: CompliantForwarderContract; @@ -95,7 +69,6 @@ describe.only(ContractName.CompliantForwarder, () => { noncompliantAddress, ] = accounts); // Create wrappers - const erc721Wrapper = new ERC721Wrapper(provider, usedAddresses, owner); erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); // Deploy ERC20 tokens const numDummyErc20ToDeploy = 3; @@ -107,26 +80,16 @@ describe.only(ContractName.CompliantForwarder, () => { ); defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; + zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); // Deploy Yes Token const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( artifacts.YesComplianceToken, provider, txDefaults, ); - compliantForwarderContract = new CompliantForwarderContract( - yesTokenInstance.abi, - yesTokenInstance.address, - provider, - ); // Create proxies const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy tokens & set asset data - wethContract = await WETH9Contract.deployFrom0xArtifactAsync(artifacts.WETH9, provider, txDefaults); - weth = new DummyERC20TokenContract(wethContract.abi, wethContract.address, provider); - erc20Wrapper.addDummyTokenContract(weth); - wethAssetData = assetDataUtils.encodeERC20AssetData(wethContract.address); - zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); // Deploy Exchange congtract const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, @@ -162,12 +125,12 @@ describe.only(ContractName.CompliantForwarder, () => { exchangeInstance.address, yesTokenInstance.address, ); - compliantForwarderContract = new CompliantForwarderContract( + /* + const compliantForwarderContract = new CompliantForwarderContract( compliantForwarderInstance.abi, compliantForwarderInstance.address, provider, ); - /* forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ // Initialize Yes Token @@ -177,7 +140,10 @@ describe.only(ContractName.CompliantForwarder, () => { await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); // Verify Maker / Taker const addressesCanControlTheirToken = true; - compliantMakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + const compliantMakerCountryCode = new BigNumber(519); + const compliantMakerYesMark = new BigNumber(1); + const compliantMakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( compliantMakerAddress, compliantMakerEntityId, addressesCanControlTheirToken, @@ -185,7 +151,10 @@ describe.only(ContractName.CompliantForwarder, () => { [compliantMakerYesMark], { from: owner }, ); - compliantTakerYesTokenId = await yesTokenInstance.mint2.sendTransactionAsync( + const compliantTakerCountryCode = new BigNumber(519); + const compliantTakerYesMark = new BigNumber(1); + const compliantTakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( compliantTakerAddress, compliantTakerEntityId, addressesCanControlTheirToken, -- cgit v1.2.3 From 33e41dd500960fde6bf1f5b1f4cf650731086963 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 13:56:39 -0800 Subject: More tests + require instead of revert in compliance contract --- .../CompliantForwarder/CompliantForwarder.sol | 19 +++-- .../test/extensions/compliant_forwarder.ts | 89 +++++++++++++++++++--- packages/types/src/index.ts | 2 + 3 files changed, 92 insertions(+), 18 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2febc5cce..f34ee699d 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -51,8 +51,14 @@ contract CompliantForwarder { if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); } + + // Taker must be compliant + require( + COMPLIANCE_TOKEN.balanceOf(signerAddress) > 0, + "TAKER_UNVERIFIED" + ); - // Extract maker address from fill order transaction + // Extract maker address from fill order transaction and ensure maker is compliant // Below is the table of calldata offsets into a fillOrder transaction. /** ### parameters @@ -82,13 +88,10 @@ contract CompliantForwarder { // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 address makerAddress = signedFillOrderTransaction.readAddress(0x70); - - // Verify maker/taker have been verified by the compliance token. - if (COMPLIANCE_TOKEN.balanceOf(makerAddress) == 0) { - revert("MAKER_UNVERIFIED"); - } else if (COMPLIANCE_TOKEN.balanceOf(signerAddress) == 0) { - revert("TAKER_UNVERIFIED"); - } + require( + COMPLIANCE_TOKEN.balanceOf(makerAddress) > 0, + "MAKER_UNVERIFIED" + ); // All entities are verified. Execute fillOrder. EXCHANGE.executeTransaction( diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 932012c0d..311ad78e9 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -4,6 +4,7 @@ import { RevertReason, SignedOrder } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; +import * as ethUtil from 'ethereumjs-util'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; @@ -12,9 +13,8 @@ import { YesComplianceTokenContract } from '../../generated-wrappers/yes_complia import { artifacts } from '../../src/artifacts'; import { - expectContractCreationFailedAsync, expectTransactionFailedAsync, - sendTransactionResult, + expectTransactionFailedWithoutReasonAsync, } from '../utils/assertions'; import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; @@ -36,17 +36,19 @@ describe.only(ContractName.CompliantForwarder, () => { let owner: string; let compliantTakerAddress: string; let feeRecipientAddress: string; - let noncompliantAddress: string; + let nonCompliantAddress: string; let defaultMakerAssetAddress: string; let defaultTakerAssetAddress: string; let zrxAssetData: string; let zrxToken: DummyERC20TokenContract; + let exchangeInstance: ExchangeContract; let exchangeWrapper: ExchangeWrapper; let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; + let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let noncompliantSignedFillOrderTx: SignedTransaction; @@ -66,7 +68,7 @@ describe.only(ContractName.CompliantForwarder, () => { compliantMakerAddress, compliantTakerAddress, feeRecipientAddress, - noncompliantAddress, + nonCompliantAddress, ] = accounts); // Create wrappers erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); @@ -91,7 +93,7 @@ describe.only(ContractName.CompliantForwarder, () => { const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); // Deploy Exchange congtract - const exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( artifacts.Exchange, provider, txDefaults, @@ -164,7 +166,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; - const takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); + takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, }); @@ -190,8 +192,7 @@ describe.only(ContractName.CompliantForwarder, () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - - it.only('should transfer the correct amounts when maker and taker are verified', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { await compliantForwarderInstance.fillOrder.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -230,8 +231,76 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), ); }); - // @TODO: Should fail if order's senderAddress is not set to the compliant forwarding contract - // @TODO: Should fail if the signed transaction is not intended for fillOrder + it('should revert if the signed transaction is not intended for fillOrder', async () => { + // Create signed order without the fillOrder function selector + const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); + const selectorLengthInBytes = 4; + const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); + const badSelector = '0x00000000'; + const badSelectorBuf = ethUtil.toBuffer(badSelector); + const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); + const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + txDataBufWithBadSelectorHex, + compliantSignedFillOrderTx.signature, + )); + }); + it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { + // Create signed order with incorrect senderAddress + const notCompliantForwarderAddress = zrxToken.address; + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: notCompliantForwarderAddress, + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + )); + }); + it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedAsync( + compliantForwarderInstance.fillOrder.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + ), + RevertReason.MakerUnverified + ); + }); // @TODO: Should fail if maker is not verified // @TODO: Should fail it taker is not verified }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 6b728af71..0c6fd7fd7 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -243,6 +243,8 @@ export enum RevertReason { AuctionNotStarted = 'AUCTION_NOT_STARTED', AuctionInvalidBeginTime = 'INVALID_BEGIN_TIME', InvalidAssetData = 'INVALID_ASSET_DATA', + MakerUnverified = 'MAKER_UNVERIFED', + TakerUnverified = 'TAKER_UNVERIFIED', } export enum StatusCodes { -- cgit v1.2.3 From 3f7bd24250d0a965e4f4c95b731b2c6e5a481fcb Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 14:05:04 -0800 Subject: Wrapped up tests for compliant forwarder --- .../CompliantForwarder/CompliantForwarder.sol | 3 ++- .../contracts/test/extensions/compliant_forwarder.ts | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index f34ee699d..aaa2d93d6 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -93,12 +93,13 @@ contract CompliantForwarder { "MAKER_UNVERIFIED" ); + /* // All entities are verified. Execute fillOrder. EXCHANGE.executeTransaction( salt, signerAddress, signedFillOrderTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 311ad78e9..61bbe020c 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -273,14 +273,25 @@ describe.only(ContractName.CompliantForwarder, () => { signedFillOrderTx.signature, )); }); + it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { + return expectTransactionFailedAsync( + compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + nonCompliantAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ), + RevertReason.TakerUnverified + ); + }); it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { // Create signed order with non-compliant maker address - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, makerAddress: nonCompliantAddress }); const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, + signedOrderWithBadMakerAddress, ); const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( signedOrderWithoutExchangeAddress, @@ -301,8 +312,6 @@ describe.only(ContractName.CompliantForwarder, () => { RevertReason.MakerUnverified ); }); - // @TODO: Should fail if maker is not verified - // @TODO: Should fail it taker is not verified }); }); // tslint:disable:max-file-line-count -- cgit v1.2.3 From 743c4c36eb6300866a0bd0c8a4bf39a3ce7b31c2 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 14:06:09 -0800 Subject: Removed unnecessary comments in compliant forwarder --- .../contracts/extensions/CompliantForwarder/CompliantForwarder.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index aaa2d93d6..f34ee699d 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -93,13 +93,12 @@ contract CompliantForwarder { "MAKER_UNVERIFIED" ); - /* // All entities are verified. Execute fillOrder. EXCHANGE.executeTransaction( salt, signerAddress, signedFillOrderTransaction, signature - );*/ + ); } } \ No newline at end of file -- cgit v1.2.3 From 37a1271af256af2c0a64b947b3c212a749b540b0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 16:30:51 -0800 Subject: fillOrder -> executeTransaction rename --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 2 +- packages/contracts/test/extensions/compliant_forwarder.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index f34ee699d..7a4c8d2f6 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -38,7 +38,7 @@ contract CompliantForwarder { COMPLIANCE_TOKEN = IERC721Token(complianceToken); } - function fillOrder( + function executeTransaction( uint256 salt, address signerAddress, bytes signedFillOrderTransaction, diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 61bbe020c..c962d82ac 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -193,7 +193,7 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); it('should transfer the correct amounts when maker and taker are compliant', async () => { - await compliantForwarderInstance.fillOrder.sendTransactionAsync( + await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, @@ -241,7 +241,7 @@ describe.only(ContractName.CompliantForwarder, () => { const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, txDataBufWithBadSelectorHex, @@ -266,7 +266,7 @@ describe.only(ContractName.CompliantForwarder, () => { signedOrderWithoutExchangeAddressData, ); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.fillOrder.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( signedFillOrderTx.salt, signedFillOrderTx.signerAddress, signedFillOrderTx.data, @@ -275,7 +275,7 @@ describe.only(ContractName.CompliantForwarder, () => { }); it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { return expectTransactionFailedAsync( - compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, nonCompliantAddress, compliantSignedFillOrderTx.data, @@ -303,7 +303,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); // Call compliant forwarder return expectTransactionFailedAsync( - compliantForwarderInstance.fillOrder.sendTransactionAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( signedFillOrderTx.salt, signedFillOrderTx.signerAddress, signedFillOrderTx.data, -- cgit v1.2.3 From 4e341582ae74d7ab1c52c4e888d72c0c1c78e890 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 29 Nov 2018 18:24:19 -0800 Subject: Extract makerAddress in assembly --- .../CompliantForwarder/CompliantForwarder.sol | 42 ++++++++++++++++++---- .../test/extensions/compliant_forwarder.ts | 16 ++++++++- 2 files changed, 50 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 7a4c8d2f6..b8ba43b15 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -31,6 +31,8 @@ contract CompliantForwarder { IExchange internal EXCHANGE; IERC721Token internal COMPLIANCE_TOKEN; + bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR_2 = 0xb4be83d5; + constructor(address exchange, address complianceToken) public { @@ -41,17 +43,43 @@ contract CompliantForwarder { function executeTransaction( uint256 salt, address signerAddress, - bytes signedFillOrderTransaction, + bytes signedExchangeTransaction, bytes signature ) - public + external { // Validate `signedFillOrderTransaction` - bytes4 selector = signedFillOrderTransaction.readBytes4(0); - if (selector != EXCHANGE_FILL_ORDER_SELECTOR) { - revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); + bytes4 selector = signedExchangeTransaction.readBytes4(0); + address makerAddress = 0x00; + assembly { + function getMakerAddress(orderPtr) -> makerAddress { + let orderOffset := calldataload(orderPtr) + makerAddress := calldataload(orderOffset) + } + + switch selector + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { + let exchangeTxPtr := calldataload(0x44) + + // Add 0x20 for length offset and 0x04 for selector offset + let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 + let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) + + makerAddress := calldataload(orderPtr) + + + //makerAddress := getMakerAddress(orderPtr) + } + default { + // revert(0, 100) + } } + /* + if (selector != 0xb4be83d5) { + revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); + }*/ + // Taker must be compliant require( COMPLIANCE_TOKEN.balanceOf(signerAddress) > 0, @@ -87,7 +115,7 @@ contract CompliantForwarder { // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 - address makerAddress = signedFillOrderTransaction.readAddress(0x70); + //address makerAddress = signedExchangeTransaction.readAddress(0x70); require( COMPLIANCE_TOKEN.balanceOf(makerAddress) > 0, "MAKER_UNVERIFIED" @@ -97,7 +125,7 @@ contract CompliantForwarder { EXCHANGE.executeTransaction( salt, signerAddress, - signedFillOrderTransaction, + signedExchangeTransaction, signature ); } diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index c962d82ac..b916f54c9 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -26,6 +26,9 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; +import { MethodAbi } from 'ethereum-types'; +import { AbiEncoder } from '@0x/utils'; + chaiSetup.configure(); const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); @@ -192,7 +195,18 @@ describe.only(ContractName.CompliantForwarder, () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { + it.only('should transfer the correct amounts when maker and taker are compliant', async () => { + + + const method = new AbiEncoder.Method(compliantForwarderInstance.abi[0] as MethodAbi); + const args = [ + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature + ]; + console.log(method.encode(args, {annotate: true})); + await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, -- cgit v1.2.3 From 9f68ac7bbecea109692d62d5555bac67e86c123a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 30 Nov 2018 16:43:04 -0800 Subject: Making progress on generalized forwarder --- .../CompliantForwarder/CompliantForwarder.sol | 140 +++++++++++---------- .../test/extensions/compliant_forwarder.ts | 35 +++--- 2 files changed, 98 insertions(+), 77 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index b8ba43b15..739f55024 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -22,16 +22,20 @@ pragma experimental ABIEncoderV2; import "../../protocol/Exchange/interfaces/IExchange.sol"; import "../../tokens/ERC721Token/IERC721Token.sol"; import "../../utils/LibBytes/LibBytes.sol"; +import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; -contract CompliantForwarder { +contract CompliantForwarder is ExchangeSelectors{ using LibBytes for bytes; - bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR = bytes4(keccak256("fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)")); IExchange internal EXCHANGE; IERC721Token internal COMPLIANCE_TOKEN; - bytes4 constant internal EXCHANGE_FILL_ORDER_SELECTOR_2 = 0xb4be83d5; + event ValidatedAddresses ( + bytes32 selector, + address one, + address[] addresses + ); constructor(address exchange, address complianceToken) public @@ -49,84 +53,94 @@ contract CompliantForwarder { external { // Validate `signedFillOrderTransaction` - bytes4 selector = signedExchangeTransaction.readBytes4(0); - address makerAddress = 0x00; + address[] memory validatedAddresses; + bytes32 selectorS; + address one; assembly { - function getMakerAddress(orderPtr) -> makerAddress { - let orderOffset := calldataload(orderPtr) - makerAddress := calldataload(orderOffset) + // Adds address to validate + function addAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory location + let addressesToValidate_ := mload(0x40) + let nAddressesToValidate_ := mload(addressesToValidate_) + + // Increment length + nAddressesToValidate_ := add(mload(addressesToValidate_), 1) + mstore(addressesToValidate_, nAddressesToValidate_) + + // Append address to validate + let offset := mul(32, nAddressesToValidate_) + mstore(add(addressesToValidate_, offset), addressToValidate) } - switch selector - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { + function appendMakerAddressFromOrder(paramIndex) -> makerAddress { let exchangeTxPtr := calldataload(0x44) - // Add 0x20 for length offset and 0x04 for selector offset let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) - makerAddress := calldataload(orderPtr) - - - //makerAddress := getMakerAddress(orderPtr) + addAddressToValidate(makerAddress) + } + + + // Extract addresses to validate + let exchangeTxPtr1 := calldataload(0x44) + let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) + switch selector + case 0x097bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ + { + + } + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ + { + + } + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ + { + one := appendMakerAddressFromOrder(0) + //appendSignerAddress() } + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 /* cancelOrder */ {} default { - // revert(0, 100) + revert(0, 100) } + + let addressesToValidate := mload(0x40) + let nAddressesToValidate := mload(addressesToValidate) + let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) + mstore(0x40, newMemFreePtr) + + // Validate addresses + /* + let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) + for {let i := add(32, mload(addressesToValidate))} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + // call `COMPLIANCE_TOKEN.balanceOf` + let success := call( + gas, // forward all gas + complianceTokenAddress, // call address of asset proxy + 0, // don't send any ETH + i, // pointer to start of input + 32, // length of input (one padded address) + 0, // write output over memory that won't be reused + 0 // don't copy output to memory + ) + if eq(success, 0) { + revert(0, 100) + } + }*/ + + validatedAddresses := addressesToValidate + selectorS := selector } - - /* - if (selector != 0xb4be83d5) { - revert("EXCHANGE_TRANSACTION_NOT_FILL_ORDER"); - }*/ - - // Taker must be compliant - require( - COMPLIANCE_TOKEN.balanceOf(signerAddress) > 0, - "TAKER_UNVERIFIED" - ); - - // Extract maker address from fill order transaction and ensure maker is compliant - // Below is the table of calldata offsets into a fillOrder transaction. - /** - ### parameters - 0x00 ptr - 0x20 takerAssetFillAmount - 0x40 ptr - ### order - 0x60 makerAddress - 0x80 takerAddress - 0xa0 feeRecipientAddress - 0xc0 senderAddress - 0xe0 makerAssetAmount - 0x100 takerAssetAmount - 0x120 makerFee - 0x140 takerFee - 0x160 expirationTimeSeconds - 0x180 salt - 0x1a0 ptr - 0x1c0 ptr - 0x1e0 makerAssetData - * takerAssetData - * signature - ------------------------------ - * Context-dependent offsets; unknown at compile time. - */ - // Add 0x4 to a given offset to account for the fillOrder selector prepended to `signedFillOrderTransaction`. - // Add 0xc to the makerAddress since abi-encoded addresses are left padded with 12 bytes. - // Putting this together: makerAddress = 0x60 + 0x4 + 0xc = 0x70 - //address makerAddress = signedExchangeTransaction.readAddress(0x70); - require( - COMPLIANCE_TOKEN.balanceOf(makerAddress) > 0, - "MAKER_UNVERIFIED" - ); + + emit ValidatedAddresses(selectorS, one, validatedAddresses); // All entities are verified. Execute fillOrder. + /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index b916f54c9..4eedffe05 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -5,6 +5,7 @@ import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; @@ -26,8 +27,10 @@ import { TransactionFactory } from '../utils/transaction_factory'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; -import { MethodAbi } from 'ethereum-types'; +import { MethodAbi, AbiDefinition } from 'ethereum-types'; import { AbiEncoder } from '@0x/utils'; +import { Method } from '@0x/utils/lib/src/abi_encoder'; +import { LogDecoder } from '../utils/log_decoder'; chaiSetup.configure(); const expect = chai.expect; @@ -184,6 +187,18 @@ describe.only(ContractName.CompliantForwarder, () => { compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( compliantSignedOrderWithoutExchangeAddressData, ); + + /* generate selectors for every exchange method + _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { + try { + const method = new Method(abiDefinition as MethodAbi); + console.log('\n', `// ${method.getDataItem().name}`); + console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); + console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); + } catch(e) { + _.noop(); + } + });*/ }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -196,23 +211,15 @@ describe.only(ContractName.CompliantForwarder, () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); it.only('should transfer the correct amounts when maker and taker are compliant', async () => { - - - const method = new AbiEncoder.Method(compliantForwarderInstance.abi[0] as MethodAbi); - const args = [ - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature - ]; - console.log(method.encode(args, {annotate: true})); - - await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature, ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) @@ -298,7 +305,7 @@ describe.only(ContractName.CompliantForwarder, () => { RevertReason.TakerUnverified ); }); - it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + it.only('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, -- cgit v1.2.3 From 8c9d48477d47fdb974e03e1beb9d6752d1149ea1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 30 Nov 2018 16:43:22 -0800 Subject: Exchange selector contract - autogenerated --- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 162 +++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol (limited to 'packages') diff --git a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol new file mode 100644 index 000000000..d4c9bef28 --- /dev/null +++ b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol @@ -0,0 +1,162 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + + +contract ExchangeSelectors { + // filled + bytes4 constant filledSelector = 0x288cdc91; + bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); + + // batchFillOrders + bytes4 constant batchFillOrdersSelector = 0x297bb70b; + bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + + // cancelled + bytes4 constant cancelledSelector = 0x2ac12622; + bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); + + // preSign + bytes4 constant preSignSelector = 0x3683ef8e; + bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); + + // matchOrders + bytes4 constant matchOrdersSelector = 0x3c28d861; + bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); + + // fillOrderNoThrow + bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; + bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + + // assetProxies + bytes4 constant assetProxiesSelector = 0x3fd3c997; + bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); + + // batchCancelOrders + bytes4 constant batchCancelOrdersSelector = 0x4ac14782; + bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + + // batchFillOrKillOrders + bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; + bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + + // cancelOrdersUpTo + bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; + bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); + + // batchFillOrdersNoThrow + bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; + bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + + // getAssetProxy + bytes4 constant getAssetProxySelector = 0x60704108; + bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); + + // transactions + bytes4 constant transactionsSelector = 0x642f2eaf; + bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); + + // fillOrKillOrder + bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; + bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + + // setSignatureValidatorApproval + bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; + bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); + + // allowedValidators + bytes4 constant allowedValidatorsSelector = 0x7b8e3514; + bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); + + // marketSellOrders + bytes4 constant marketSellOrdersSelector = 0x7e1d9808; + bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // getOrdersInfo + bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; + bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + + // preSigned + bytes4 constant preSignedSelector = 0x82c174d0; + bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); + + // owner + bytes4 constant ownerSelector = 0x8da5cb5b; + bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); + + // isValidSignature + bytes4 constant isValidSignatureSelector = 0x93634702; + bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); + + // marketBuyOrdersNoThrow + bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; + bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // fillOrder + bytes4 constant fillOrderSelector = 0xb4be83d5; + bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + + // executeTransaction + bytes4 constant executeTransactionSelector = 0xbfc8bfce; + bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); + + // registerAssetProxy + bytes4 constant registerAssetProxySelector = 0xc585bb93; + bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); + + // getOrderInfo + bytes4 constant getOrderInfoSelector = 0xc75e0a81; + bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + + // cancelOrder + bytes4 constant cancelOrderSelector = 0xd46b02c3; + bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + + // orderEpoch + bytes4 constant orderEpochSelector = 0xd9bfa73e; + bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); + + // ZRX_ASSET_DATA + bytes4 constant ZRX_ASSET_DATASelector = 0xdb123b1a; + bytes4 constant ZRX_ASSET_DATASelectorGenerator = bytes4(keccak256('ZRX_ASSET_DATA()')); + + // marketSellOrdersNoThrow + bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; + bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // EIP712_DOMAIN_HASH + bytes4 constant EIP712_DOMAIN_HASHSelector = 0xe306f779; + bytes4 constant EIP712_DOMAIN_HASHSelectorGenerator = bytes4(keccak256('EIP712_DOMAIN_HASH()')); + + // marketBuyOrders + bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; + bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + + // currentContextAddress + bytes4 constant currentContextAddressSelector = 0xeea086ba; + bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); + + // transferOwnership + bytes4 constant transferOwnershipSelector = 0xf2fde38b; + bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); + + // VERSION + bytes4 constant VERSIONSelector = 0xffa1ad74; + bytes4 constant VERSIONSelectorGenerator = bytes4(keccak256('VERSION()')); +} \ No newline at end of file -- cgit v1.2.3 From 61a906e9e774b40cb1c1053005fef98aa1cc7c85 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 12:23:30 -0800 Subject: Abstract address validation in asm --- .../CompliantForwarder/CompliantForwarder.sol | 34 +++++++++++++--------- 1 file changed, 21 insertions(+), 13 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 739f55024..27c578eae 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,12 +72,12 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function appendMakerAddressFromOrder(paramIndex) -> makerAddress { + function appendMakerAddressFromOrder(paramIndex) { let exchangeTxPtr := calldataload(0x44) // Add 0x20 for length offset and 0x04 for selector offset let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) - makerAddress := calldataload(orderPtr) + let makerAddress := calldataload(orderPtr) addAddressToValidate(makerAddress) } @@ -96,8 +96,8 @@ contract CompliantForwarder is ExchangeSelectors{ } case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ { - one := appendMakerAddressFromOrder(0) - //appendSignerAddress() + appendMakerAddressFromOrder(0) + addAddressToValidate(signerAddress) } case 0xd46b02c300000000000000000000000000000000000000000000000000000000 /* cancelOrder */ {} default { @@ -110,23 +110,32 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(0x40, newMemFreePtr) // Validate addresses - /* let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let i := add(32, mload(addressesToValidate))} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` + mstore(newMemFreePtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, newMemFreePtr), mload(i)) + // call `COMPLIANCE_TOKEN.balanceOf` let success := call( gas, // forward all gas complianceTokenAddress, // call address of asset proxy 0, // don't send any ETH - i, // pointer to start of input - 32, // length of input (one padded address) - 0, // write output over memory that won't be reused - 0 // don't copy output to memory + newMemFreePtr, // pointer to start of input + 0x24, // length of input (one padded address) + newMemFreePtr, // write output to next free memory offset + 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { revert(0, 100) } - }*/ + + // Revert if balance not held + let addressBalance := mload(newMemFreePtr) + if eq(addressBalance, 0) { + revert(0, 100) + } + } validatedAddresses := addressesToValidate selectorS := selector @@ -135,12 +144,11 @@ contract CompliantForwarder is ExchangeSelectors{ emit ValidatedAddresses(selectorS, one, validatedAddresses); // All entities are verified. Execute fillOrder. - /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - );*/ + ); } } \ No newline at end of file -- cgit v1.2.3 From 16bd0ce7ec834029b5a6e8aa308c1d52ce6130ea Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 14:48:10 -0800 Subject: generalized KYC extension passing all tests --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 12 +++++++++++- packages/contracts/test/extensions/compliant_forwarder.ts | 8 ++++---- packages/types/src/index.ts | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 27c578eae..b79d8db54 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -127,13 +127,23 @@ contract CompliantForwarder is ExchangeSelectors{ 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { + // Revert with `Error("BALANCE_CHECK_FAILED")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000001453454e4445525f4e4f545f415554484f52495a454400000000000000) + mstore(96, 0) revert(0, 100) } // Revert if balance not held let addressBalance := mload(newMemFreePtr) if eq(addressBalance, 0) { - revert(0, 100) + // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) + mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) + revert(0, 109) } } diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 4eedffe05..639893798 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -210,7 +210,7 @@ describe.only(ContractName.CompliantForwarder, () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it.only('should transfer the correct amounts when maker and taker are compliant', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -302,10 +302,10 @@ describe.only(ContractName.CompliantForwarder, () => { compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature, ), - RevertReason.TakerUnverified + RevertReason.AtLeastOneAddressHasZeroBalance ); }); - it.only('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, @@ -330,7 +330,7 @@ describe.only(ContractName.CompliantForwarder, () => { signedFillOrderTx.data, signedFillOrderTx.signature, ), - RevertReason.MakerUnverified + RevertReason.AtLeastOneAddressHasZeroBalance ); }); }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 0c6fd7fd7..022b24e70 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -245,6 +245,7 @@ export enum RevertReason { InvalidAssetData = 'INVALID_ASSET_DATA', MakerUnverified = 'MAKER_UNVERIFED', TakerUnverified = 'TAKER_UNVERIFIED', + AtLeastOneAddressHasZeroBalance = 'AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE', } export enum StatusCodes { -- cgit v1.2.3 From bab7569ed9b0739a3d01c64dea17adb1bf5b82e1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 15:38:06 -0800 Subject: Refactoring asm --- .../CompliantForwarder/CompliantForwarder.sol | 44 ++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index b79d8db54..4ad9092bd 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,7 +72,42 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function appendMakerAddressFromOrder(paramIndex) { + function toGlobalCalldataOffset(offset) -> globalOffset { + globalOffset := add(0x4, offset) + } + + function toExchangeCalldataOffset(offset, orderParamIndex) -> exchangeOffset { + // exchangeTxPtr at global level + // 0x20 for length offset into exchange TX + // 0x4 for function selector in exhcange TX + let exchangeTxPtr := calldataload(0x44) + exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) + } + + function toOrderOffset(offset, orderParamIndex) -> orderOffset { + let exchangeOffset := calldataload( + toExchangeCalldataOffset( + offset, + orderParamIndex + ) + ) + orderOffset := toExchangeCalldataOffset(exchangeOffset, orderParamIndex) + } + + // function readMakerFieldFromOrder() + + /* + function readFieldFromOrder() + + function readMakerFieldFromOrder()*/ + + function appendMakerAddressFromOrder(orderParamIndex) { + let makerAddress := calldataload(toOrderOffset(0 /* makerAddress is at 0'th field */, 0 /*order is 1st param*/)) + addAddressToValidate(makerAddress) + } + +/* + function appendMakerAddressFromOrderSet(paramIndex) { let exchangeTxPtr := calldataload(0x44) // Add 0x20 for length offset and 0x04 for selector offset let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 @@ -80,6 +115,7 @@ contract CompliantForwarder is ExchangeSelectors{ let makerAddress := calldataload(orderPtr) addAddressToValidate(makerAddress) } +*/ // Extract addresses to validate @@ -88,11 +124,13 @@ contract CompliantForwarder is ExchangeSelectors{ switch selector case 0x097bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - + //appendMakerAddressFromOrderSet() } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { - + // appendMakerAddressFromOrder(0) + //// appendMakerAddressFromOrder(1) + // addAddressToValidate(signerAddress) } case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ { -- cgit v1.2.3 From a332c5e5c2ff11a7d4bd534f694d7859483a33f0 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 16:36:19 -0800 Subject: Code to validate addresses from order arrays --- .../CompliantForwarder/CompliantForwarder.sol | 29 ++++++++++++++++------ .../test/extensions/compliant_forwarder.ts | 28 ++++++++++++++++++++- 2 files changed, 49 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 4ad9092bd..ee32b135e 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -33,7 +33,7 @@ contract CompliantForwarder is ExchangeSelectors{ event ValidatedAddresses ( bytes32 selector, - address one, + bytes32 one, address[] addresses ); @@ -55,7 +55,7 @@ contract CompliantForwarder is ExchangeSelectors{ // Validate `signedFillOrderTransaction` address[] memory validatedAddresses; bytes32 selectorS; - address one; + bytes32 one; assembly { // Adds address to validate function addAddressToValidate(addressToValidate) { @@ -106,6 +106,17 @@ contract CompliantForwarder is ExchangeSelectors{ addAddressToValidate(makerAddress) } + function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { + let orderSetPtr := calldataload(toExchangeCalldataOffset(0, 0)) + let orderSetPtrCalldata := toExchangeCalldataOffset(add(orderSetPtr, 0x20), 0) + let orderSetLength := calldataload(toExchangeCalldataOffset(orderSetPtr, 0)) + for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { + let orderPtr := calldataload(toExchangeCalldataOffset(orderPtrOffset, 0)) + let makerAddress := calldataload(add(orderSetPtrCalldata, orderPtr)) + addAddressToValidate(makerAddress) + } + } + /* function appendMakerAddressFromOrderSet(paramIndex) { let exchangeTxPtr := calldataload(0x44) @@ -118,13 +129,15 @@ contract CompliantForwarder is ExchangeSelectors{ */ + + // Extract addresses to validate let exchangeTxPtr1 := calldataload(0x44) let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) switch selector - case 0x097bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - //appendMakerAddressFromOrderSet() + one := appendMakerAddressesFromOrderSet(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { @@ -147,6 +160,7 @@ contract CompliantForwarder is ExchangeSelectors{ let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) mstore(0x40, newMemFreePtr) + /* // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { @@ -165,7 +179,7 @@ contract CompliantForwarder is ExchangeSelectors{ 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { - // Revert with `Error("BALANCE_CHECK_FAILED")` + // Revert with `Error("BALANCE_CHECK_FAILED")` @TODO mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) mstore(64, 0x0000001453454e4445525f4e4f545f415554484f52495a454400000000000000) @@ -183,7 +197,7 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) revert(0, 109) } - } + }*/ validatedAddresses := addressesToValidate selectorS := selector @@ -192,11 +206,12 @@ contract CompliantForwarder is ExchangeSelectors{ emit ValidatedAddresses(selectorS, one, validatedAddresses); // All entities are verified. Execute fillOrder. + /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 639893798..846414685 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,7 +206,7 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); @@ -334,6 +334,32 @@ describe.only(ContractName.CompliantForwarder, () => { ); }); }); + + describe.only('batchFillOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it.only ('should transfer the correct amounts when maker and taker are compliant', async () => { + let order2 = _.cloneDeep(compliantSignedOrder); + order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; + const orders = [compliantSignedOrder, order2]; + const fillAmounts = [new BigNumber(4), new BigNumber(4)]; + const signatures = ["0xabcd", "0xabcd"]; + const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); + console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + exchangeCalldata, + compliantSignedFillOrderTx.signature, + ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); + }); + }); }); // tslint:disable:max-file-line-count // tslint:enable:no-unnecessary-type-assertion -- cgit v1.2.3 From 8007ef6c0b08ce7d2394323f000df7baeb254ebf Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:02:58 -0800 Subject: cleaning I --- .../CompliantForwarder/CompliantForwarder.sol | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index ee32b135e..0c596dfc2 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -117,6 +117,22 @@ contract CompliantForwarder is ExchangeSelectors{ } } + function exchangeCalldataload(offset) -> value { + value := calldataload(toExchangeCalldataOffset(offset, 0)) + } + + + function appendMakerAddressesFromOrderSet2(orderSetParamIndex) -> one { + let orderSetPtr := exchangeCalldataload(0) + let orderSetLength := exchangeCalldataload(orderSetPtr) + + for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { + let orderPtr := exchangeCalldataload(orderPtrOffset) + let makerAddress := exchangeCalldataload(add(orderSetPtr, add(0x20, orderPtr))) + addAddressToValidate(makerAddress) + } + } + /* function appendMakerAddressFromOrderSet(paramIndex) { let exchangeTxPtr := calldataload(0x44) @@ -137,7 +153,7 @@ contract CompliantForwarder is ExchangeSelectors{ switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - one := appendMakerAddressesFromOrderSet(0) + one := appendMakerAddressesFromOrderSet2(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { -- cgit v1.2.3 From c040ad085063ec67abfd73c5cf61739f54d2a7a6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:18:34 -0800 Subject: cleaning --- .../CompliantForwarder/CompliantForwarder.sol | 60 ++++------------------ .../test/extensions/compliant_forwarder.ts | 15 +++--- 2 files changed, 19 insertions(+), 56 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 0c596dfc2..2d06d69e6 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -84,76 +84,36 @@ contract CompliantForwarder is ExchangeSelectors{ exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) } - function toOrderOffset(offset, orderParamIndex) -> orderOffset { - let exchangeOffset := calldataload( - toExchangeCalldataOffset( - offset, - orderParamIndex - ) - ) - orderOffset := toExchangeCalldataOffset(exchangeOffset, orderParamIndex) + function exchangeCalldataload(offset) -> value { + + value := calldataload(toExchangeCalldataOffset(offset, 0)) } - // function readMakerFieldFromOrder() - - /* - function readFieldFromOrder() - - function readMakerFieldFromOrder()*/ - function appendMakerAddressFromOrder(orderParamIndex) { - let makerAddress := calldataload(toOrderOffset(0 /* makerAddress is at 0'th field */, 0 /*order is 1st param*/)) + let orderPtr := exchangeCalldataload(0) + let makerAddress := exchangeCalldataload(orderPtr) addAddressToValidate(makerAddress) } function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { - let orderSetPtr := calldataload(toExchangeCalldataOffset(0, 0)) - let orderSetPtrCalldata := toExchangeCalldataOffset(add(orderSetPtr, 0x20), 0) - let orderSetLength := calldataload(toExchangeCalldataOffset(orderSetPtr, 0)) - for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { - let orderPtr := calldataload(toExchangeCalldataOffset(orderPtrOffset, 0)) - let makerAddress := calldataload(add(orderSetPtrCalldata, orderPtr)) - addAddressToValidate(makerAddress) - } - } - - function exchangeCalldataload(offset) -> value { - value := calldataload(toExchangeCalldataOffset(offset, 0)) - } - - - function appendMakerAddressesFromOrderSet2(orderSetParamIndex) -> one { let orderSetPtr := exchangeCalldataload(0) let orderSetLength := exchangeCalldataload(orderSetPtr) - - for {let orderPtrOffset := add(0x20, orderSetPtr)} lt(orderPtrOffset, add(0x20, add(orderSetPtr, mul(0x20, orderSetLength)))) {orderPtrOffset := add(0x20, orderPtrOffset)} { + let orderSetElementPtr := add(orderSetPtr, 0x20) + let orderSetElementEndPtr := add(orderSetElementPtr, mul(orderSetLength, 0x20)) + for {let orderPtrOffset := orderSetElementPtr} lt(orderPtrOffset, orderSetElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { let orderPtr := exchangeCalldataload(orderPtrOffset) - let makerAddress := exchangeCalldataload(add(orderSetPtr, add(0x20, orderPtr))) + let makerAddress := exchangeCalldataload(add(orderPtr, orderSetElementPtr)) addAddressToValidate(makerAddress) } } -/* - function appendMakerAddressFromOrderSet(paramIndex) { - let exchangeTxPtr := calldataload(0x44) - // Add 0x20 for length offset and 0x04 for selector offset - let orderPtrRelativeToExchangeTx := calldataload(add(0x4, add(exchangeTxPtr, 0x24))) // 0x60 - let orderPtr := add(0x4,add(exchangeTxPtr, add(0x24, orderPtrRelativeToExchangeTx))) - let makerAddress := calldataload(orderPtr) - addAddressToValidate(makerAddress) - } -*/ - - - - // Extract addresses to validate let exchangeTxPtr1 := calldataload(0x44) let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - one := appendMakerAddressesFromOrderSet2(0) + one := appendMakerAddressesFromOrderSet(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 846414685..e995e5435 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,11 +206,11 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('fillOrder', () => { + describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { + it.only('should transfer the correct amounts when maker and taker are compliant', async () => { const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -220,7 +220,10 @@ describe.only(ContractName.CompliantForwarder, () => { const decoder = new LogDecoder(web3Wrapper); const tx = await decoder.getTxWithDecodedLogsAsync(txHash); console.log(JSON.stringify(tx, null, 4)); - const newBalances = await erc20Wrapper.getBalancesAsync(); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + + + /*const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -250,7 +253,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); + );*/ }); it('should revert if the signed transaction is not intended for fillOrder', async () => { // Create signed order without the fillOrder function selector @@ -335,11 +338,11 @@ describe.only(ContractName.CompliantForwarder, () => { }); }); - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it.only ('should transfer the correct amounts when maker and taker are compliant', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { let order2 = _.cloneDeep(compliantSignedOrder); order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; const orders = [compliantSignedOrder, order2]; -- cgit v1.2.3 From 28a5ed6a9a1e44dd298d98f9f4bcb65b1d021e26 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:19:58 -0800 Subject: cleaning --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2d06d69e6..687e931da 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,21 +72,13 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function toGlobalCalldataOffset(offset) -> globalOffset { - globalOffset := add(0x4, offset) - } - - function toExchangeCalldataOffset(offset, orderParamIndex) -> exchangeOffset { + function exchangeCalldataload(offset) -> value { // exchangeTxPtr at global level // 0x20 for length offset into exchange TX // 0x4 for function selector in exhcange TX let exchangeTxPtr := calldataload(0x44) - exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) - } - - function exchangeCalldataload(offset) -> value { - - value := calldataload(toExchangeCalldataOffset(offset, 0)) + let exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) + value := calldataload(exchangeOffset) } function appendMakerAddressFromOrder(orderParamIndex) { -- cgit v1.2.3 From df0de071841e2953f6843a86bbce4ecb3ad7b04f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:26:24 -0800 Subject: cleaning --- .../CompliantForwarder/CompliantForwarder.sol | 28 +++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 687e931da..b5290dc10 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -72,36 +72,46 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } + function validateAddress(addressToValidate) { + + } + function exchangeCalldataload(offset) -> value { // exchangeTxPtr at global level // 0x20 for length offset into exchange TX // 0x4 for function selector in exhcange TX let exchangeTxPtr := calldataload(0x44) - let exchangeOffset := add(0x4, add(exchangeTxPtr, add(0x24, offset))) + let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) value := calldataload(exchangeOffset) } + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + function appendMakerAddressFromOrder(orderParamIndex) { - let orderPtr := exchangeCalldataload(0) - let makerAddress := exchangeCalldataload(orderPtr) + let orderPtr := loadExchangeData(0) + let makerAddress := loadExchangeData(orderPtr) addAddressToValidate(makerAddress) } function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { - let orderSetPtr := exchangeCalldataload(0) - let orderSetLength := exchangeCalldataload(orderSetPtr) + let orderSetPtr := loadExchangeData(0) + let orderSetLength := loadExchangeData(orderSetPtr) let orderSetElementPtr := add(orderSetPtr, 0x20) let orderSetElementEndPtr := add(orderSetElementPtr, mul(orderSetLength, 0x20)) for {let orderPtrOffset := orderSetElementPtr} lt(orderPtrOffset, orderSetElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := exchangeCalldataload(orderPtrOffset) - let makerAddress := exchangeCalldataload(add(orderPtr, orderSetElementPtr)) + let orderPtr := loadExchangeData(orderPtrOffset) + let makerAddress := loadExchangeData(add(orderPtr, orderSetElementPtr)) addAddressToValidate(makerAddress) } } // Extract addresses to validate - let exchangeTxPtr1 := calldataload(0x44) - let selector := and(calldataload(add(0x4, add(0x20, exchangeTxPtr1))), 0xffffffff00000000000000000000000000000000000000000000000000000000) + let selector := and( + exchangeCalldataload(0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { -- cgit v1.2.3 From 5863a29a913e6418c5ae9cbec146ad30c3eee6cc Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:35:45 -0800 Subject: cleaning --- .../CompliantForwarder/CompliantForwarder.sol | 58 +++++++++++----------- .../test/extensions/compliant_forwarder.ts | 6 +-- 2 files changed, 30 insertions(+), 34 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index b5290dc10..55100cae1 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -32,8 +32,6 @@ contract CompliantForwarder is ExchangeSelectors{ IERC721Token internal COMPLIANCE_TOKEN; event ValidatedAddresses ( - bytes32 selector, - bytes32 one, address[] addresses ); @@ -52,11 +50,29 @@ contract CompliantForwarder is ExchangeSelectors{ ) external { - // Validate `signedFillOrderTransaction` + // Addresses that are validated below. address[] memory validatedAddresses; - bytes32 selectorS; - bytes32 one; + + /** + * Do not add variables after this point. + * The assembly block may overwrite their values. + */ + + // Validate addresses assembly { + function exchangeCalldataload(offset) -> value { + // exchangeTxPtr at global level + // 0x20 for length offset into exchange TX + // 0x4 for function selector in exhcange TX + let exchangeTxPtr := calldataload(0x44) + let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeOffset) + } + + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + // Adds address to validate function addAddressToValidate(addressToValidate) { // Compute `addressesToValidate` memory location @@ -72,30 +88,13 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(add(addressesToValidate_, offset), addressToValidate) } - function validateAddress(addressToValidate) { - - } - - function exchangeCalldataload(offset) -> value { - // exchangeTxPtr at global level - // 0x20 for length offset into exchange TX - // 0x4 for function selector in exhcange TX - let exchangeTxPtr := calldataload(0x44) - let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeOffset) - } - - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - function appendMakerAddressFromOrder(orderParamIndex) { let orderPtr := loadExchangeData(0) let makerAddress := loadExchangeData(orderPtr) addAddressToValidate(makerAddress) } - function appendMakerAddressesFromOrderSet(orderSetParamIndex) -> one { + function appendMakerAddressesFromOrderSet(orderSetParamIndex) { let orderSetPtr := loadExchangeData(0) let orderSetLength := loadExchangeData(orderSetPtr) let orderSetElementPtr := add(orderSetPtr, 0x20) @@ -115,7 +114,7 @@ contract CompliantForwarder is ExchangeSelectors{ switch selector case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { - one := appendMakerAddressesFromOrderSet(0) + appendMakerAddressesFromOrderSet(0) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { @@ -133,12 +132,12 @@ contract CompliantForwarder is ExchangeSelectors{ revert(0, 100) } + // let addressesToValidate := mload(0x40) let nAddressesToValidate := mload(addressesToValidate) let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) mstore(0x40, newMemFreePtr) - /* // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { @@ -175,21 +174,20 @@ contract CompliantForwarder is ExchangeSelectors{ mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) revert(0, 109) } - }*/ + } + // Record validated addresses validatedAddresses := addressesToValidate - selectorS := selector } - emit ValidatedAddresses(selectorS, one, validatedAddresses); + emit ValidatedAddresses(validatedAddresses); // All entities are verified. Execute fillOrder. - /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - );*/ + ); } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index e995e5435..cc05f9981 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -221,9 +221,7 @@ describe.only(ContractName.CompliantForwarder, () => { const tx = await decoder.getTxWithDecodedLogsAsync(txHash); console.log(JSON.stringify(tx, null, 4)); console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - - - /*const newBalances = await erc20Wrapper.getBalancesAsync(); + const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); @@ -253,7 +251,7 @@ describe.only(ContractName.CompliantForwarder, () => { ); expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - );*/ + ); }); it('should revert if the signed transaction is not intended for fillOrder', async () => { // Create signed order without the fillOrder function selector -- cgit v1.2.3 From 4217d0cd7d7993b72415cd34a8fc3fab6c17976e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:43:00 -0800 Subject: cleanup --- .../CompliantForwarder/CompliantForwarder.sol | 33 ++++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 55100cae1..f81b975be 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -115,6 +115,7 @@ contract CompliantForwarder is ExchangeSelectors{ case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ { appendMakerAddressesFromOrderSet(0) + addAddressToValidate(signerAddress) } case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ { @@ -132,40 +133,42 @@ contract CompliantForwarder is ExchangeSelectors{ revert(0, 100) } - // + // Load addresses to validate from memory let addressesToValidate := mload(0x40) - let nAddressesToValidate := mload(addressesToValidate) - let newMemFreePtr := add(addressesToValidate, add(0x20, mul(mload(addressesToValidate), 0x20))) - mstore(0x40, newMemFreePtr) + let addressesToValidateLength := mload(addressesToValidate) + let addressesToValidateElementPtr := add(addressesToValidate, 0x20) + let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) + + // Record new free memory pointer to after `addressesToValidate` array + // This is to avoid corruption when making calls in the loop below. + let freeMemPtr := addressesToValidateElementEndPtr + mstore(0x40, freeMemPtr) // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let i := add(0x20, addressesToValidate)} lt(i, add(addressesToValidate, add(32, mul(nAddressesToValidate, 32)))) {i := add(i, 32)} { + + for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` - mstore(newMemFreePtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, newMemFreePtr), mload(i)) + mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, freeMemPtr), mload(addressToValidate)) // call `COMPLIANCE_TOKEN.balanceOf` let success := call( gas, // forward all gas complianceTokenAddress, // call address of asset proxy 0, // don't send any ETH - newMemFreePtr, // pointer to start of input + freeMemPtr, // pointer to start of input 0x24, // length of input (one padded address) - newMemFreePtr, // write output to next free memory offset + freeMemPtr, // write output to next free memory offset 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { - // Revert with `Error("BALANCE_CHECK_FAILED")` @TODO - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000001453454e4445525f4e4f545f415554484f52495a454400000000000000) - mstore(96, 0) + // @TODO Revert with `Error("BALANCE_CHECK_FAILED")` revert(0, 100) } // Revert if balance not held - let addressBalance := mload(newMemFreePtr) + let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) -- cgit v1.2.3 From 0556defa58ba0a74779a0dc828c112631fb19eb6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 3 Dec 2018 17:46:25 -0800 Subject: working on batch fills. Compliance part is finished. --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 8 ++++---- packages/contracts/test/extensions/compliant_forwarder.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index f81b975be..706c2091d 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -171,11 +171,11 @@ contract CompliantForwarder is ExchangeSelectors{ let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + /*mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) - revert(0, 109) + revert(0, 109)*/ } } @@ -186,11 +186,11 @@ contract CompliantForwarder is ExchangeSelectors{ emit ValidatedAddresses(validatedAddresses); // All entities are verified. Execute fillOrder. - EXCHANGE.executeTransaction( + /* EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - ); + );*/ } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index cc05f9981..b066f5d08 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,11 +206,11 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); - it.only('should transfer the correct amounts when maker and taker are compliant', async () => { + it('should transfer the correct amounts when maker and taker are compliant', async () => { const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, @@ -336,7 +336,7 @@ describe.only(ContractName.CompliantForwarder, () => { }); }); - describe('batchFillOrders', () => { + describe.only('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); -- cgit v1.2.3 From 3bb147b0f1554366733098dac1af924225effc26 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 13:37:48 -0800 Subject: Documented inline assembly functions --- .../CompliantForwarder/CompliantForwarder.sol | 200 ++++++++++++++++----- .../test/extensions/compliant_forwarder.ts | 4 +- 2 files changed, 153 insertions(+), 51 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 706c2091d..2dce95716 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -60,93 +60,193 @@ contract CompliantForwarder is ExchangeSelectors{ // Validate addresses assembly { + /** + * Emulates the `calldataload` opcode on the embedded Exchange calldata, + * which is accessed through `signedExchangeTransaction`. + * @param offset - Offset into the Exchange calldata. + * @return value - Corresponding 32 byte value stored at `offset`. + */ function exchangeCalldataload(offset) -> value { - // exchangeTxPtr at global level - // 0x20 for length offset into exchange TX - // 0x4 for function selector in exhcange TX + // Pointer to exchange transaction + // 0x04 for calldata selector + // 0x40 to access `signedExchangeTransaction`, which is the third parameter let exchangeTxPtr := calldataload(0x44) - let exchangeOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeOffset) + + // Offset into Exchange calldata + // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. + // 0x04 for calldata selector + // 0x20 for length field of `signedExchangeTransaction` + let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeCalldataOffset) } + /** + * Convenience function that skips the 4 byte selector when loading + * from the embedded Exchange calldata. + * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + * @return value - Corresponding 32 byte value stored at `offset` + 4. + */ function loadExchangeData(offset) -> value { value := exchangeCalldataload(add(offset, 0x4)) } - // Adds address to validate - function addAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory location + /** + * A running list is maintained of addresses to validate. + * This function records an address in this array. + * @param addressToValidate - Address to record for validation. + * @note - Variables are scoped but names are not, so we append + * underscores to names that share the global namespace. + */ + function recordAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory offset let addressesToValidate_ := mload(0x40) let nAddressesToValidate_ := mload(addressesToValidate_) // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 1) + nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) mstore(addressesToValidate_, nAddressesToValidate_) // Append address to validate - let offset := mul(32, nAddressesToValidate_) + let offset := mul(nAddressesToValidate_, 0x20) mstore(add(addressesToValidate_, offset), addressToValidate) } - function appendMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(0) + /** + * Extracts the maker address from an order stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records it in + * the running list of addresses to validate. + * @param orderParamIndex - Index of the order in the Exchange function's signature + */ + function recordMakerAddressFromOrder(orderParamIndex) { + let orderPtr := loadExchangeData(orderParamIndex) let makerAddress := loadExchangeData(orderPtr) - addAddressToValidate(makerAddress) + recordAddressToValidate(makerAddress) } - function appendMakerAddressesFromOrderSet(orderSetParamIndex) { - let orderSetPtr := loadExchangeData(0) - let orderSetLength := loadExchangeData(orderSetPtr) - let orderSetElementPtr := add(orderSetPtr, 0x20) - let orderSetElementEndPtr := add(orderSetElementPtr, mul(orderSetLength, 0x20)) - for {let orderPtrOffset := orderSetElementPtr} lt(orderPtrOffset, orderSetElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { + /** + * Extracts the maker addresses from an array of orders stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records them in + * the running list of addresses to validate. + * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature + */ + function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { + let orderArrayPtr := loadExchangeData(0x0) + let orderArrayLength := loadExchangeData(orderArrayPtr) + let orderArrayElementPtr := add(orderArrayPtr, 0x20) + let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) + for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderSetElementPtr)) - addAddressToValidate(makerAddress) + let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) + recordAddressToValidate(makerAddress) } } - // Extract addresses to validate - let selector := and( - exchangeCalldataload(0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - switch selector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 /* batchFillOrders */ - { - appendMakerAddressesFromOrderSet(0) - addAddressToValidate(signerAddress) + /** + * Records address of signer in the running list of addresses to validate. + * @note: We cannot access `signerAddress` directly from within the asm function, + * so it is loaded from the calldata. + */ + function recordSignerAddress() { + // Load the signer address from calldata + // 0x04 for selector + // 0x20 to access `signerAddress`, which is the second parameter. + let signerAddress_ := calldataload(0x24) + recordAddressToValidate(signerAddress_) + } + + /** + * Records addresses to be validated when Exchange transaction is a batch fill variant. + * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + * Reference signature: (Order[],uint256[],bytes[]) + */ + function recordAddressesForBatchFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a fill order variant. + * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + * Reference signature: (Order,uint256,bytes) + */ + function recordAddressesForFillOrderVariant() { + // Record maker address from the order (param index 0) + // The signer is the taker for this order and must also be validated. + recordMakerAddressFromOrder(0) + recordSignerAddress() } - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 /* matchOrders */ - { - // appendMakerAddressFromOrder(0) - //// appendMakerAddressFromOrder(1) - // addAddressToValidate(signerAddress) + + /** + * Records addresses to be validated when Exchange transaction is a market fill variant. + * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + * Reference signature: (Order[],uint256,bytes[]) + */ + function recordAddressesForMarketFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() } - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 /* fillOrder */ - { - appendMakerAddressFromOrder(0) - addAddressToValidate(signerAddress) + + /** + * Records addresses to be validated when Exchange transaction is matchOrders. + * Reference signature: matchOrders(Order,Order) + */ + function recordAddressesForMatchOrders() { + // Record maker address from both orders (param indices 0 & 1). + // The signer is the taker and must also be validated. + recordMakerAddressFromOrder(0) + recordMakerAddressFromOrder(1) + recordSignerAddress() } - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 /* cancelOrder */ {} + + ///// Record Addresses to Validate ///// + + // Addresses needing validation depends on which Exchange function is being called. + // Step 1/2 Read the exchange function selector. + let exchangeFunctionSelector := and( + exchangeCalldataload(0x0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) + + // Step 2/2 Extract addresses to validate based on this selector. + // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors + switch exchangeFunctionSelector + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders + case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow + case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder + case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow + case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder + case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders + case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow + case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders + case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder + case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders + case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo default { revert(0, 100) } - // Load addresses to validate from memory + ///// Validate Recorded Addresses ///// + + // Load from memory the addresses to validate let addressesToValidate := mload(0x40) let addressesToValidateLength := mload(addressesToValidate) let addressesToValidateElementPtr := add(addressesToValidate, 0x20) let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - // Record new free memory pointer to after `addressesToValidate` array + // Set free memory pointer to after `addressesToValidate` array. // This is to avoid corruption when making calls in the loop below. let freeMemPtr := addressesToValidateElementEndPtr mstore(0x40, freeMemPtr) // Validate addresses let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) @@ -171,11 +271,11 @@ contract CompliantForwarder is ExchangeSelectors{ let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` - /*mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) - revert(0, 109)*/ + revert(0, 109) } } @@ -183,14 +283,16 @@ contract CompliantForwarder is ExchangeSelectors{ validatedAddresses := addressesToValidate } + + ///// If we hit this point then all addresses are valid ///// emit ValidatedAddresses(validatedAddresses); - // All entities are verified. Execute fillOrder. - /* EXCHANGE.executeTransaction( + // All addresses are valid. Execute fillOrder. + EXCHANGE.executeTransaction( salt, signerAddress, signedExchangeTransaction, signature - );*/ + ); } } \ No newline at end of file diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index b066f5d08..8fa811936 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -206,7 +206,7 @@ describe.only(ContractName.CompliantForwarder, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe('fillOrder', () => { + describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); @@ -336,7 +336,7 @@ describe.only(ContractName.CompliantForwarder, () => { }); }); - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); -- cgit v1.2.3 From ba986432eca33ffb4d3c916a78049c6633147b82 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 14:03:48 -0800 Subject: Some comments --- .../extensions/CompliantForwarder/CompliantForwarder.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 2dce95716..71c124292 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -229,6 +229,7 @@ contract CompliantForwarder is ExchangeSelectors{ case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo default { + // @TODO Revert with `Error("INVALID_OR_UNHANDLED_EXCHANGE_SELECTOR")` revert(0, 100) } @@ -272,9 +273,9 @@ contract CompliantForwarder is ExchangeSelectors{ if eq(addressBalance, 0) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) - mstore(96, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) + mstore(0x60, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) revert(0, 109) } } @@ -283,7 +284,6 @@ contract CompliantForwarder is ExchangeSelectors{ validatedAddresses := addressesToValidate } - ///// If we hit this point then all addresses are valid ///// emit ValidatedAddresses(validatedAddresses); -- cgit v1.2.3 From dbf1de2e691743672fa4918e7ab0f1e3948401f1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 14:21:07 -0800 Subject: Revert reasons for balance threshold filter --- .../CompliantForwarder/CompliantForwarder.sol | 41 +++++++++++++++++----- .../test/extensions/compliant_forwarder.ts | 4 +-- packages/types/src/index.ts | 7 ++-- 3 files changed, 39 insertions(+), 13 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol index 71c124292..d33f4f398 100644 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol @@ -229,8 +229,18 @@ contract CompliantForwarder is ExchangeSelectors{ case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo default { - // @TODO Revert with `Error("INVALID_OR_UNHANDLED_EXCHANGE_SELECTOR")` - revert(0, 100) + // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) + mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. + revert(0, 132) } ///// Validate Recorded Addresses ///// @@ -264,19 +274,34 @@ contract CompliantForwarder is ExchangeSelectors{ 0x20 // reserve space for return balance (0x20 bytes) ) if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_CHECK_FAILED")` + // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) + mstore(0x60, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. revert(0, 100) } // Revert if balance not held let addressBalance := mload(freeMemPtr) if eq(addressBalance, 0) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000002541545f4c454153545f4f4e455f414444524553535f4841535f5a4552) - mstore(0x60, 0x4f5f42414c414e43450000000000000000000000000000000000000000000000) - revert(0, 109) + mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) + mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. + revert(0, 132) } } diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts index 8fa811936..196167264 100644 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ b/packages/contracts/test/extensions/compliant_forwarder.ts @@ -303,7 +303,7 @@ describe.only(ContractName.CompliantForwarder, () => { compliantSignedFillOrderTx.data, compliantSignedFillOrderTx.signature, ), - RevertReason.AtLeastOneAddressHasZeroBalance + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { @@ -331,7 +331,7 @@ describe.only(ContractName.CompliantForwarder, () => { signedFillOrderTx.data, signedFillOrderTx.signature, ), - RevertReason.AtLeastOneAddressHasZeroBalance + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); }); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 022b24e70..4d5b6e1f2 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -243,9 +243,10 @@ export enum RevertReason { AuctionNotStarted = 'AUCTION_NOT_STARTED', AuctionInvalidBeginTime = 'INVALID_BEGIN_TIME', InvalidAssetData = 'INVALID_ASSET_DATA', - MakerUnverified = 'MAKER_UNVERIFED', - TakerUnverified = 'TAKER_UNVERIFIED', - AtLeastOneAddressHasZeroBalance = 'AT_LEAST_ONE_ADDRESS_HAS_ZERO_BALANCE', + // Balance Threshold Filter + InvalidOrBlockedExchangeSelector = 'INVALID_OR_BLOCKED_EXCHANGE_SELECTOR', + BalanceQueryFailed = 'BALANCE_QUERY_FAILED', + AtLeastOneAddressDoesNotMeetBalanceThreshold= 'AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD', } export enum StatusCodes { -- cgit v1.2.3 From 1cdd82178ff630827095e778a222fafa4161969e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 15:23:25 -0800 Subject: ComplianceForwarder renamed to BalanceThresholdFilter --- .../BalanceThresholdFilter.sol | 323 ++++++++++++++++++ .../interfaces/IThresholdAsset.sol | 30 ++ .../CompliantForwarder/CompliantForwarder.sol | 323 ------------------ .../test/extensions/balance_threshold_filter.ts | 366 +++++++++++++++++++++ .../test/extensions/compliant_forwarder.ts | 366 --------------------- 5 files changed, 719 insertions(+), 689 deletions(-) create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol delete mode 100644 packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol create mode 100644 packages/contracts/test/extensions/balance_threshold_filter.ts delete mode 100644 packages/contracts/test/extensions/compliant_forwarder.ts (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol new file mode 100644 index 000000000..93b63eb52 --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -0,0 +1,323 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../protocol/Exchange/interfaces/IExchange.sol"; +import "../../utils/LibBytes/LibBytes.sol"; +import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; +import "./interfaces/IThresholdAsset.sol"; + +contract BalanceThresholdFilter is ExchangeSelectors { + + using LibBytes for bytes; + + IExchange internal EXCHANGE; + IThresholdAsset internal THRESHOLD_ASSET; + + event ValidatedAddresses ( + address[] addresses + ); + + constructor(address exchange, address thresholdAsset) + public + { + EXCHANGE = IExchange(exchange); + THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); + } + + function executeTransaction( + uint256 salt, + address signerAddress, + bytes signedExchangeTransaction, + bytes signature + ) + external + { + // Addresses that are validated below. + address[] memory validatedAddresses; + + /** + * Do not add variables after this point. + * The assembly block may overwrite their values. + */ + + // Validate addresses + assembly { + /** + * Emulates the `calldataload` opcode on the embedded Exchange calldata, + * which is accessed through `signedExchangeTransaction`. + * @param offset - Offset into the Exchange calldata. + * @return value - Corresponding 32 byte value stored at `offset`. + */ + function exchangeCalldataload(offset) -> value { + // Pointer to exchange transaction + // 0x04 for calldata selector + // 0x40 to access `signedExchangeTransaction`, which is the third parameter + let exchangeTxPtr := calldataload(0x44) + + // Offset into Exchange calldata + // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. + // 0x04 for calldata selector + // 0x20 for length field of `signedExchangeTransaction` + let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeCalldataOffset) + } + + /** + * Convenience function that skips the 4 byte selector when loading + * from the embedded Exchange calldata. + * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + * @return value - Corresponding 32 byte value stored at `offset` + 4. + */ + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + + /** + * A running list is maintained of addresses to validate. + * This function records an address in this array. + * @param addressToValidate - Address to record for validation. + * @note - Variables are scoped but names are not, so we append + * underscores to names that share the global namespace. + */ + function recordAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory offset + let addressesToValidate_ := mload(0x40) + let nAddressesToValidate_ := mload(addressesToValidate_) + + // Increment length + nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) + mstore(addressesToValidate_, nAddressesToValidate_) + + // Append address to validate + let offset := mul(nAddressesToValidate_, 0x20) + mstore(add(addressesToValidate_, offset), addressToValidate) + } + + /** + * Extracts the maker address from an order stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records it in + * the running list of addresses to validate. + * @param orderParamIndex - Index of the order in the Exchange function's signature + */ + function recordMakerAddressFromOrder(orderParamIndex) { + let orderPtr := loadExchangeData(orderParamIndex) + let makerAddress := loadExchangeData(orderPtr) + recordAddressToValidate(makerAddress) + } + + /** + * Extracts the maker addresses from an array of orders stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records them in + * the running list of addresses to validate. + * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature + */ + function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { + let orderArrayPtr := loadExchangeData(0x0) + let orderArrayLength := loadExchangeData(orderArrayPtr) + let orderArrayElementPtr := add(orderArrayPtr, 0x20) + let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) + for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { + let orderPtr := loadExchangeData(orderPtrOffset) + let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) + recordAddressToValidate(makerAddress) + } + } + + /** + * Records address of signer in the running list of addresses to validate. + * @note: We cannot access `signerAddress` directly from within the asm function, + * so it is loaded from the calldata. + */ + function recordSignerAddress() { + // Load the signer address from calldata + // 0x04 for selector + // 0x20 to access `signerAddress`, which is the second parameter. + let signerAddress_ := calldataload(0x24) + recordAddressToValidate(signerAddress_) + } + + /** + * Records addresses to be validated when Exchange transaction is a batch fill variant. + * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + * Reference signature: (Order[],uint256[],bytes[]) + */ + function recordAddressesForBatchFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a fill order variant. + * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + * Reference signature: (Order,uint256,bytes) + */ + function recordAddressesForFillOrderVariant() { + // Record maker address from the order (param index 0) + // The signer is the taker for this order and must also be validated. + recordMakerAddressFromOrder(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a market fill variant. + * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + * Reference signature: (Order[],uint256,bytes[]) + */ + function recordAddressesForMarketFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is matchOrders. + * Reference signature: matchOrders(Order,Order) + */ + function recordAddressesForMatchOrders() { + // Record maker address from both orders (param indices 0 & 1). + // The signer is the taker and must also be validated. + recordMakerAddressFromOrder(0) + recordMakerAddressFromOrder(1) + recordSignerAddress() + } + + ///// Record Addresses to Validate ///// + + // Addresses needing validation depends on which Exchange function is being called. + // Step 1/2 Read the exchange function selector. + let exchangeFunctionSelector := and( + exchangeCalldataload(0x0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) + + // Step 2/2 Extract addresses to validate based on this selector. + // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors + switch exchangeFunctionSelector + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders + case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow + case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder + case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow + case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder + case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders + case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow + case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders + case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder + case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders + case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo + default { + // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) + mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. + revert(0, 132) + } + + ///// Validate Recorded Addresses ///// + + // Load from memory the addresses to validate + let addressesToValidate := mload(0x40) + let addressesToValidateLength := mload(addressesToValidate) + let addressesToValidateElementPtr := add(addressesToValidate, 0x20) + let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) + + // Set free memory pointer to after `addressesToValidate` array. + // This is to avoid corruption when making calls in the loop below. + let freeMemPtr := addressesToValidateElementEndPtr + mstore(0x40, freeMemPtr) + + // Validate addresses + let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) + for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { + // Construct calldata for `THRESHOLD_ASSET.balanceOf` + mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, freeMemPtr), mload(addressToValidate)) + + // call `THRESHOLD_ASSET.balanceOf` + let success := call( + gas, // forward all gas + thresholdAssetAddress, // call address of asset proxy + 0, // don't send any ETH + freeMemPtr, // pointer to start of input + 0x24, // length of input (one padded address) + freeMemPtr, // write output to next free memory offset + 0x20 // reserve space for return balance (0x20 bytes) + ) + if eq(success, 0) { + // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) + mstore(0x60, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. + revert(0, 100) + } + + // Revert if balance not held + let addressBalance := mload(freeMemPtr) + if eq(addressBalance, 0) { + // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) + mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. + revert(0, 132) + } + } + + // Record validated addresses + validatedAddresses := addressesToValidate + } + + ///// If we hit this point then all addresses are valid ///// + emit ValidatedAddresses(validatedAddresses); + + // All addresses are valid. Execute fillOrder. + EXCHANGE.executeTransaction( + salt, + signerAddress, + signedExchangeTransaction, + signature + ); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol new file mode 100644 index 000000000..61acaba0a --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol @@ -0,0 +1,30 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; + +contract IThresholdAsset { + + /// @param _owner The address from which the balance will be retrieved + /// @return Balance of owner + function balanceOf(address _owner) + external + view + returns (uint256); + +} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol b/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol deleted file mode 100644 index d33f4f398..000000000 --- a/packages/contracts/contracts/extensions/CompliantForwarder/CompliantForwarder.sol +++ /dev/null @@ -1,323 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "../../protocol/Exchange/interfaces/IExchange.sol"; -import "../../tokens/ERC721Token/IERC721Token.sol"; -import "../../utils/LibBytes/LibBytes.sol"; -import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; - -contract CompliantForwarder is ExchangeSelectors{ - - using LibBytes for bytes; - - IExchange internal EXCHANGE; - IERC721Token internal COMPLIANCE_TOKEN; - - event ValidatedAddresses ( - address[] addresses - ); - - constructor(address exchange, address complianceToken) - public - { - EXCHANGE = IExchange(exchange); - COMPLIANCE_TOKEN = IERC721Token(complianceToken); - } - - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external - { - // Addresses that are validated below. - address[] memory validatedAddresses; - - /** - * Do not add variables after this point. - * The assembly block may overwrite their values. - */ - - // Validate addresses - assembly { - /** - * Emulates the `calldataload` opcode on the embedded Exchange calldata, - * which is accessed through `signedExchangeTransaction`. - * @param offset - Offset into the Exchange calldata. - * @return value - Corresponding 32 byte value stored at `offset`. - */ - function exchangeCalldataload(offset) -> value { - // Pointer to exchange transaction - // 0x04 for calldata selector - // 0x40 to access `signedExchangeTransaction`, which is the third parameter - let exchangeTxPtr := calldataload(0x44) - - // Offset into Exchange calldata - // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. - // 0x04 for calldata selector - // 0x20 for length field of `signedExchangeTransaction` - let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeCalldataOffset) - } - - /** - * Convenience function that skips the 4 byte selector when loading - * from the embedded Exchange calldata. - * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - * @return value - Corresponding 32 byte value stored at `offset` + 4. - */ - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - - /** - * A running list is maintained of addresses to validate. - * This function records an address in this array. - * @param addressToValidate - Address to record for validation. - * @note - Variables are scoped but names are not, so we append - * underscores to names that share the global namespace. - */ - function recordAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory offset - let addressesToValidate_ := mload(0x40) - let nAddressesToValidate_ := mload(addressesToValidate_) - - // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) - mstore(addressesToValidate_, nAddressesToValidate_) - - // Append address to validate - let offset := mul(nAddressesToValidate_, 0x20) - mstore(add(addressesToValidate_, offset), addressToValidate) - } - - /** - * Extracts the maker address from an order stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records it in - * the running list of addresses to validate. - * @param orderParamIndex - Index of the order in the Exchange function's signature - */ - function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(orderParamIndex) - let makerAddress := loadExchangeData(orderPtr) - recordAddressToValidate(makerAddress) - } - - /** - * Extracts the maker addresses from an array of orders stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records them in - * the running list of addresses to validate. - * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - */ - function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(0x0) - let orderArrayLength := loadExchangeData(orderArrayPtr) - let orderArrayElementPtr := add(orderArrayPtr, 0x20) - let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) - for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) - recordAddressToValidate(makerAddress) - } - } - - /** - * Records address of signer in the running list of addresses to validate. - * @note: We cannot access `signerAddress` directly from within the asm function, - * so it is loaded from the calldata. - */ - function recordSignerAddress() { - // Load the signer address from calldata - // 0x04 for selector - // 0x20 to access `signerAddress`, which is the second parameter. - let signerAddress_ := calldataload(0x24) - recordAddressToValidate(signerAddress_) - } - - /** - * Records addresses to be validated when Exchange transaction is a batch fill variant. - * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - * Reference signature: (Order[],uint256[],bytes[]) - */ - function recordAddressesForBatchFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a fill order variant. - * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - * Reference signature: (Order,uint256,bytes) - */ - function recordAddressesForFillOrderVariant() { - // Record maker address from the order (param index 0) - // The signer is the taker for this order and must also be validated. - recordMakerAddressFromOrder(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a market fill variant. - * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - * Reference signature: (Order[],uint256,bytes[]) - */ - function recordAddressesForMarketFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is matchOrders. - * Reference signature: matchOrders(Order,Order) - */ - function recordAddressesForMatchOrders() { - // Record maker address from both orders (param indices 0 & 1). - // The signer is the taker and must also be validated. - recordMakerAddressFromOrder(0) - recordMakerAddressFromOrder(1) - recordSignerAddress() - } - - ///// Record Addresses to Validate ///// - - // Addresses needing validation depends on which Exchange function is being called. - // Step 1/2 Read the exchange function selector. - let exchangeFunctionSelector := and( - exchangeCalldataload(0x0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - - // Step 2/2 Extract addresses to validate based on this selector. - // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors - switch exchangeFunctionSelector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders - case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow - case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder - case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow - case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder - case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders - case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow - case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders - case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder - case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders - case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo - default { - // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) - mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. - revert(0, 132) - } - - ///// Validate Recorded Addresses ///// - - // Load from memory the addresses to validate - let addressesToValidate := mload(0x40) - let addressesToValidateLength := mload(addressesToValidate) - let addressesToValidateElementPtr := add(addressesToValidate, 0x20) - let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - - // Set free memory pointer to after `addressesToValidate` array. - // This is to avoid corruption when making calls in the loop below. - let freeMemPtr := addressesToValidateElementEndPtr - mstore(0x40, freeMemPtr) - - // Validate addresses - let complianceTokenAddress := sload(COMPLIANCE_TOKEN_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { - // Construct calldata for `COMPLIANCE_TOKEN.balanceOf` - mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, freeMemPtr), mload(addressToValidate)) - - // call `COMPLIANCE_TOKEN.balanceOf` - let success := call( - gas, // forward all gas - complianceTokenAddress, // call address of asset proxy - 0, // don't send any ETH - freeMemPtr, // pointer to start of input - 0x24, // length of input (one padded address) - freeMemPtr, // write output to next free memory offset - 0x20 // reserve space for return balance (0x20 bytes) - ) - if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) - mstore(0x60, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. - revert(0, 100) - } - - // Revert if balance not held - let addressBalance := mload(freeMemPtr) - if eq(addressBalance, 0) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) - mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. - revert(0, 132) - } - } - - // Record validated addresses - validatedAddresses := addressesToValidate - } - - ///// If we hit this point then all addresses are valid ///// - emit ValidatedAddresses(validatedAddresses); - - // All addresses are valid. Execute fillOrder. - EXCHANGE.executeTransaction( - salt, - signerAddress, - signedExchangeTransaction, - signature - ); - } -} \ No newline at end of file diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts new file mode 100644 index 000000000..50fd79439 --- /dev/null +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -0,0 +1,366 @@ +import { BlockchainLifecycle } from '@0x/dev-utils'; +import { assetDataUtils } from '@0x/order-utils'; +import { RevertReason, SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as chai from 'chai'; +import * as ethUtil from 'ethereumjs-util'; +import * as _ from 'lodash'; + +import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; +import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; +import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; + +import { artifacts } from '../../src/artifacts'; +import { + expectTransactionFailedAsync, + expectTransactionFailedWithoutReasonAsync, +} from '../utils/assertions'; +import { chaiSetup } from '../utils/chai_setup'; +import { constants } from '../utils/constants'; +import { ERC20Wrapper } from '../utils/erc20_wrapper'; +import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { OrderFactory } from '../utils/order_factory'; +import { orderUtils } from '../utils/order_utils'; +import { TransactionFactory } from '../utils/transaction_factory'; +import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; +import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; + +import { MethodAbi, AbiDefinition } from 'ethereum-types'; +import { AbiEncoder } from '@0x/utils'; +import { Method } from '@0x/utils/lib/src/abi_encoder'; +import { LogDecoder } from '../utils/log_decoder'; + +chaiSetup.configure(); +const expect = chai.expect; +const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); +const DECIMALS_DEFAULT = 18; + +describe.only(ContractName.BalanceThresholdFilter, () => { + let compliantMakerAddress: string; + let owner: string; + let compliantTakerAddress: string; + let feeRecipientAddress: string; + let nonCompliantAddress: string; + let defaultMakerAssetAddress: string; + let defaultTakerAssetAddress: string; + let zrxAssetData: string; + let zrxToken: DummyERC20TokenContract; + let exchangeInstance: ExchangeContract; + let exchangeWrapper: ExchangeWrapper; + + let orderFactory: OrderFactory; + let erc20Wrapper: ERC20Wrapper; + let erc20Balances: ERC20BalancesByOwner; + + let takerTransactionFactory: TransactionFactory; + let compliantSignedOrder: SignedOrder; + let compliantSignedFillOrderTx: SignedTransaction; + let noncompliantSignedFillOrderTx: SignedTransaction; + + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); + const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); + const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); + + let compliantForwarderInstance: BalanceThresholdFilterContract; + + before(async () => { + // Create accounts + await blockchainLifecycle.startAsync(); + const accounts = await web3Wrapper.getAvailableAddressesAsync(); + const usedAddresses = ([ + owner, + compliantMakerAddress, + compliantTakerAddress, + feeRecipientAddress, + nonCompliantAddress, + ] = accounts); + // Create wrappers + erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + // Deploy ERC20 tokens + const numDummyErc20ToDeploy = 3; + let erc20TokenA: DummyERC20TokenContract; + let erc20TokenB: DummyERC20TokenContract; + [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + numDummyErc20ToDeploy, + constants.DUMMY_TOKEN_DECIMALS, + ); + defaultMakerAssetAddress = erc20TokenA.address; + defaultTakerAssetAddress = erc20TokenB.address; + zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); + // Deploy Yes Token + const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( + artifacts.YesComplianceToken, + provider, + txDefaults, + ); + // Create proxies + const erc20Proxy = await erc20Wrapper.deployProxyAsync(); + await erc20Wrapper.setBalancesAndAllowancesAsync(); + // Deploy Exchange congtract + exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( + artifacts.Exchange, + provider, + txDefaults, + zrxAssetData, + ); + exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); + // Register proxies + await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); + await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { + from: owner, + }); + // Default order parameters + const defaultOrderParams = { + exchangeAddress: exchangeInstance.address, + makerAddress: compliantMakerAddress, + feeRecipientAddress, + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + makerAssetAmount, + takerAssetAmount, + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), + }; + const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; + orderFactory = new OrderFactory(privateKey, defaultOrderParams); + // Deploy Compliant Forwarder + compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + artifacts.BalanceThresholdFilter, + provider, + txDefaults, + exchangeInstance.address, + yesTokenInstance.address, + ); + /* + const compliantForwarderContract = new BalanceThresholdFilterContract( + compliantForwarderInstance.abi, + compliantForwarderInstance.address, + provider, + ); + forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); + */ + // Initialize Yes Token + await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); + const yesTokenName = 'YesToken'; + const yesTokenTicker = 'YEET'; + await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); + // Verify Maker / Taker + const addressesCanControlTheirToken = true; + const compliantMakerCountryCode = new BigNumber(519); + const compliantMakerYesMark = new BigNumber(1); + const compliantMakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( + compliantMakerAddress, + compliantMakerEntityId, + addressesCanControlTheirToken, + compliantMakerCountryCode, + [compliantMakerYesMark], + { from: owner }, + ); + const compliantTakerCountryCode = new BigNumber(519); + const compliantTakerYesMark = new BigNumber(1); + const compliantTakerEntityId = new BigNumber(2); + await yesTokenInstance.mint2.sendTransactionAsync( + compliantTakerAddress, + compliantTakerEntityId, + addressesCanControlTheirToken, + compliantTakerCountryCode, + [compliantTakerYesMark], + { from: owner }, + ); + // Create Valid/Invalid orders + const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; + takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); + compliantSignedOrder = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + }); + const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + compliantSignedOrder, + ); + const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + compliantSignedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( + compliantSignedOrderWithoutExchangeAddressData, + ); + + /* generate selectors for every exchange method + _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { + try { + const method = new Method(abiDefinition as MethodAbi); + console.log('\n', `// ${method.getDataItem().name}`); + console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); + console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); + } catch(e) { + _.noop(); + } + });*/ + }); + beforeEach(async () => { + await blockchainLifecycle.startAsync(); + }); + afterEach(async () => { + await blockchainLifecycle.revertAsync(); + }); + describe.only('fillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts when maker and taker are compliant', async () => { + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if the signed transaction is not intended for fillOrder', async () => { + // Create signed order without the fillOrder function selector + const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); + const selectorLengthInBytes = 4; + const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); + const badSelector = '0x00000000'; + const badSelectorBuf = ethUtil.toBuffer(badSelector); + const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); + const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + txDataBufWithBadSelectorHex, + compliantSignedFillOrderTx.signature, + )); + }); + it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { + // Create signed order with incorrect senderAddress + const notBalanceThresholdFilterAddress = zrxToken.address; + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: notBalanceThresholdFilterAddress, + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + )); + }); + it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + nonCompliantAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadMakerAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + }); + + describe('batchFillOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts when maker and taker are compliant', async () => { + let order2 = _.cloneDeep(compliantSignedOrder); + order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; + const orders = [compliantSignedOrder, order2]; + const fillAmounts = [new BigNumber(4), new BigNumber(4)]; + const signatures = ["0xabcd", "0xabcd"]; + const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); + console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + exchangeCalldata, + compliantSignedFillOrderTx.signature, + ); + const decoder = new LogDecoder(web3Wrapper); + const tx = await decoder.getTxWithDecodedLogsAsync(txHash); + console.log(JSON.stringify(tx, null, 4)); + }); + }); +}); +// tslint:disable:max-file-line-count +// tslint:enable:no-unnecessary-type-assertion diff --git a/packages/contracts/test/extensions/compliant_forwarder.ts b/packages/contracts/test/extensions/compliant_forwarder.ts deleted file mode 100644 index 196167264..000000000 --- a/packages/contracts/test/extensions/compliant_forwarder.ts +++ /dev/null @@ -1,366 +0,0 @@ -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; - -import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { CompliantForwarderContract } from '../../generated-wrappers/compliant_forwarder'; -import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; - -import { artifacts } from '../../src/artifacts'; -import { - expectTransactionFailedAsync, - expectTransactionFailedWithoutReasonAsync, -} from '../utils/assertions'; -import { chaiSetup } from '../utils/chai_setup'; -import { constants } from '../utils/constants'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { OrderFactory } from '../utils/order_factory'; -import { orderUtils } from '../utils/order_utils'; -import { TransactionFactory } from '../utils/transaction_factory'; -import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; -import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; - -import { MethodAbi, AbiDefinition } from 'ethereum-types'; -import { AbiEncoder } from '@0x/utils'; -import { Method } from '@0x/utils/lib/src/abi_encoder'; -import { LogDecoder } from '../utils/log_decoder'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -const DECIMALS_DEFAULT = 18; - -describe.only(ContractName.CompliantForwarder, () => { - let compliantMakerAddress: string; - let owner: string; - let compliantTakerAddress: string; - let feeRecipientAddress: string; - let nonCompliantAddress: string; - let defaultMakerAssetAddress: string; - let defaultTakerAssetAddress: string; - let zrxAssetData: string; - let zrxToken: DummyERC20TokenContract; - let exchangeInstance: ExchangeContract; - let exchangeWrapper: ExchangeWrapper; - - let orderFactory: OrderFactory; - let erc20Wrapper: ERC20Wrapper; - let erc20Balances: ERC20BalancesByOwner; - - let takerTransactionFactory: TransactionFactory; - let compliantSignedOrder: SignedOrder; - let compliantSignedFillOrderTx: SignedTransaction; - let noncompliantSignedFillOrderTx: SignedTransaction; - - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); - const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - - let compliantForwarderInstance: CompliantForwarderContract; - - before(async () => { - // Create accounts - await blockchainLifecycle.startAsync(); - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([ - owner, - compliantMakerAddress, - compliantTakerAddress, - feeRecipientAddress, - nonCompliantAddress, - ] = accounts); - // Create wrappers - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - // Deploy ERC20 tokens - const numDummyErc20ToDeploy = 3; - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - defaultMakerAssetAddress = erc20TokenA.address; - defaultTakerAssetAddress = erc20TokenB.address; - zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Deploy Yes Token - const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( - artifacts.YesComplianceToken, - provider, - txDefaults, - ); - // Create proxies - const erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy Exchange congtract - exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); - // Register proxies - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); - // Default order parameters - const defaultOrderParams = { - exchangeAddress: exchangeInstance.address, - makerAddress: compliantMakerAddress, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount, - takerAssetAmount, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), - }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - // Deploy Compliant Forwarder - compliantForwarderInstance = await CompliantForwarderContract.deployFrom0xArtifactAsync( - artifacts.CompliantForwarder, - provider, - txDefaults, - exchangeInstance.address, - yesTokenInstance.address, - ); - /* - const compliantForwarderContract = new CompliantForwarderContract( - compliantForwarderInstance.abi, - compliantForwarderInstance.address, - provider, - ); - forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); - */ - // Initialize Yes Token - await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); - const yesTokenName = 'YesToken'; - const yesTokenTicker = 'YEET'; - await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); - // Verify Maker / Taker - const addressesCanControlTheirToken = true; - const compliantMakerCountryCode = new BigNumber(519); - const compliantMakerYesMark = new BigNumber(1); - const compliantMakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantMakerAddress, - compliantMakerEntityId, - addressesCanControlTheirToken, - compliantMakerCountryCode, - [compliantMakerYesMark], - { from: owner }, - ); - const compliantTakerCountryCode = new BigNumber(519); - const compliantTakerYesMark = new BigNumber(1); - const compliantTakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantTakerAddress, - compliantTakerEntityId, - addressesCanControlTheirToken, - compliantTakerCountryCode, - [compliantTakerYesMark], - { from: owner }, - ); - // Create Valid/Invalid orders - const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; - takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); - compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - }); - const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - compliantSignedOrder, - ); - const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - compliantSignedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( - compliantSignedOrderWithoutExchangeAddressData, - ); - - /* generate selectors for every exchange method - _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { - try { - const method = new Method(abiDefinition as MethodAbi); - console.log('\n', `// ${method.getDataItem().name}`); - console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); - console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); - } catch(e) { - _.noop(); - } - });*/ - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - describe.only('fillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, - ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if the signed transaction is not intended for fillOrder', async () => { - // Create signed order without the fillOrder function selector - const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); - const selectorLengthInBytes = 4; - const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); - const badSelector = '0x00000000'; - const badSelectorBuf = ethUtil.toBuffer(badSelector); - const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); - const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - txDataBufWithBadSelectorHex, - compliantSignedFillOrderTx.signature, - )); - }); - it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { - // Create signed order with incorrect senderAddress - const notCompliantForwarderAddress = zrxToken.address; - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: notCompliantForwarderAddress, - }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - )); - }); - it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { - return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - nonCompliantAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadMakerAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder - return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('batchFillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - let order2 = _.cloneDeep(compliantSignedOrder); - order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; - const orders = [compliantSignedOrder, order2]; - const fillAmounts = [new BigNumber(4), new BigNumber(4)]; - const signatures = ["0xabcd", "0xabcd"]; - const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); - console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - exchangeCalldata, - compliantSignedFillOrderTx.signature, - ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion -- cgit v1.2.3 From 2be9b1ff082ff2753798cc0c218ab1829949661f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 15:47:45 -0800 Subject: Updated Balance Threshold Filter to use mixin pattern --- .../BalanceThresholdFilter.sol | 292 +------------------ .../MixinBalanceThresholdFilterCore.sol | 312 +++++++++++++++++++++ .../mixins/MBalanceThresholdFilterCore.sol | 44 +++ 3 files changed, 358 insertions(+), 290 deletions(-) create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol create mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol index 93b63eb52..ce3e925fe 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -20,20 +20,11 @@ pragma solidity 0.4.24; pragma experimental ABIEncoderV2; import "../../protocol/Exchange/interfaces/IExchange.sol"; -import "../../utils/LibBytes/LibBytes.sol"; -import "../../utils/ExchangeSelectors/ExchangeSelectors.sol"; import "./interfaces/IThresholdAsset.sol"; +import "./MixinBalanceThresholdFilterCore.sol"; -contract BalanceThresholdFilter is ExchangeSelectors { - using LibBytes for bytes; - - IExchange internal EXCHANGE; - IThresholdAsset internal THRESHOLD_ASSET; - - event ValidatedAddresses ( - address[] addresses - ); +contract BalanceThresholdFilter is MixinBalanceThresholdFilterCore { constructor(address exchange, address thresholdAsset) public @@ -41,283 +32,4 @@ contract BalanceThresholdFilter is ExchangeSelectors { EXCHANGE = IExchange(exchange); THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); } - - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external - { - // Addresses that are validated below. - address[] memory validatedAddresses; - - /** - * Do not add variables after this point. - * The assembly block may overwrite their values. - */ - - // Validate addresses - assembly { - /** - * Emulates the `calldataload` opcode on the embedded Exchange calldata, - * which is accessed through `signedExchangeTransaction`. - * @param offset - Offset into the Exchange calldata. - * @return value - Corresponding 32 byte value stored at `offset`. - */ - function exchangeCalldataload(offset) -> value { - // Pointer to exchange transaction - // 0x04 for calldata selector - // 0x40 to access `signedExchangeTransaction`, which is the third parameter - let exchangeTxPtr := calldataload(0x44) - - // Offset into Exchange calldata - // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. - // 0x04 for calldata selector - // 0x20 for length field of `signedExchangeTransaction` - let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeCalldataOffset) - } - - /** - * Convenience function that skips the 4 byte selector when loading - * from the embedded Exchange calldata. - * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - * @return value - Corresponding 32 byte value stored at `offset` + 4. - */ - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - - /** - * A running list is maintained of addresses to validate. - * This function records an address in this array. - * @param addressToValidate - Address to record for validation. - * @note - Variables are scoped but names are not, so we append - * underscores to names that share the global namespace. - */ - function recordAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory offset - let addressesToValidate_ := mload(0x40) - let nAddressesToValidate_ := mload(addressesToValidate_) - - // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) - mstore(addressesToValidate_, nAddressesToValidate_) - - // Append address to validate - let offset := mul(nAddressesToValidate_, 0x20) - mstore(add(addressesToValidate_, offset), addressToValidate) - } - - /** - * Extracts the maker address from an order stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records it in - * the running list of addresses to validate. - * @param orderParamIndex - Index of the order in the Exchange function's signature - */ - function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(orderParamIndex) - let makerAddress := loadExchangeData(orderPtr) - recordAddressToValidate(makerAddress) - } - - /** - * Extracts the maker addresses from an array of orders stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records them in - * the running list of addresses to validate. - * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - */ - function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(0x0) - let orderArrayLength := loadExchangeData(orderArrayPtr) - let orderArrayElementPtr := add(orderArrayPtr, 0x20) - let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) - for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) - recordAddressToValidate(makerAddress) - } - } - - /** - * Records address of signer in the running list of addresses to validate. - * @note: We cannot access `signerAddress` directly from within the asm function, - * so it is loaded from the calldata. - */ - function recordSignerAddress() { - // Load the signer address from calldata - // 0x04 for selector - // 0x20 to access `signerAddress`, which is the second parameter. - let signerAddress_ := calldataload(0x24) - recordAddressToValidate(signerAddress_) - } - - /** - * Records addresses to be validated when Exchange transaction is a batch fill variant. - * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - * Reference signature: (Order[],uint256[],bytes[]) - */ - function recordAddressesForBatchFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a fill order variant. - * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - * Reference signature: (Order,uint256,bytes) - */ - function recordAddressesForFillOrderVariant() { - // Record maker address from the order (param index 0) - // The signer is the taker for this order and must also be validated. - recordMakerAddressFromOrder(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is a market fill variant. - * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - * Reference signature: (Order[],uint256,bytes[]) - */ - function recordAddressesForMarketFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /** - * Records addresses to be validated when Exchange transaction is matchOrders. - * Reference signature: matchOrders(Order,Order) - */ - function recordAddressesForMatchOrders() { - // Record maker address from both orders (param indices 0 & 1). - // The signer is the taker and must also be validated. - recordMakerAddressFromOrder(0) - recordMakerAddressFromOrder(1) - recordSignerAddress() - } - - ///// Record Addresses to Validate ///// - - // Addresses needing validation depends on which Exchange function is being called. - // Step 1/2 Read the exchange function selector. - let exchangeFunctionSelector := and( - exchangeCalldataload(0x0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - - // Step 2/2 Extract addresses to validate based on this selector. - // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors - switch exchangeFunctionSelector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders - case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow - case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder - case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow - case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder - case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders - case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow - case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders - case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder - case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders - case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo - default { - // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) - mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. - revert(0, 132) - } - - ///// Validate Recorded Addresses ///// - - // Load from memory the addresses to validate - let addressesToValidate := mload(0x40) - let addressesToValidateLength := mload(addressesToValidate) - let addressesToValidateElementPtr := add(addressesToValidate, 0x20) - let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - - // Set free memory pointer to after `addressesToValidate` array. - // This is to avoid corruption when making calls in the loop below. - let freeMemPtr := addressesToValidateElementEndPtr - mstore(0x40, freeMemPtr) - - // Validate addresses - let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { - // Construct calldata for `THRESHOLD_ASSET.balanceOf` - mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, freeMemPtr), mload(addressToValidate)) - - // call `THRESHOLD_ASSET.balanceOf` - let success := call( - gas, // forward all gas - thresholdAssetAddress, // call address of asset proxy - 0, // don't send any ETH - freeMemPtr, // pointer to start of input - 0x24, // length of input (one padded address) - freeMemPtr, // write output to next free memory offset - 0x20 // reserve space for return balance (0x20 bytes) - ) - if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) - mstore(0x60, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. - revert(0, 100) - } - - // Revert if balance not held - let addressBalance := mload(freeMemPtr) - if eq(addressBalance, 0) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) - mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. - revert(0, 132) - } - } - - // Record validated addresses - validatedAddresses := addressesToValidate - } - - ///// If we hit this point then all addresses are valid ///// - emit ValidatedAddresses(validatedAddresses); - - // All addresses are valid. Execute fillOrder. - EXCHANGE.executeTransaction( - salt, - signerAddress, - signedExchangeTransaction, - signature - ); - } } \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol new file mode 100644 index 000000000..dbb352f90 --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -0,0 +1,312 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "./mixins/MBalanceThresholdFilterCore.sol"; + + +contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { + + function executeTransaction( + uint256 salt, + address signerAddress, + bytes signedExchangeTransaction, + bytes signature + ) + external + { + // Validate addresses. + validateBalanceThresholdsOrRevert(); + + // All addresses are valid. Execute fillOrder. + EXCHANGE.executeTransaction( + salt, + signerAddress, + signedExchangeTransaction, + signature + ); + } + + function validateBalanceThresholdsOrRevert() + internal + { + // Addresses that are validated below. + address[] memory validatedAddresses; + + /** + * Do not add variables after this point. + * The assembly block may overwrite their values. + */ + + // Validate addresses + assembly { + /** + * Emulates the `calldataload` opcode on the embedded Exchange calldata, + * which is accessed through `signedExchangeTransaction`. + * @param offset - Offset into the Exchange calldata. + * @return value - Corresponding 32 byte value stored at `offset`. + */ + function exchangeCalldataload(offset) -> value { + // Pointer to exchange transaction + // 0x04 for calldata selector + // 0x40 to access `signedExchangeTransaction`, which is the third parameter + let exchangeTxPtr := calldataload(0x44) + + // Offset into Exchange calldata + // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. + // 0x04 for calldata selector + // 0x20 for length field of `signedExchangeTransaction` + let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) + value := calldataload(exchangeCalldataOffset) + } + + /** + * Convenience function that skips the 4 byte selector when loading + * from the embedded Exchange calldata. + * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + * @return value - Corresponding 32 byte value stored at `offset` + 4. + */ + function loadExchangeData(offset) -> value { + value := exchangeCalldataload(add(offset, 0x4)) + } + + /** + * A running list is maintained of addresses to validate. + * This function records an address in this array. + * @param addressToValidate - Address to record for validation. + * @note - Variables are scoped but names are not, so we append + * underscores to names that share the global namespace. + */ + function recordAddressToValidate(addressToValidate) { + // Compute `addressesToValidate` memory offset + let addressesToValidate_ := mload(0x40) + let nAddressesToValidate_ := mload(addressesToValidate_) + + // Increment length + nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) + mstore(addressesToValidate_, nAddressesToValidate_) + + // Append address to validate + let offset := mul(nAddressesToValidate_, 0x20) + mstore(add(addressesToValidate_, offset), addressToValidate) + } + + /** + * Extracts the maker address from an order stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records it in + * the running list of addresses to validate. + * @param orderParamIndex - Index of the order in the Exchange function's signature + */ + function recordMakerAddressFromOrder(orderParamIndex) { + let orderPtr := loadExchangeData(orderParamIndex) + let makerAddress := loadExchangeData(orderPtr) + recordAddressToValidate(makerAddress) + } + + /** + * Extracts the maker addresses from an array of orders stored in the Exchange calldata + * (which is embedded in `signedExchangeTransaction`), and records them in + * the running list of addresses to validate. + * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature + */ + function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { + let orderArrayPtr := loadExchangeData(orderArrayParamIndex) + let orderArrayLength := loadExchangeData(orderArrayPtr) + let orderArrayElementPtr := add(orderArrayPtr, 0x20) + let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) + for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { + let orderPtr := loadExchangeData(orderPtrOffset) + let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) + recordAddressToValidate(makerAddress) + } + } + + /** + * Records address of signer in the running list of addresses to validate. + * @note: We cannot access `signerAddress` directly from within the asm function, + * so it is loaded from the calldata. + */ + function recordSignerAddress() { + // Load the signer address from calldata + // 0x04 for selector + // 0x20 to access `signerAddress`, which is the second parameter. + let signerAddress_ := calldataload(0x24) + recordAddressToValidate(signerAddress_) + } + + /** + * Records addresses to be validated when Exchange transaction is a batch fill variant. + * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + * Reference signature: (Order[],uint256[],bytes[]) + */ + function recordAddressesForBatchFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a fill order variant. + * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + * Reference signature: (Order,uint256,bytes) + */ + function recordAddressesForFillOrderVariant() { + // Record maker address from the order (param index 0) + // The signer is the taker for this order and must also be validated. + recordMakerAddressFromOrder(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is a market fill variant. + * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + * Reference signature: (Order[],uint256,bytes[]) + */ + function recordAddressesForMarketFillVariant() { + // Record maker addresses from order array (parameter index 0) + // The signer is the taker for these orders and must also be validated. + recordMakerAddressesFromOrderArray(0) + recordSignerAddress() + } + + /** + * Records addresses to be validated when Exchange transaction is matchOrders. + * Reference signature: matchOrders(Order,Order) + */ + function recordAddressesForMatchOrders() { + // Record maker address from both orders (param indices 0 & 1). + // The signer is the taker and must also be validated. + recordMakerAddressFromOrder(0) + recordMakerAddressFromOrder(1) + recordSignerAddress() + } + + ///// Record Addresses to Validate ///// + + // Addresses needing validation depends on which Exchange function is being called. + // Step 1/2 Read the exchange function selector. + let exchangeFunctionSelector := and( + exchangeCalldataload(0x0), + 0xffffffff00000000000000000000000000000000000000000000000000000000 + ) + + // Step 2/2 Extract addresses to validate based on this selector. + // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors + switch exchangeFunctionSelector + case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders + case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow + case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders + case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder + case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow + case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder + case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders + case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow + case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders + case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow + case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders + case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder + case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders + case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo + default { + // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) + mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. + revert(0, 132) + } + + ///// Validate Recorded Addresses ///// + + // Load from memory the addresses to validate + let addressesToValidate := mload(0x40) + let addressesToValidateLength := mload(addressesToValidate) + let addressesToValidateElementPtr := add(addressesToValidate, 0x20) + let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) + + // Set free memory pointer to after `addressesToValidate` array. + // This is to avoid corruption when making calls in the loop below. + let freeMemPtr := addressesToValidateElementEndPtr + mstore(0x40, freeMemPtr) + + // Validate addresses + let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) + for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { + // Construct calldata for `THRESHOLD_ASSET.balanceOf` + mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) + mstore(add(4, freeMemPtr), mload(addressToValidate)) + + // call `THRESHOLD_ASSET.balanceOf` + let success := call( + gas, // forward all gas + thresholdAssetAddress, // call address of asset proxy + 0, // don't send any ETH + freeMemPtr, // pointer to start of input + 0x24, // length of input (one padded address) + freeMemPtr, // write output to next free memory offset + 0x20 // reserve space for return balance (0x20 bytes) + ) + if eq(success, 0) { + // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) + mstore(0x60, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. + revert(0, 100) + } + + // Revert if balance not held + let addressBalance := mload(freeMemPtr) + if eq(addressBalance, 0) { + // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` + mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) + mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) + mstore(0x80, 0x00000000) + // Revert length calculation: + // 4 -- error selector + // 32 -- offset to string + // 32 -- string length field + // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. + revert(0, 132) + } + } + + // Record validated addresses + validatedAddresses := addressesToValidate + } + + ///// If we hit this point then all addresses are valid ///// + emit ValidatedAddresses(validatedAddresses); + } +} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol new file mode 100644 index 000000000..1de415f27 --- /dev/null +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -0,0 +1,44 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity 0.4.24; +pragma experimental ABIEncoderV2; + +import "../../../protocol/Exchange/interfaces/IExchange.sol"; +import "../interfaces/IThresholdAsset.sol"; + + +contract MBalanceThresholdFilterCore { + + IExchange internal EXCHANGE; + IThresholdAsset internal THRESHOLD_ASSET; + + event ValidatedAddresses ( + address[] addresses + ); + + function executeTransaction( + uint256 salt, + address signerAddress, + bytes signedExchangeTransaction, + bytes signature + ) + external; + + function validateBalanceThresholdsOrRevert() internal; +} \ No newline at end of file -- cgit v1.2.3 From 58a382d9b69eedc5f618c8d29d130151ba0ed740 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 16:07:45 -0800 Subject: Function Documentation --- .../MixinBalanceThresholdFilterCore.sol | 126 +++++++++++---------- .../mixins/MBalanceThresholdFilterCore.sol | 29 +++++ 2 files changed, 96 insertions(+), 59 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index dbb352f90..fe4a50a29 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -24,6 +24,28 @@ import "./mixins/MBalanceThresholdFilterCore.sol"; contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { + /// @dev Executes an Exchange transaction iff the maker and taker meet + /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR + /// the exchange function is a cancellation. + /// Supported Exchange functions: + /// - batchFillOrdersNoThrow + /// - batchFillOrKillOrders + /// - fillOrder + /// - fillOrderNoThrow + /// - fillOrKillOrder + /// - marketBuyOrders + /// - marketBuyOrdersNoThrow + /// - marketSellOrders + /// - marketSellOrdersNoThrow + /// - matchOrders + /// - cancelOrder + /// - batchCancelOrders + /// - cancelOrdersUpTo + /// Trying to call any other exchange function will throw. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param signedExchangeTransaction AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. function executeTransaction( uint256 salt, address signerAddress, @@ -44,25 +66,29 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { ); } + /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` + /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold + /// then this function will revert. Which addresses are validated depends on + /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). + /// No parameters are taken as this function reads arguments directly from calldata, to save gas. + /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all + /// of the addresses whose balance thresholds it checked. function validateBalanceThresholdsOrRevert() internal { // Addresses that are validated below. address[] memory validatedAddresses; - /** - * Do not add variables after this point. - * The assembly block may overwrite their values. - */ + + ///// Do not add variables after this point. ///// + ///// The assembly block may overwrite their values. ///// // Validate addresses assembly { - /** - * Emulates the `calldataload` opcode on the embedded Exchange calldata, - * which is accessed through `signedExchangeTransaction`. - * @param offset - Offset into the Exchange calldata. - * @return value - Corresponding 32 byte value stored at `offset`. - */ + /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata, + /// which is accessed through `signedExchangeTransaction`. + /// @param offset - Offset into the Exchange calldata. + /// @return value - Corresponding 32 byte value stored at `offset`. function exchangeCalldataload(offset) -> value { // Pointer to exchange transaction // 0x04 for calldata selector @@ -77,23 +103,19 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { value := calldataload(exchangeCalldataOffset) } - /** - * Convenience function that skips the 4 byte selector when loading - * from the embedded Exchange calldata. - * @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - * @return value - Corresponding 32 byte value stored at `offset` + 4. - */ + /// @dev Convenience function that skips the 4 byte selector when loading + /// from the embedded Exchange calldata. + /// @param offset - Offset into the Exchange calldata (minus the 4 byte selector) + /// @return value - Corresponding 32 byte value stored at `offset` + 4. function loadExchangeData(offset) -> value { value := exchangeCalldataload(add(offset, 0x4)) } - /** - * A running list is maintained of addresses to validate. - * This function records an address in this array. - * @param addressToValidate - Address to record for validation. - * @note - Variables are scoped but names are not, so we append - * underscores to names that share the global namespace. - */ + /// @dev A running list is maintained of addresses to validate. + /// This function records an address in this array. + /// @param addressToValidate - Address to record for validation. + /// @note - Variables are scoped but names are not, so we append + /// underscores to names that share the global namespace. function recordAddressToValidate(addressToValidate) { // Compute `addressesToValidate` memory offset let addressesToValidate_ := mload(0x40) @@ -108,24 +130,20 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { mstore(add(addressesToValidate_, offset), addressToValidate) } - /** - * Extracts the maker address from an order stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records it in - * the running list of addresses to validate. - * @param orderParamIndex - Index of the order in the Exchange function's signature - */ + /// @dev Extracts the maker address from an order stored in the Exchange calldata + /// (which is embedded in `signedExchangeTransaction`), and records it in + /// the running list of addresses to validate. + /// @param orderParamIndex - Index of the order in the Exchange function's signature function recordMakerAddressFromOrder(orderParamIndex) { let orderPtr := loadExchangeData(orderParamIndex) let makerAddress := loadExchangeData(orderPtr) recordAddressToValidate(makerAddress) } - /** - * Extracts the maker addresses from an array of orders stored in the Exchange calldata - * (which is embedded in `signedExchangeTransaction`), and records them in - * the running list of addresses to validate. - * @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - */ + /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata + /// (which is embedded in `signedExchangeTransaction`), and records them in + /// the running list of addresses to validate. + /// @param orderArrayParamIndex - Index of the order array in the Exchange function's signature function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { let orderArrayPtr := loadExchangeData(orderArrayParamIndex) let orderArrayLength := loadExchangeData(orderArrayPtr) @@ -138,11 +156,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { } } - /** - * Records address of signer in the running list of addresses to validate. - * @note: We cannot access `signerAddress` directly from within the asm function, - * so it is loaded from the calldata. - */ + /// @dev Records address of signer in the running list of addresses to validate. + /// @note: We cannot access `signerAddress` directly from within the asm function, + /// so it is loaded from the calldata. function recordSignerAddress() { // Load the signer address from calldata // 0x04 for selector @@ -151,11 +167,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordAddressToValidate(signerAddress_) } - /** - * Records addresses to be validated when Exchange transaction is a batch fill variant. - * This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - * Reference signature: (Order[],uint256[],bytes[]) - */ + /// @dev Records addresses to be validated when Exchange transaction is a batch fill variant. + /// This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow + /// Reference signature: (Order[],uint256[],bytes[]) function recordAddressesForBatchFillVariant() { // Record maker addresses from order array (parameter index 0) // The signer is the taker for these orders and must also be validated. @@ -163,11 +177,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordSignerAddress() } - /** - * Records addresses to be validated when Exchange transaction is a fill order variant. - * This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - * Reference signature: (Order,uint256,bytes) - */ + /// @dev Records addresses to be validated when Exchange transaction is a fill order variant. + /// This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow + /// Reference signature: (Order,uint256,bytes) function recordAddressesForFillOrderVariant() { // Record maker address from the order (param index 0) // The signer is the taker for this order and must also be validated. @@ -175,11 +187,9 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordSignerAddress() } - /** - * Records addresses to be validated when Exchange transaction is a market fill variant. - * This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - * Reference signature: (Order[],uint256,bytes[]) - */ + /// @dev Records addresses to be validated when Exchange transaction is a market fill variant. + /// This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow + /// Reference signature: (Order[],uint256,bytes[]) function recordAddressesForMarketFillVariant() { // Record maker addresses from order array (parameter index 0) // The signer is the taker for these orders and must also be validated. @@ -187,10 +197,8 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { recordSignerAddress() } - /** - * Records addresses to be validated when Exchange transaction is matchOrders. - * Reference signature: matchOrders(Order,Order) - */ + /// @dev Records addresses to be validated when Exchange transaction is matchOrders. + /// Reference signature: matchOrders(Order,Order) function recordAddressesForMatchOrders() { // Record maker address from both orders (param indices 0 & 1). // The signer is the taker and must also be validated. diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index 1de415f27..ecebaa31b 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -32,6 +32,28 @@ contract MBalanceThresholdFilterCore { address[] addresses ); + /// @dev Executes an Exchange transaction iff the maker and taker meet + /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR + /// the exchange function is a cancellation. + /// Supported Exchange functions: + /// - batchFillOrdersNoThrow + /// - batchFillOrKillOrders + /// - fillOrder + /// - fillOrderNoThrow + /// - fillOrKillOrder + /// - marketBuyOrders + /// - marketBuyOrdersNoThrow + /// - marketSellOrders + /// - marketSellOrdersNoThrow + /// - matchOrders + /// - cancelOrder + /// - batchCancelOrders + /// - cancelOrdersUpTo + /// Trying to call any other exchange function will throw. + /// @param salt Arbitrary number to ensure uniqueness of transaction hash. + /// @param signerAddress Address of transaction signer. + /// @param signedExchangeTransaction AbiV2 encoded calldata. + /// @param signature Proof of signer transaction by signer. function executeTransaction( uint256 salt, address signerAddress, @@ -40,5 +62,12 @@ contract MBalanceThresholdFilterCore { ) external; + /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` + /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold + /// then this function will revert. Which addresses are validated depends on + /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). + /// No parameters are taken as this function reads arguments directly from calldata, to save gas. + /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all + /// of the addresses whose balance thresholds it checked. function validateBalanceThresholdsOrRevert() internal; } \ No newline at end of file -- cgit v1.2.3 From 14c97b3ec368904d2e072f4603ab41ea8458aebb Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 16:15:35 -0800 Subject: Include threshold balance in constructor of BalanceThresholdFilter contract --- .../BalanceThresholdFilter/BalanceThresholdFilter.sol | 11 ++++++++++- .../MixinBalanceThresholdFilterCore.sol | 3 ++- .../mixins/MBalanceThresholdFilterCore.sol | 7 +++++++ .../contracts/test/extensions/balance_threshold_filter.ts | 2 ++ 4 files changed, 21 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol index ce3e925fe..bf4a94509 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol @@ -26,10 +26,19 @@ import "./MixinBalanceThresholdFilterCore.sol"; contract BalanceThresholdFilter is MixinBalanceThresholdFilterCore { - constructor(address exchange, address thresholdAsset) + /// @dev Constructs BalanceThresholdFilter. + /// @param exchange Address of 0x exchange. + /// @param thresholdAsset The asset that must be held by makers/takers. + /// @param thresholdBalance The minimum balance of `thresholdAsset` that must be held by makers/takers. + constructor( + address exchange, + address thresholdAsset, + uint256 thresholdBalance + ) public { EXCHANGE = IExchange(exchange); THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); + THRESHOLD_BALANCE = thresholdBalance; } } \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index fe4a50a29..51b3b9736 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -263,6 +263,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Validate addresses let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) + let thresholdBalance := sload(THRESHOLD_BALANCE_slot) for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `THRESHOLD_ASSET.balanceOf` mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) @@ -294,7 +295,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Revert if balance not held let addressBalance := mload(freeMemPtr) - if eq(addressBalance, 0) { + if lt(addressBalance, thresholdBalance) { // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index ecebaa31b..046caecdd 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -25,9 +25,16 @@ import "../interfaces/IThresholdAsset.sol"; contract MBalanceThresholdFilterCore { + // Points to 0x exchange contract IExchange internal EXCHANGE; + + // The asset that must be held by makers/takers IThresholdAsset internal THRESHOLD_ASSET; + // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers + uint256 internal THRESHOLD_BALANCE; + + // Addresses that hold at least `THRESHOLD_BALANCE` of `THRESHOLD_ASSET` event ValidatedAddresses ( address[] addresses ); diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 50fd79439..db4fea77a 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -126,12 +126,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; orderFactory = new OrderFactory(privateKey, defaultOrderParams); // Deploy Compliant Forwarder + const erc721BalanceThreshold = new BigNumber(1); compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, exchangeInstance.address, yesTokenInstance.address, + erc721BalanceThreshold ); /* const compliantForwarderContract = new BalanceThresholdFilterContract( -- cgit v1.2.3 From 18f028fb0833b5a5b983a213343caa7460262002 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 16:47:45 -0800 Subject: Removed unnecessary note --- .../BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol | 2 -- 1 file changed, 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 51b3b9736..f730c5a11 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -114,8 +114,6 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// @dev A running list is maintained of addresses to validate. /// This function records an address in this array. /// @param addressToValidate - Address to record for validation. - /// @note - Variables are scoped but names are not, so we append - /// underscores to names that share the global namespace. function recordAddressToValidate(addressToValidate) { // Compute `addressesToValidate` memory offset let addressesToValidate_ := mload(0x40) -- cgit v1.2.3 From 7e7880aea05b70e6d0515586a005fafe34eba850 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 4 Dec 2018 17:28:05 -0800 Subject: Conformed to hex format for constructing offsets --- .../BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index f730c5a11..9bed7ba47 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -79,7 +79,6 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // Addresses that are validated below. address[] memory validatedAddresses; - ///// Do not add variables after this point. ///// ///// The assembly block may overwrite their values. ///// @@ -265,7 +264,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { // Construct calldata for `THRESHOLD_ASSET.balanceOf` mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(4, freeMemPtr), mload(addressToValidate)) + mstore(add(freeMemPtr, 0x04), mload(addressToValidate)) // call `THRESHOLD_ASSET.balanceOf` let success := call( -- cgit v1.2.3 From d882133e444d034c62b5f88c988d4cdd3fb2dbf4 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 6 Dec 2018 18:00:53 -0800 Subject: Wrappers for balance threshold filter + updated some tests to use the wrapper --- .../test/extensions/balance_threshold_filter.ts | 249 +++++++++++++++------ .../test/utils/balance_threshold_wrapper.ts | 243 ++++++++++++++++++++ .../test/utils/compliant_forwarder_wrapper.ts | 0 3 files changed, 429 insertions(+), 63 deletions(-) create mode 100644 packages/contracts/test/utils/balance_threshold_wrapper.ts delete mode 100644 packages/contracts/test/utils/compliant_forwarder_wrapper.ts (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index db4fea77a..9b48cdf93 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -6,6 +6,7 @@ import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; import * as ethUtil from 'ethereumjs-util'; import * as _ from 'lodash'; +import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; @@ -24,6 +25,7 @@ import { ExchangeWrapper } from '../utils/exchange_wrapper'; import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; +import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; @@ -37,6 +39,10 @@ const expect = chai.expect; const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); const DECIMALS_DEFAULT = 18; +interface ValidatedAddressesLog { + args: {addresses: string[]} +} + describe.only(ContractName.BalanceThresholdFilter, () => { let compliantMakerAddress: string; let owner: string; @@ -53,18 +59,35 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let orderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; + let balanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let noncompliantSignedFillOrderTx: SignedTransaction; + let logDecoder: LogDecoder; + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); let compliantForwarderInstance: BalanceThresholdFilterContract; + const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => { + expect(txReceipt.logs.length).to.be.gte(1); + const validatedAddressesLog = (txReceipt.logs[0] as any) as ValidatedAddressesLog; + const validatedAddresses = validatedAddressesLog.args.addresses; + // @HACK-hysz: Nested addresses are not translated to lower-case but this will change once + // the new ABI Encoder/Decoder is used by the contract templates. + let validatedAddressesNormalized: string[] = []; + _.each(validatedAddresses, (address) => { + const normalizedAddress = _.toLower(address); + validatedAddressesNormalized.push(normalizedAddress); + }); + expect(validatedAddressesNormalized).to.be.deep.equal(expectedValidatedAddresses); + }; + before(async () => { // Create accounts await blockchainLifecycle.startAsync(); @@ -190,17 +213,30 @@ describe.only(ContractName.BalanceThresholdFilter, () => { compliantSignedOrderWithoutExchangeAddressData, ); - /* generate selectors for every exchange method + /* _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { try { const method = new Method(abiDefinition as MethodAbi); - console.log('\n', `// ${method.getDataItem().name}`); - console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); - console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); + console.log(method.getSignature()); + if (!method.getSignature().startsWith('matchOrders')) { + return; + } + console.log(`FOUND IT`); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + compliantSignedOrder, + ); + const args = [signedOrderWithoutExchangeAddress, signedOrderWithoutExchangeAddress, compliantSignedOrder.signature, compliantSignedOrder.signature]; + console.log(method.encode(args, {annotate: true})); + //console.log('\n', `// ${method.getDataItem().name}`); + //console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); + //console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); } catch(e) { - _.noop(); + console.log(`encoding failed: ${e}`); } - });*/ + }); + throw new Error(`w`);*/ + logDecoder = new LogDecoder(web3Wrapper); + balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -208,21 +244,101 @@ describe.only(ContractName.BalanceThresholdFilter, () => { afterEach(async () => { await blockchainLifecycle.revertAsync(); }); - describe.only('fillOrder', () => { + + describe('General Sanity Checks', () => { + it('should revert if the signed transaction is not intended for supported', async () => { + // Create signed order without the fillOrder function selector + const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); + const selectorLengthInBytes = 4; + const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); + const badSelector = '0x00000000'; + const badSelectorBuf = ethUtil.toBuffer(badSelector); + const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); + const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + compliantSignedFillOrderTx.signerAddress, + txDataBufWithBadSelectorHex, + compliantSignedFillOrderTx.signature, + )); + }); + it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { + // Create signed order with incorrect senderAddress + const notBalanceThresholdFilterAddress = zrxToken.address; + const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: notBalanceThresholdFilterAddress, + }); + const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( + signedOrderWithBadSenderAddress, + ); + const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( + signedOrderWithoutExchangeAddress, + takerAssetFillAmount, + compliantSignedOrder.signature, + ); + const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( + signedOrderWithoutExchangeAddressData, + ); + // Call compliant forwarder + return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + )); + }); + // @TODO - greater than 1 balance + }); + + + + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); }); it('should transfer the correct amounts when maker and taker are compliant', async () => { + let order2 = _.cloneDeep(compliantSignedOrder); + order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; + const orders = [compliantSignedOrder, order2]; + const fillAmounts = [new BigNumber(4), new BigNumber(4)]; + const signatures = ["0xabcd", "0xabcd"]; + const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); + console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); + console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, - compliantSignedFillOrderTx.data, + exchangeCalldata, compliantSignedFillOrderTx.signature, ); const decoder = new LogDecoder(web3Wrapper); const tx = await decoder.getTxWithDecodedLogsAsync(txHash); console.log(JSON.stringify(tx, null, 4)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + }); + }); + + describe('batchFillOrdersNoThrow', () => { + }); + + describe('batchFillOrKillOrders', () => { + }); + + describe('batchFillOrKillOrders', () => { + }); + + describe.only('fillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantSignedFillOrderTx.signerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances const newBalances = await erc20Wrapper.getBalancesAsync(); const makerAssetFillAmount = takerAssetFillAmount .times(compliantSignedOrder.makerAssetAmount) @@ -255,31 +371,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), ); }); - it('should revert if the signed transaction is not intended for fillOrder', async () => { - // Create signed order without the fillOrder function selector - const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); - const selectorLengthInBytes = 4; - const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); - const badSelector = '0x00000000'; - const badSelectorBuf = ethUtil.toBuffer(badSelector); - const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); - const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - txDataBufWithBadSelectorHex, - compliantSignedFillOrderTx.signature, - )); - }); - it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { - // Create signed order with incorrect senderAddress - const notBalanceThresholdFilterAddress = zrxToken.address; - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: notBalanceThresholdFilterAddress, + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress }); const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, + signedOrderWithBadMakerAddress, ); const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( signedOrderWithoutExchangeAddress, @@ -290,14 +389,17 @@ describe.only(ContractName.BalanceThresholdFilter, () => { signedOrderWithoutExchangeAddressData, ); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - )); + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + signedFillOrderTx.salt, + signedFillOrderTx.signerAddress, + signedFillOrderTx.data, + signedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); }); - it('should revert if taker address is not compliant (does not hold a Yes Token)', async () => { + it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( compliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, @@ -308,7 +410,16 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - it('should revert if maker address is not compliant (does not hold a Yes Token)', async () => { + }); + + describe('fillOrderNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + + }); + it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ senderAddress: compliantForwarderInstance.address, @@ -336,32 +447,44 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + compliantForwarderInstance.executeTransaction.sendTransactionAsync( + compliantSignedFillOrderTx.salt, + nonCompliantAddress, + compliantSignedFillOrderTx.data, + compliantSignedFillOrderTx.signature, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('batchFillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - let order2 = _.cloneDeep(compliantSignedOrder); - order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; - const orders = [compliantSignedOrder, order2]; - const fillAmounts = [new BigNumber(4), new BigNumber(4)]; - const signatures = ["0xabcd", "0xabcd"]; - const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); - console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); + describe('fillOrKillOrder', () => { + }); - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - exchangeCalldata, - compliantSignedFillOrderTx.signature, - ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); - }); + describe('marketBuyOrders', () => { + }); + + describe('marketBuyOrdersNoThrow', () => { + }); + + describe('marketSellOrders', () => { + }); + + describe('marketSellOrdersNoThrow', () => { + }); + + describe('matchOrders', () => { + }); + + describe('cancelOrder', () => { + }); + + describe('batchCancelOrders', () => { + }); + + describe('cancelOrdersUpTo', () => { }); }); // tslint:disable:max-file-line-count diff --git a/packages/contracts/test/utils/balance_threshold_wrapper.ts b/packages/contracts/test/utils/balance_threshold_wrapper.ts new file mode 100644 index 000000000..ac7bdd593 --- /dev/null +++ b/packages/contracts/test/utils/balance_threshold_wrapper.ts @@ -0,0 +1,243 @@ +import { SignedOrder } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { ExchangeContract } from '../../generated-wrappers/exchange'; +import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; + +import { formatters } from './formatters'; +import { LogDecoder } from './log_decoder'; +import { orderUtils } from './order_utils'; +import { TransactionFactory } from '../utils/transaction_factory'; +import { OrderInfo } from './types'; + +export class BalanceThresholdWrapper { + private readonly _balanceThresholdFilter: BalanceThresholdFilterContract; + private readonly _signerTransactionFactory: TransactionFactory; + private readonly _exchange: ExchangeContract; + private readonly _web3Wrapper: Web3Wrapper; + private readonly _logDecoder: LogDecoder; + constructor(balanceThresholdFilter: BalanceThresholdFilterContract, exchangeContract: ExchangeContract, signerTransactionFactory: TransactionFactory, provider: Provider) { + this._balanceThresholdFilter = balanceThresholdFilter; + this._exchange = exchangeContract; + this._signerTransactionFactory = signerTransactionFactory; + this._web3Wrapper = new Web3Wrapper(provider); + this._logDecoder = new LogDecoder(this._web3Wrapper); + } + public async fillOrderAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrder.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async fillOrKillOrderAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrKillOrder.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async fillOrderNoThrowAsync( + signedOrder: SignedOrder, + from: string, + opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {}, + ): Promise { + const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); + const data = this._exchange.fillOrderNoThrow.getABIEncodedTransactionData( + params.order, + params.takerAssetFillAmount, + params.signature, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async batchFillOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[] } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const data = this._exchange.batchFillOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async batchFillOrKillOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[] } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async batchFillOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {}, + ): Promise { + const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); + const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmounts, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async marketSellOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmount: BigNumber }, + ): Promise { + const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); + const data = this._exchange.marketSellOrders.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async marketSellOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { takerAssetFillAmount: BigNumber; gas?: number }, + ): Promise { + const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); + const data = this._exchange.marketSellOrdersNoThrow.getABIEncodedTransactionData( + params.orders, + params.takerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async marketBuyOrdersAsync( + orders: SignedOrder[], + from: string, + opts: { makerAssetFillAmount: BigNumber }, + ): Promise { + const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); + const data = this._exchange.marketBuyOrders.getABIEncodedTransactionData( + params.orders, + params.makerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async marketBuyOrdersNoThrowAsync( + orders: SignedOrder[], + from: string, + opts: { makerAssetFillAmount: BigNumber; gas?: number }, + ): Promise { + const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); + const data = this._exchange.marketBuyOrdersNoThrow.getABIEncodedTransactionData( + params.orders, + params.makerAssetFillAmount, + params.signatures, + ); + const txReceipt = this._executeTransaction(data, from, opts.gas); + return txReceipt; + } + public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise { + const params = orderUtils.createCancel(signedOrder); + const data = this._exchange.cancelOrder.getABIEncodedTransactionData(params.order); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async batchCancelOrdersAsync( + orders: SignedOrder[], + from: string, + ): Promise { + const params = formatters.createBatchCancel(orders); + const data = this._exchange.batchCancelOrders.getABIEncodedTransactionData(params.orders); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise { + const data = this._exchange.cancelOrdersUpTo.getABIEncodedTransactionData(salt); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { + const filledAmount = await this._exchange.filled.callAsync(orderHashHex); + return filledAmount; + } + public async isCancelledAsync(orderHashHex: string): Promise { + const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex); + return isCancelled; + } + public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise { + const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress); + return orderEpoch; + } + public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { + const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; + return orderInfo; + } + public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise { + const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[]; + return ordersInfo; + } + public async matchOrdersAsync( + signedOrderLeft: SignedOrder, + signedOrderRight: SignedOrder, + from: string, + ): Promise { + const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight); + const data = await this._exchange.matchOrders.getABIEncodedTransactionData( + params.left, + params.right, + params.leftSignature, + params.rightSignature + ); + const txReceipt = this._executeTransaction(data, from); + return txReceipt; + } + public getBalanceThresholdAddress(): string { + return this._balanceThresholdFilter.address; + } + public getExchangeAddress(): string { + return this._exchange.address; + } + private async _executeTransaction(abiEncodedExchangeTxData: string, from: string, gas?: number): Promise { + const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData); + const txOpts = _.isUndefined(gas) ? {from} : {from, gas}; + const txHash = await this._balanceThresholdFilter.executeTransaction.sendTransactionAsync( + signedExchangeTx.salt, + signedExchangeTx.signerAddress, + signedExchangeTx.data, + signedExchangeTx.signature, + txOpts, + ); + const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); + return txReceipt; + } +} diff --git a/packages/contracts/test/utils/compliant_forwarder_wrapper.ts b/packages/contracts/test/utils/compliant_forwarder_wrapper.ts deleted file mode 100644 index e69de29bb..000000000 -- cgit v1.2.3 From 7af2c751dceb52adc84ded417b260666e6a96c29 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 14:21:29 -0800 Subject: fillOrder tests with new wrapper --- .../test/extensions/balance_threshold_filter.ts | 37 ++++++++-------------- 1 file changed, 13 insertions(+), 24 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 9b48cdf93..cfa1dcbdf 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -60,11 +60,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; let balanceThresholdWrapper: BalanceThresholdWrapper; + let nonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; - let noncompliantSignedFillOrderTx: SignedTransaction; let logDecoder: LogDecoder; @@ -237,6 +237,8 @@ describe.only(ContractName.BalanceThresholdFilter, () => { throw new Error(`w`);*/ logDecoder = new LogDecoder(web3Wrapper); balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; + nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -334,7 +336,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantSignedFillOrderTx.signerAddress, {takerAssetFillAmount}); + const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -377,35 +379,22 @@ describe.only(ContractName.BalanceThresholdFilter, () => { senderAddress: compliantForwarderInstance.address, makerAddress: nonCompliantAddress }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadMakerAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder + // Execute transaction return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, + balanceThresholdWrapper.fillOrderAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - nonCompliantAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, + nonCompliantBalanceThresholdWrapper.fillOrderAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); -- cgit v1.2.3 From a655f4b193d032cea7654f982b2f012220a0e77f Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 14:54:24 -0800 Subject: tests for fillOrder variants --- .../test/extensions/balance_threshold_filter.ts | 155 +++++++++++++++++---- 1 file changed, 131 insertions(+), 24 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index cfa1dcbdf..778aee1a1 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -298,6 +298,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); }); it('should transfer the correct amounts when maker and taker are compliant', async () => { let order2 = _.cloneDeep(compliantSignedOrder); @@ -333,6 +334,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill @@ -401,12 +403,49 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('fillOrderNoThrow', () => { + describe.only('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - + // Execute a valid fill + const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); }); it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address @@ -414,42 +453,110 @@ describe.only(ContractName.BalanceThresholdFilter, () => { senderAddress: compliantForwarderInstance.address, makerAddress: nonCompliantAddress }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadMakerAddress, + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrderNoThrowAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, + }); + }); + + describe.only('fillOrKillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; + const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount_ + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), ); - // Call compliant forwarder + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, + balanceThresholdWrapper.fillOrKillOrderAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - nonCompliantAddress, - compliantSignedFillOrderTx.data, - compliantSignedFillOrderTx.signature, + nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} ), RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - }); - - describe('fillOrKillOrder', () => { + it('should revert if order is not fully filled', async () => { + const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + compliantTakerAddress, + {takerAssetFillAmount: tooBigTakerAssetFillAmount} + ), + RevertReason.FailedExecution + ); + }); }); describe('marketBuyOrders', () => { -- cgit v1.2.3 From 4b0d01ad72b93e8ccb01ca2eabe521cdf78eeea2 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 15:35:49 -0800 Subject: tests for batchFillOrdes --- .../test/extensions/balance_threshold_filter.ts | 326 ++++++++++++++++++--- 1 file changed, 287 insertions(+), 39 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 778aee1a1..c91d4880e 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -45,6 +45,7 @@ interface ValidatedAddressesLog { describe.only(ContractName.BalanceThresholdFilter, () => { let compliantMakerAddress: string; + let compliantMakerAddress2: string; let owner: string; let compliantTakerAddress: string; let feeRecipientAddress: string; @@ -57,6 +58,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let exchangeWrapper: ExchangeWrapper; let orderFactory: OrderFactory; + let orderFactory2: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; let balanceThresholdWrapper: BalanceThresholdWrapper; @@ -64,6 +66,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let takerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; + let compliantSignedOrder2: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; let logDecoder: LogDecoder; @@ -95,6 +98,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const usedAddresses = ([ owner, compliantMakerAddress, + compliantMakerAddress2, compliantTakerAddress, feeRecipientAddress, nonCompliantAddress, @@ -134,10 +138,19 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { from: owner, }); + // Deploy Compliant Forwarder + const erc721BalanceThreshold = new BigNumber(1); + compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + artifacts.BalanceThresholdFilter, + provider, + txDefaults, + exchangeInstance.address, + yesTokenInstance.address, + erc721BalanceThreshold + ); // Default order parameters const defaultOrderParams = { exchangeAddress: exchangeInstance.address, - makerAddress: compliantMakerAddress, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), @@ -145,19 +158,22 @@ describe.only(ContractName.BalanceThresholdFilter, () => { takerAssetAmount, makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), + senderAddress: compliantForwarderInstance.address, }; - const privateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; - orderFactory = new OrderFactory(privateKey, defaultOrderParams); - // Deploy Compliant Forwarder - const erc721BalanceThreshold = new BigNumber(1); - compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( - artifacts.BalanceThresholdFilter, - provider, - txDefaults, - exchangeInstance.address, - yesTokenInstance.address, - erc721BalanceThreshold - ); + const defaultOrderParams1 = { + makerAddress: compliantMakerAddress, + ... + defaultOrderParams, + } + const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; + orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1); + const defaultOrderParams2 = { + makerAddress: compliantMakerAddress2, + ... + defaultOrderParams, + } + const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress2)]; + orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2); /* const compliantForwarderContract = new BalanceThresholdFilterContract( compliantForwarderInstance.abi, @@ -195,6 +211,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { [compliantTakerYesMark], { from: owner }, ); + await yesTokenInstance.mint2.sendTransactionAsync( + compliantMakerAddress2, + compliantTakerEntityId, + addressesCanControlTheirToken, + compliantTakerCountryCode, + [compliantTakerYesMark], + { from: owner }, + ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); @@ -293,45 +317,269 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); + describe('batchFillOrdersNoThrow', () => { + }); + + describe('batchFillOrKillOrders', () => { + }); + describe('batchFillOrKillOrders', () => { + }); - describe('batchFillOrders', () => { + describe.only('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); }); - it('should transfer the correct amounts when maker and taker are compliant', async () => { - let order2 = _.cloneDeep(compliantSignedOrder); - order2.makerAddress = `0x${_.reverse(compliantSignedOrder.makerAddress.slice(2).split('')).join('')}`; - const orders = [compliantSignedOrder, order2]; - const fillAmounts = [new BigNumber(4), new BigNumber(4)]; - const signatures = ["0xabcd", "0xabcd"]; - const exchangeCalldata = exchangeInstance.batchFillOrders.getABIEncodedTransactionData(orders, fillAmounts, signatures); - console.log('*'.repeat(40), exchangeCalldata, '*'.repeat(40)); - console.log('****** MAKER ADDRESS = ', compliantSignedOrder.makerAddress); - - const txHash = await compliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - exchangeCalldata, - compliantSignedFillOrderTx.signature, + it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const txReceipt = await balanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .times(2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); - const decoder = new LogDecoder(web3Wrapper); - const tx = await decoder.getTxWithDecodedLogsAsync(txHash); - console.log(JSON.stringify(tx, null, 4)); }); }); - describe('batchFillOrdersNoThrow', () => { + /* + describe.only('fillOrderNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrderNoThrowAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('batchFillOrKillOrders', () => { + describe.only('fillOrKillOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; + const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount_ + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); + it('should revert if maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrKillOrderAsync( + signedOrderWithBadMakerAddress, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if order is not fully filled', async () => { + const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); + return expectTransactionFailedAsync( + balanceThresholdWrapper.fillOrKillOrderAsync( + compliantSignedOrder, + compliantTakerAddress, + {takerAssetFillAmount: tooBigTakerAssetFillAmount} + ), + RevertReason.FailedExecution + ); + }); }); - describe('batchFillOrKillOrders', () => { - }); + */ - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -403,7 +651,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrderNoThrow', () => { + describe('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -475,7 +723,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrKillOrder', () => { + describe('fillOrKillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); -- cgit v1.2.3 From 51355209a2a43a1a7b5a72c78aabefcdac5ede33 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 15:59:15 -0800 Subject: Tests for batchFill variants --- .../test/extensions/balance_threshold_filter.ts | 214 +++++++++++++++++++-- 1 file changed, 203 insertions(+), 11 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index c91d4880e..003bc2cdc 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -317,16 +317,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); - describe('batchFillOrdersNoThrow', () => { - }); - - describe('batchFillOrKillOrders', () => { - }); - - describe('batchFillOrKillOrders', () => { - }); - - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -420,6 +411,207 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); + describe('batchFillOrdersNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const txReceipt = await balanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .times(2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrdersNoThrowAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + }); + + describe.only('batchFillOrKillOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const txReceipt = await balanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) + .times(2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrKillOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmounts} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if one takerAssetFillAmount is not fully filled', async () => { + const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; + return expectTransactionFailedAsync( + balanceThresholdWrapper.batchFillOrKillOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmounts} + ), + RevertReason.FailedExecution + ); + }); + }); + /* describe.only('fillOrderNoThrow', () => { beforeEach(async () => { @@ -564,7 +756,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - it('should revert if order is not fully filled', async () => { + it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( balanceThresholdWrapper.fillOrKillOrderAsync( -- cgit v1.2.3 From e45a0ffdbfd5289b9e123a4b58c26d6dbbcfd8c7 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:03:39 -0800 Subject: All tests running so far --- .../MixinBalanceThresholdFilterCore.sol | 1 + .../mixins/MBalanceThresholdFilterCore.sol | 1 + .../test/extensions/balance_threshold_filter.ts | 173 +-------------------- 3 files changed, 9 insertions(+), 166 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 9bed7ba47..2e058742b 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -28,6 +28,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR /// the exchange function is a cancellation. /// Supported Exchange functions: + /// - batchFillOrders /// - batchFillOrdersNoThrow /// - batchFillOrKillOrders /// - fillOrder diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol index 046caecdd..612fd481c 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol @@ -43,6 +43,7 @@ contract MBalanceThresholdFilterCore { /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR /// the exchange function is a cancellation. /// Supported Exchange functions: + /// - batchFillOrders /// - batchFillOrdersNoThrow /// - batchFillOrKillOrders /// - fillOrder diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 003bc2cdc..efe1ce49c 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -271,7 +271,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await blockchainLifecycle.revertAsync(); }); - describe('General Sanity Checks', () => { + describe.only('General Sanity Checks', () => { it('should revert if the signed transaction is not intended for supported', async () => { // Create signed order without the fillOrder function selector const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); @@ -317,7 +317,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); - describe('batchFillOrders', () => { + describe.only('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -411,7 +411,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('batchFillOrdersNoThrow', () => { + describe.only('batchFillOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -612,166 +612,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - /* - describe.only('fillOrderNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrderNoThrowAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe.only('fillOrKillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount_ - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if takerAssetFillAmount is not fully filled', async () => { - const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); - return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - compliantTakerAddress, - {takerAssetFillAmount: tooBigTakerAssetFillAmount} - ), - RevertReason.FailedExecution - ); - }); - }); - - */ - - describe('fillOrder', () => { + describe.only('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -843,7 +684,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('fillOrderNoThrow', () => { + describe.only('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -915,7 +756,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('fillOrKillOrder', () => { + describe.only('fillOrKillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -986,7 +827,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold ); }); - it('should revert if order is not fully filled', async () => { + it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( balanceThresholdWrapper.fillOrKillOrderAsync( -- cgit v1.2.3 From a2df428afbd820b15ea108f721ab88f77a8d6dc1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:18:20 -0800 Subject: tests for marketSellOrders --- .../test/extensions/balance_threshold_filter.ts | 108 +++++++++++++++++++-- 1 file changed, 99 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index efe1ce49c..4c927f1d1 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -271,7 +271,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await blockchainLifecycle.revertAsync(); }); - describe.only('General Sanity Checks', () => { + describe('General Sanity Checks', () => { it('should revert if the signed transaction is not intended for supported', async () => { // Create signed order without the fillOrder function selector const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); @@ -317,7 +317,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // @TODO - greater than 1 balance }); - describe.only('batchFillOrders', () => { + describe('batchFillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -411,7 +411,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('batchFillOrdersNoThrow', () => { + describe('batchFillOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -505,7 +505,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('batchFillOrKillOrders', () => { + describe('batchFillOrKillOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -612,7 +612,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrder', () => { + describe('fillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -684,7 +684,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrderNoThrow', () => { + describe('fillOrderNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -756,7 +756,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('fillOrKillOrder', () => { + describe('fillOrKillOrder', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -840,11 +840,101 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('marketBuyOrders', () => { + describe.only('marketSellOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const txReceipt = await balanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketSellOrdersAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('marketBuyOrdersNoThrow', () => { + describe.only('marketBuyOrdersNoThrow', () => { + }); + describe('marketSellOrders', () => { }); -- cgit v1.2.3 From 4f977aa51d6a5baa4cb602cbc1d71b1481aaaa3d Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:18:59 -0800 Subject: marketSellNoThrow tests --- .../test/extensions/balance_threshold_filter.ts | 91 ++++++++++++++++++++++ 1 file changed, 91 insertions(+) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 4c927f1d1..776712088 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -931,6 +931,97 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); + describe.only('marketSellOrdersNoThrow', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const txReceipt = await balanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketSellOrdersNoThrowAsync( + orders, + compliantTakerAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( + orders, + nonCompliantAddress, + {takerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + }); + describe.only('marketBuyOrdersNoThrow', () => { }); -- cgit v1.2.3 From 3ad72d96f416fdd2446681c41f0151986c0a22b8 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 16:28:01 -0800 Subject: Tests for marketBuy invariants --- .../test/extensions/balance_threshold_filter.ts | 195 +++++++++++++++++++-- 1 file changed, 185 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 776712088..d12c28235 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -840,7 +840,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketSellOrders', () => { + describe('marketSellOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -931,7 +931,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketSellOrdersNoThrow', () => { + describe('marketSellOrdersNoThrow', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1022,17 +1022,192 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketBuyOrdersNoThrow', () => { - - }); - - - describe('marketSellOrders', () => { + describe.only('marketBuyOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + const txReceipt = await balanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketBuyOrdersAsync( + orders, + compliantTakerAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( + orders, + nonCompliantAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - describe('marketSellOrdersNoThrow', () => { + describe.only('marketBuyOrdersNoThrowAsync', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { + // Execute a valid fill + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); + const makerAssetFillAmount2 = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); + const txReceipt = await balanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerFeePaid2 = compliantSignedOrder2.makerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid2 = compliantSignedOrder2.takerFee + .times(makerAssetFillAmount2) + .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); + // Maker #1 + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), + ); + // Maker #2 + expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), + ); + expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), + ); + // Taker + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + // Fee recipient + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), + ); + }); + it('should revert if one maker does not meet the balance threshold', async () => { + // Create order set with one non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + makerAddress: nonCompliantAddress + }); + const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; + // Execute transaction + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + balanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + orders, + compliantTakerAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + const orders = [compliantSignedOrder, compliantSignedOrder2]; + const dummyMakerAssetFillAmount = new BigNumber(0); + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + orders, + nonCompliantAddress, + {makerAssetFillAmount: dummyMakerAssetFillAmount} + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); - + describe('matchOrders', () => { }); -- cgit v1.2.3 From 93b9c251ed735905d30a0daa4d90fc27c8625aa6 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 17:54:27 -0800 Subject: Tests for MatchOrders --- .../MixinBalanceThresholdFilterCore.sol | 8 +- .../test/extensions/balance_threshold_filter.ts | 106 ++++++++++++++++++++- .../test/utils/balance_threshold_wrapper.ts | 4 + 3 files changed, 111 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 2e058742b..303e1d9c2 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -133,7 +133,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// the running list of addresses to validate. /// @param orderParamIndex - Index of the order in the Exchange function's signature function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(orderParamIndex) + let orderPtr := loadExchangeData(mul(orderParamIndex, 0x20)) let makerAddress := loadExchangeData(orderPtr) recordAddressToValidate(makerAddress) } @@ -143,7 +143,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { /// the running list of addresses to validate. /// @param orderArrayParamIndex - Index of the order array in the Exchange function's signature function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(orderArrayParamIndex) + let orderArrayPtr := loadExchangeData(mul(orderArrayParamIndex, 0x20)) let orderArrayLength := loadExchangeData(orderArrayPtr) let orderArrayElementPtr := add(orderArrayPtr, 0x20) let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) @@ -258,7 +258,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // This is to avoid corruption when making calls in the loop below. let freeMemPtr := addressesToValidateElementEndPtr mstore(0x40, freeMemPtr) - +/* // Validate addresses let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) let thresholdBalance := sload(THRESHOLD_BALANCE_slot) @@ -307,7 +307,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. revert(0, 132) } - } + }*/ // Record validated addresses validatedAddresses := addressesToValidate diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index d12c28235..7ca8a8e98 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -22,17 +22,20 @@ import { chaiSetup } from '../utils/chai_setup'; import { constants } from '../utils/constants'; import { ERC20Wrapper } from '../utils/erc20_wrapper'; import { ExchangeWrapper } from '../utils/exchange_wrapper'; +import { MatchOrderTester } from '../utils/match_order_tester'; import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; +import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; import { MethodAbi, AbiDefinition } from 'ethereum-types'; import { AbiEncoder } from '@0x/utils'; import { Method } from '@0x/utils/lib/src/abi_encoder'; import { LogDecoder } from '../utils/log_decoder'; +import { ERC721Wrapper } from '../utils/erc721_wrapper'; chaiSetup.configure(); const expect = chai.expect; @@ -70,6 +73,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let compliantSignedFillOrderTx: SignedTransaction; let logDecoder: LogDecoder; + let exchangeInternals: TestExchangeInternalsContract; const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); @@ -263,6 +267,13 @@ describe.only(ContractName.BalanceThresholdFilter, () => { balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); + + // Instantiate internal exchange contract + exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( + artifacts.TestExchangeInternals, + provider, + txDefaults, + ); }); beforeEach(async () => { await blockchainLifecycle.startAsync(); @@ -1022,7 +1033,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketBuyOrders', () => { + describe('marketBuyOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1115,7 +1126,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('marketBuyOrdersNoThrowAsync', () => { + describe('marketBuyOrdersNoThrowAsync', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1208,7 +1219,96 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe('matchOrders', () => { + describe.only('matchOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it.only('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { + // Test values/results taken from Match Orders test: + // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil' + // Create orders to match + const signedOrderLeft = await orderFactory.newSignedOrderAsync({ + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0), + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + feeRecipientAddress: feeRecipientAddress, + }); + const signedOrderRight = await orderFactory2.newSignedOrderAsync({ + makerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), + takerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), + makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), + feeRecipientAddress: feeRecipientAddress, + }); + // Compute expected transfer amounts + const expectedTransferAmounts = { + // Left Maker + amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% + // Right Maker + amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), + amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), + feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + // Taker + amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0), + feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% + feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% + }; + const txReceipt = await balanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); + // Assert validated addresses + const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + expect( + newBalances[signedOrderLeft.makerAddress][defaultMakerAssetAddress], + 'Checking left maker egress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultMakerAssetAddress].sub(expectedTransferAmounts.amountSoldByLeftMaker)); + expect( + newBalances[signedOrderRight.makerAddress][defaultTakerAssetAddress], + 'Checking right maker ingress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][defaultTakerAssetAddress].sub(expectedTransferAmounts.amountSoldByRightMaker)); + expect( + newBalances[compliantTakerAddress][defaultMakerAssetAddress], + 'Checking taker ingress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountReceivedByTaker)); + expect( + newBalances[signedOrderLeft.makerAddress][defaultTakerAssetAddress], + 'Checking left maker ingress ERC20 account balance', + ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultTakerAssetAddress].add(expectedTransferAmounts.amountBoughtByLeftMaker)); + expect( + newBalances[signedOrderRight.makerAddress][defaultMakerAssetAddress], + 'Checking right maker egress ERC20 account balance', + ).to.be.bignumber.equal( + erc20Balances[signedOrderRight.makerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountBoughtByRightMaker), + ); + // Paid fees + expect( + newBalances[signedOrderLeft.makerAddress][zrxToken.address], + 'Checking left maker egress ERC20 account fees', + ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByLeftMaker)); + expect( + newBalances[signedOrderRight.makerAddress][zrxToken.address], + 'Checking right maker egress ERC20 account fees', + ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByRightMaker)); + expect( + newBalances[compliantTakerAddress][zrxToken.address], + 'Checking taker egress ERC20 account fees', + ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByTakerLeft).sub(expectedTransferAmounts.feePaidByTakerRight)); + // Received fees + expect( + newBalances[signedOrderLeft.feeRecipientAddress][zrxToken.address], + 'Checking left fee recipient ingress ERC20 account fees', + ).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(expectedTransferAmounts.feePaidByLeftMaker).add(expectedTransferAmounts.feePaidByRightMaker).add(expectedTransferAmounts.feePaidByTakerLeft).add(expectedTransferAmounts.feePaidByTakerRight), + ); + }); }); describe('cancelOrder', () => { diff --git a/packages/contracts/test/utils/balance_threshold_wrapper.ts b/packages/contracts/test/utils/balance_threshold_wrapper.ts index ac7bdd593..cff40aa52 100644 --- a/packages/contracts/test/utils/balance_threshold_wrapper.ts +++ b/packages/contracts/test/utils/balance_threshold_wrapper.ts @@ -227,6 +227,10 @@ export class BalanceThresholdWrapper { public getExchangeAddress(): string { return this._exchange.address; } + // Exchange functions + //abiEncodeFillOrder + //getFillOrderResultsAsync + // private async _executeTransaction(abiEncodedExchangeTxData: string, from: string, gas?: number): Promise { const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData); const txOpts = _.isUndefined(gas) ? {from} : {from, gas}; -- cgit v1.2.3 From 1883f4d2726e1a879be42b7bb6168f30afd486d9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Fri, 7 Dec 2018 18:01:16 -0800 Subject: matchOrders test cases for balance threshold filter contract --- .../MixinBalanceThresholdFilterCore.sol | 4 +- .../test/extensions/balance_threshold_filter.ts | 44 +++++++++++++++++++++- 2 files changed, 45 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol index 303e1d9c2..0ad8ccddf 100644 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol @@ -258,7 +258,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // This is to avoid corruption when making calls in the loop below. let freeMemPtr := addressesToValidateElementEndPtr mstore(0x40, freeMemPtr) -/* + // Validate addresses let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) let thresholdBalance := sload(THRESHOLD_BALANCE_slot) @@ -307,7 +307,7 @@ contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. revert(0, 132) } - }*/ + } // Record validated addresses validatedAddresses := addressesToValidate diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 7ca8a8e98..0a03678b1 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -1225,7 +1225,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { compliantSignedOrder = await orderFactory.newSignedOrderAsync(); compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); }); - it.only('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { + it('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { // Test values/results taken from Match Orders test: // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil' // Create orders to match @@ -1309,6 +1309,48 @@ describe.only(ContractName.BalanceThresholdFilter, () => { erc20Balances[feeRecipientAddress][zrxToken.address].add(expectedTransferAmounts.feePaidByLeftMaker).add(expectedTransferAmounts.feePaidByRightMaker).add(expectedTransferAmounts.feePaidByTakerLeft).add(expectedTransferAmounts.feePaidByTakerRight), ); }); + it('should revert if left maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.matchOrdersAsync( + compliantSignedOrder, + signedOrderWithBadMakerAddress, + compliantTakerAddress, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if right maker does not meet the balance threshold', async () => { + // Create signed order with non-compliant maker address + const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ + senderAddress: compliantForwarderInstance.address, + makerAddress: nonCompliantAddress + }); + // Execute transaction + return expectTransactionFailedAsync( + balanceThresholdWrapper.matchOrdersAsync( + signedOrderWithBadMakerAddress, + compliantSignedOrder, + compliantTakerAddress, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); + it('should revert if taker does not meet the balance threshold', async () => { + return expectTransactionFailedAsync( + nonCompliantBalanceThresholdWrapper.matchOrdersAsync( + compliantSignedOrder, + compliantSignedOrder, + nonCompliantAddress, + ), + RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold + ); + }); }); describe('cancelOrder', () => { -- cgit v1.2.3 From cb9ec18f962d3a44e75f291795fc59494a6226de Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 15:11:00 -0800 Subject: Tests finished for balance threshold wrapper --- .../test/extensions/balance_threshold_filter.ts | 220 ++++++++++++++++++--- 1 file changed, 189 insertions(+), 31 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 0a03678b1..d0e902eda 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -27,7 +27,7 @@ import { OrderFactory } from '../utils/order_factory'; import { orderUtils } from '../utils/order_utils'; import { TransactionFactory } from '../utils/transaction_factory'; import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; -import { ContractName, ERC20BalancesByOwner, SignedTransaction } from '../utils/types'; +import { ContractName, ERC20BalancesByOwner, SignedTransaction, OrderStatus } from '../utils/types'; import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; @@ -62,12 +62,15 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let orderFactory: OrderFactory; let orderFactory2: OrderFactory; + let nonCompliantOrderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let balanceThresholdWrapper: BalanceThresholdWrapper; + let takerBalanceThresholdWrapper: BalanceThresholdWrapper; + let makerBalanceThresholdWrapper: BalanceThresholdWrapper; let nonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; + let makerTransactionFactory: TransactionFactory; let compliantSignedOrder: SignedOrder; let compliantSignedOrder2: SignedOrder; let compliantSignedFillOrderTx: SignedTransaction; @@ -170,6 +173,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { defaultOrderParams, } const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; + takerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address); orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1); const defaultOrderParams2 = { makerAddress: compliantMakerAddress2, @@ -178,6 +182,15 @@ describe.only(ContractName.BalanceThresholdFilter, () => { } const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress2)]; orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2); + + const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; + const defaultNonCompliantOrderParams = { + makerAddress: nonCompliantAddress, + ... + defaultOrderParams, + }; + nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams); + /* const compliantForwarderContract = new BalanceThresholdFilterContract( compliantForwarderInstance.abi, @@ -264,10 +277,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); throw new Error(`w`);*/ logDecoder = new LogDecoder(web3Wrapper); - balanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; + takerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + makerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); + nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - + + // Instantiate internal exchange contract exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( artifacts.TestExchangeInternals, @@ -338,7 +353,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await balanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -400,7 +415,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrdersAsync( + takerBalanceThresholdWrapper.batchFillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -432,7 +447,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await balanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -494,7 +509,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrdersNoThrowAsync( + takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -526,7 +541,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await balanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -588,7 +603,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrKillOrdersAsync( + takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -613,7 +628,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; return expectTransactionFailedAsync( - balanceThresholdWrapper.batchFillOrKillOrdersAsync( + takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -630,7 +645,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -675,7 +690,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrderAsync( + takerBalanceThresholdWrapper.fillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -702,7 +717,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await balanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -747,7 +762,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrderNoThrowAsync( + takerBalanceThresholdWrapper.fillOrderNoThrowAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -775,7 +790,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await balanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + const txReceipt = await takerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -820,7 +835,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( + takerBalanceThresholdWrapper.fillOrKillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -841,7 +856,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( - balanceThresholdWrapper.fillOrKillOrderAsync( + takerBalanceThresholdWrapper.fillOrKillOrderAsync( compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: tooBigTakerAssetFillAmount} @@ -861,7 +876,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await balanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -921,7 +936,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.marketSellOrdersAsync( + takerBalanceThresholdWrapper.marketSellOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -952,7 +967,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await balanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1012,7 +1027,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.marketSellOrdersNoThrowAsync( + takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -1047,7 +1062,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await balanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1104,7 +1119,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - balanceThresholdWrapper.marketBuyOrdersAsync( + takerBalanceThresholdWrapper.marketBuyOrdersAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1140,7 +1155,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await balanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1197,7 +1212,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - balanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1219,7 +1234,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); }); - describe.only('matchOrders', () => { + describe('matchOrders', () => { beforeEach(async () => { erc20Balances = await erc20Wrapper.getBalancesAsync(); compliantSignedOrder = await orderFactory.newSignedOrderAsync(); @@ -1260,7 +1275,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% }; - const txReceipt = await balanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); + const txReceipt = await takerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); // Assert validated addresses const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1317,7 +1332,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.matchOrdersAsync( + takerBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, signedOrderWithBadMakerAddress, compliantTakerAddress, @@ -1333,7 +1348,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); // Execute transaction return expectTransactionFailedAsync( - balanceThresholdWrapper.matchOrdersAsync( + takerBalanceThresholdWrapper.matchOrdersAsync( signedOrderWithBadMakerAddress, compliantSignedOrder, compliantTakerAddress, @@ -1354,12 +1369,155 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); describe('cancelOrder', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it('Should successfully cancel order if maker meets balance threshold', async () => { + // Verify order is not cancelled + const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + // Cancel + const txReceipt = await makerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); + it('Should successfully cancel order if maker does not meet balance threshold', async () => { + // Create order where maker does not meet balance threshold + const signedOrderWithBadMakerAddress = await nonCompliantOrderFactory.newSignedOrderAsync({}); + // Verify order is not cancelled + const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + // Cancel + const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); }); describe('batchCancelOrders', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('Should successfully batch cancel orders if maker meets balance threshold', async () => { + // Create orders to cancel + const compliantSignedOrders = [ + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + await orderFactory.newSignedOrderAsync(), + ]; + // Verify orders are not cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder) => { + const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const txReceipt = await makerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder) => { + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); + }); + it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { + // Create orders to cancel + const nonCompliantSignedOrders = [ + await nonCompliantOrderFactory.newSignedOrderAsync(), + await nonCompliantOrderFactory.newSignedOrderAsync(), + await nonCompliantOrderFactory.newSignedOrderAsync(), + ]; + // Verify orders are not cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { + const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const txReceipt = await nonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { + const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + }); + }); }); describe('cancelOrdersUpTo', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + }); + it('Should successfully batch cancel orders if maker meets balance threshold', async () => { + // Create orders to cancel + const compliantSignedOrders = [ + await orderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), + await orderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), + await orderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), + ]; + // Verify orders are not cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder) => { + const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const cancelOrdersUpToThisSalt = new BigNumber(1); + const txReceipt = await makerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => { + const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const saltAsBigNumber = new BigNumber(salt); + if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + } else { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + } + }); + }); + it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { + // Create orders to cancel + const nonCompliantSignedOrders = [ + await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), + await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), + await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), + ]; + // Verify orders are not cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { + const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + }); + // Cancel + const cancelOrdersUpToThisSalt = new BigNumber(1); + const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); + // Assert validated addresses + const expectedValidatedAddresseses: string[] = []; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check that order was cancelled + await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => { + const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const saltAsBigNumber = new BigNumber(salt); + if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); + } else { + return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); + } + }); + }); }); }); // tslint:disable:max-file-line-count -- cgit v1.2.3 From 8d6219296a4ac0c2ec46ae077eb87cebb19f8b55 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 17:08:16 -0800 Subject: Removed Yes Token - its no longer needed to test Balance Threshold Filter --- .../YesComplianceToken/IYesComplianceToken.sol | 119 ---- .../YesComplianceToken/WyreERC721Token/ERC721.sol | 40 -- .../WyreERC721Token/ERC721Basic.sol | 47 -- .../WyreERC721Token/ERC721BasicToken.sol | 343 ----------- .../WyreERC721Token/ERC721Token.sol | 209 ------- .../YesComplianceToken/YesComplianceToken.sol | 648 --------------------- .../test/extensions/balance_threshold_filter.ts | 59 +- 7 files changed, 12 insertions(+), 1453 deletions(-) delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol delete mode 100644 packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol deleted file mode 100644 index a1c9b9671..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/IYesComplianceToken.sol +++ /dev/null @@ -1,119 +0,0 @@ -pragma solidity ^0.4.24; - -import "./WyreERC721Token/ERC721Token.sol"; - -/** - * @notice an ERC721 "yes" compliance token supporting a collection of country-specific attributions which answer specific - * compliance-related queries with YES. (attestations) - * - * primarily ERC721 is useful for the self-management of claiming addresses. a single token is more useful - * than a non-ERC721 interface because of interop with other 721-supporting systems/ui; it allows users to - * manage their financial stamp with flexibility using a well-established simple concept of non-fungible tokens. - * this interface is for anyone needing to carry around and otherwise manage their proof of compliance. - * - * the financial systems these users authenticate against have a different set of API requirements. they need - * more contextualization ability than a balance check to support distinctions of attestations, as well as geographic - * distinction. these integrations are made simpler as the language of the query more closely match the language of compliance. - * - * this interface describes, beyond 721, these simple compliance-specific interfaces (and their management tools) - * - * notes: - * - no address can be associated with more than one identity (though addresses may have more than token). issuance - * in this circumstance will fail - * - one person or business = one entity - * - one entity may have many tokens across many addresses; they can mint and burn tokens tied to their identity at will - * - two token types: control & non-control. both carry compliance proof - * - control tokens let their holders mint and burn (within the same entity) - * - non-control tokens are solely for compliance queries - * - a lock on the entity is used instead of token revocation to remove the cash burden assumed by a customer to - * redistribute a fleet of coins - * - all country codes should be via ISO-3166-1 - * - * any (non-view) methods not explicitly marked idempotent are not idempotent. - */ -contract YesComplianceTokenV1 is ERC721Token /*, ERC165 :should: */ { - - uint256 public constant OWNER_ENTITY_ID = 1; - - uint8 public constant YESMARK_OWNER = 128; - uint8 public constant YESMARK_VALIDATOR = 129; - - /* - todo events: entity updated, destroyed, ???? - Finalized - Attested - - */ - - /** - * @notice query api: returns true if the specified address has the given country/yes attestation. this - * is the primary method partners will use to query the active qualifications of any particular - * address. - */ - function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) ; - - /** @notice same as isYes except as an imperative */ - function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view ; - - /** - * @notice retrieve all YES marks for an address in a particular country - * @param _validatorEntityId the validator ID to consider. or, use 0 for any of them - * @param _address the validator ID to consider, or 0 for any of them - * @param _countryCode the ISO-3166-1 country code - * @return (non-duplicate) array of YES marks present - */ - function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] /* memory */); - - // function getCountries(uint256 _validatorEntityId, address _address) external view returns(uint16[] /* memory */); - - /** - * @notice create new tokens. fail if _to already - * belongs to a different entity and caller is not validator - * @param _control true if the new token is a control token (can mint, burn). aka NOT limited. - * @param _entityId the entity to mint for, supply 0 to use the entity tied to the caller - * @return the newly created token ID - */ - function mint(address _to, uint256 _entityId, bool _control) external returns (uint256); - - /** @notice shortcut to mint() + setYes() in one call, for a single country */ - function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256); - - /** @notice destroys a specific token */ - function burn(uint256 _tokenId) external; - - /** @notice destroys the entire entity and all tokens */ - function burnEntity(uint256 _entityId) external; - - /** - * @notice adds a specific attestations (yes) to an entity. idempotent: will return normally even if the mark - * was already set by this validator - */ - function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; - - /** - * @notice removes a attestation(s) from a specific validator for an entity. idempotent - */ - function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external; - - /** @notice removes all attestations in a given country for a particular entity. idempotent */ - function clearYes(uint256 _entityId, uint16 _countryCode) external; - - /** @notice removes all attestations for a particular entity. idempotent */ - function clearYes(uint256 _entityId) external; - - /** @notice assigns a lock to an entity, rendering all isYes queries false. idempotent */ - function setLocked(uint256 _entityId, bool _lock) external; - - /** @notice checks whether or not a particular entity is locked */ - function isLocked(uint256 _entityId) external view returns(bool); - - /** @notice returns true if the specified token has been finalized (cannot be moved) */ - function isFinalized(uint256 _tokenId) external view returns(bool); - - /** @notice finalizes a token by ID preventing it from getting moved. idempotent */ - function finalize(uint256 _tokenId) external; - - /** @return the entity ID associated with an address (or fail if there is not one) */ - function getEntityId(address _address) external view returns(uint256); - -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol deleted file mode 100644 index 5b4907f13..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721.sol +++ /dev/null @@ -1,40 +0,0 @@ -pragma solidity ^0.4.21; - -import "./ERC721Basic.sol"; - - -/** - * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Enumerable is ERC721Basic { - function totalSupply() public view returns (uint256); - function tokenOfOwnerByIndex( - address _owner, - uint256 _index - ) - public - view - returns (uint256 _tokenId); - - function tokenByIndex(uint256 _index) public view returns (uint256); -} - - -/** - * @title ERC-721 Non-Fungible Token Standard, optional metadata extension - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Metadata is ERC721Basic { - function name() external view returns (string _name); - function symbol() external view returns (string _symbol); - function tokenURI(uint256 _tokenId) public view returns (string); -} - - -/** - * @title ERC-721 Non-Fungible Token Standard, full implementation interface - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721 is ERC721Basic, ERC721Enumerable, ERC721Metadata { -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol deleted file mode 100644 index 20f3c5812..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Basic.sol +++ /dev/null @@ -1,47 +0,0 @@ -pragma solidity ^0.4.21; - -/** - * @title ERC721 Non-Fungible Token Standard basic interface - * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Basic { - event Transfer( - address indexed _from, - address indexed _to, - uint256 indexed _tokenId - ); - event Approval( - address indexed _owner, - address indexed _approved, - uint256 indexed _tokenId - ); - event ApprovalForAll( - address indexed _owner, - address indexed _operator, - bool _approved - ); - - function balanceOf(address _owner) public view returns (uint256 _balance); - function ownerOf(uint256 _tokenId) public view returns (address _owner); - function exists(uint256 _tokenId) public view returns (bool _exists); - - function approve(address _to, uint256 _tokenId) public; - function getApproved(uint256 _tokenId) - public view returns (address _operator); - - function setApprovalForAll(address _operator, bool _approved) public; - function isApprovedForAll(address _owner, address _operator) - public view returns (bool); - - function transferFrom(address _from, address _to, uint256 _tokenId) public; - function safeTransferFrom(address _from, address _to, uint256 _tokenId) - public; - - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId, - bytes _data - ) - public; -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol deleted file mode 100644 index 788e31580..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721BasicToken.sol +++ /dev/null @@ -1,343 +0,0 @@ -pragma solidity ^0.4.21; - -import "./ERC721Basic.sol"; -import "../../ERC721Token/IERC721Receiver.sol"; -import "../../../utils/SafeMath/SafeMath.sol"; - - -/** - * @title ERC721 Non-Fungible Token Standard basic implementation - * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721BasicToken is ERC721Basic, SafeMath { - - bytes4 private constant InterfaceId_ERC721 = 0x80ac58cd; - /* - * 0x80ac58cd === - * bytes4(keccak256('balanceOf(address)')) ^ - * bytes4(keccak256('ownerOf(uint256)')) ^ - * bytes4(keccak256('approve(address,uint256)')) ^ - * bytes4(keccak256('getApproved(uint256)')) ^ - * bytes4(keccak256('setApprovalForAll(address,bool)')) ^ - * bytes4(keccak256('isApprovedForAll(address,address)')) ^ - * bytes4(keccak256('transferFrom(address,address,uint256)')) ^ - * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^ - * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) - */ - - bytes4 private constant InterfaceId_ERC721Exists = 0x4f558e79; - /* - * 0x4f558e79 === - * bytes4(keccak256('exists(uint256)')) - */ - - // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` - // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` - bytes4 private constant ERC721_RECEIVED = 0x150b7a02; - - // Mapping from token ID to owner - mapping (uint256 => address) internal tokenOwner; - - // Mapping from token ID to approved address - mapping (uint256 => address) internal tokenApprovals; - - // Mapping from owner to number of owned token - mapping (address => uint256) internal ownedTokensCount; - - // Mapping from owner to operator approvals - mapping (address => mapping (address => bool)) internal operatorApprovals; - - /** - * @dev Guarantees msg.sender is owner of the given token - * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender - */ - modifier onlyOwnerOf(uint256 _tokenId) { - require(ownerOf(_tokenId) == msg.sender); - _; - } - - /** - * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator - * @param _tokenId uint256 ID of the token to validate - */ - modifier canTransfer(uint256 _tokenId) { - require(isApprovedOrOwner(msg.sender, _tokenId)); - _; - } - - /** - * @dev Gets the balance of the specified address - * @param _owner address to query the balance of - * @return uint256 representing the amount owned by the passed address - */ - function balanceOf(address _owner) public view returns (uint256) { - require(_owner != address(0)); - return ownedTokensCount[_owner]; - } - - /** - * @dev Gets the owner of the specified token ID - * @param _tokenId uint256 ID of the token to query the owner of - * @return owner address currently marked as the owner of the given token ID - */ - function ownerOf(uint256 _tokenId) public view returns (address) { - address owner = tokenOwner[_tokenId]; - require(owner != address(0)); - return owner; - } - - /** - * @dev Returns whether the specified token exists - * @param _tokenId uint256 ID of the token to query the existence of - * @return whether the token exists - */ - function exists(uint256 _tokenId) public view returns (bool) { - address owner = tokenOwner[_tokenId]; - return owner != address(0); - } - - /** - * @dev Approves another address to transfer the given token ID - * The zero address indicates there is no approved address. - * There can only be one approved address per token at a given time. - * Can only be called by the token owner or an approved operator. - * @param _to address to be approved for the given token ID - * @param _tokenId uint256 ID of the token to be approved - */ - function approve(address _to, uint256 _tokenId) public { - address owner = ownerOf(_tokenId); - require(_to != owner); - require(msg.sender == owner || isApprovedForAll(owner, msg.sender)); - - tokenApprovals[_tokenId] = _to; - emit Approval(owner, _to, _tokenId); - } - - /** - * @dev Gets the approved address for a token ID, or zero if no address set - * @param _tokenId uint256 ID of the token to query the approval of - * @return address currently approved for the given token ID - */ - function getApproved(uint256 _tokenId) public view returns (address) { - return tokenApprovals[_tokenId]; - } - - /** - * @dev Sets or unsets the approval of a given operator - * An operator is allowed to transfer all tokens of the sender on their behalf - * @param _to operator address to set the approval - * @param _approved representing the status of the approval to be set - */ - function setApprovalForAll(address _to, bool _approved) public { - require(_to != msg.sender); - operatorApprovals[msg.sender][_to] = _approved; - emit ApprovalForAll(msg.sender, _to, _approved); - } - - /** - * @dev Tells whether an operator is approved by a given owner - * @param _owner owner address which you want to query the approval of - * @param _operator operator address which you want to query the approval of - * @return bool whether the given operator is approved by the given owner - */ - function isApprovedForAll( - address _owner, - address _operator - ) - public - view - returns (bool) - { - return operatorApprovals[_owner][_operator]; - } - - /** - * @dev Transfers the ownership of a given token ID to another address - * Usage of this method is discouraged, use `safeTransferFrom` whenever possible - * Requires the msg sender to be the owner, approved, or operator - * @param _from current owner of the token - * @param _to address to receive the ownership of the given token ID - * @param _tokenId uint256 ID of the token to be transferred - */ - function transferFrom( - address _from, - address _to, - uint256 _tokenId - ) - public - canTransfer(_tokenId) - { - require(_from != address(0)); - require(_to != address(0)); - - clearApproval(_from, _tokenId); - removeTokenFrom(_from, _tokenId); - addTokenTo(_to, _tokenId); - - emit Transfer(_from, _to, _tokenId); - } - - /** - * @dev Safely transfers the ownership of a given token ID to another address - * If the target address is a contract, it must implement `onERC721Received`, - * which is called upon a safe transfer, and return the magic value - * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, - * the transfer is reverted. - * - * Requires the msg sender to be the owner, approved, or operator - * @param _from current owner of the token - * @param _to address to receive the ownership of the given token ID - * @param _tokenId uint256 ID of the token to be transferred - */ - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId - ) - public - canTransfer(_tokenId) - { - // solium-disable-next-line arg-overflow - safeTransferFrom(_from, _to, _tokenId, ""); - } - - /** - * @dev Safely transfers the ownership of a given token ID to another address - * If the target address is a contract, it must implement `onERC721Received`, - * which is called upon a safe transfer, and return the magic value - * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise, - * the transfer is reverted. - * Requires the msg sender to be the owner, approved, or operator - * @param _from current owner of the token - * @param _to address to receive the ownership of the given token ID - * @param _tokenId uint256 ID of the token to be transferred - * @param _data bytes data to send along with a safe transfer check - */ - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId, - bytes _data - ) - public - canTransfer(_tokenId) - { - transferFrom(_from, _to, _tokenId); - // solium-disable-next-line arg-overflow - require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data)); - } - - /** - * @dev Returns whether the given spender can transfer a given token ID - * @param _spender address of the spender to query - * @param _tokenId uint256 ID of the token to be transferred - * @return bool whether the msg.sender is approved for the given token ID, - * is an operator of the owner, or is the owner of the token - */ - function isApprovedOrOwner( - address _spender, - uint256 _tokenId - ) - internal - view - returns (bool) - { - address owner = ownerOf(_tokenId); - // Disable solium check because of - // https://github.com/duaraghav8/Solium/issues/175 - // solium-disable-next-line operator-whitespace - return ( - _spender == owner || - getApproved(_tokenId) == _spender || - isApprovedForAll(owner, _spender) - ); - } - - /** - * @dev Internal function to mint a new token - * Reverts if the given token ID already exists - * @param _to The address that will own the minted token - * @param _tokenId uint256 ID of the token to be minted by the msg.sender - */ - function _mint(address _to, uint256 _tokenId) internal { - require(_to != address(0)); - addTokenTo(_to, _tokenId); - emit Transfer(address(0), _to, _tokenId); - } - - /** - * @dev Internal function to burn a specific token - * Reverts if the token does not exist - * @param _tokenId uint256 ID of the token being burned by the msg.sender - */ - function _burn(address _owner, uint256 _tokenId) internal { - clearApproval(_owner, _tokenId); - removeTokenFrom(_owner, _tokenId); - emit Transfer(_owner, address(0), _tokenId); - } - - /** - * @dev Internal function to clear current approval of a given token ID - * Reverts if the given address is not indeed the owner of the token - * @param _owner owner of the token - * @param _tokenId uint256 ID of the token to be transferred - */ - function clearApproval(address _owner, uint256 _tokenId) internal { - require(ownerOf(_tokenId) == _owner); - if (tokenApprovals[_tokenId] != address(0)) { - tokenApprovals[_tokenId] = address(0); - } - } - - /** - * @dev Internal function to add a token ID to the list of a given address - * @param _to address representing the new owner of the given token ID - * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address - */ - function addTokenTo(address _to, uint256 _tokenId) internal { - require(tokenOwner[_tokenId] == address(0)); - tokenOwner[_tokenId] = _to; - ownedTokensCount[_to] = safeAdd(ownedTokensCount[_to], 1); - } - - /** - * @dev Internal function to remove a token ID from the list of a given address - * @param _from address representing the previous owner of the given token ID - * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address - */ - function removeTokenFrom(address _from, uint256 _tokenId) internal { - require(ownerOf(_tokenId) == _from); - ownedTokensCount[_from] = safeSub(ownedTokensCount[_from], 1); - tokenOwner[_tokenId] = address(0); - } - - /** - * @dev Internal function to invoke `onERC721Received` on a target address - * The call is not executed if the target address is not a contract - * @param _from address representing the previous owner of the given token ID - * @param _to target address that will receive the tokens - * @param _tokenId uint256 ID of the token to be transferred - * @param _data bytes optional data to send along with the call - * @return whether the call correctly returned the expected magic value - */ - function checkAndCallSafeTransfer( - address _from, - address _to, - uint256 _tokenId, - bytes _data - ) - internal - returns (bool) - { - uint256 receiverCodeSize; - assembly { - receiverCodeSize := extcodesize(_to) - } - if (receiverCodeSize == 0) { - return true; - } - bytes4 retval = IERC721Receiver(_to).onERC721Received( - msg.sender, _from, _tokenId, _data); - return (retval == ERC721_RECEIVED); - } -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol b/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol deleted file mode 100644 index 832ff3784..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/WyreERC721Token/ERC721Token.sol +++ /dev/null @@ -1,209 +0,0 @@ -pragma solidity ^0.4.21; - -import "./ERC721.sol"; -import "./ERC721BasicToken.sol"; - - -/** - * @title Full ERC721 Token - * This implementation includes all the required and some optional functionality of the ERC721 standard - * Moreover, it includes approve all functionality using operator terminology - * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md - */ -contract ERC721Token is ERC721BasicToken, ERC721 { - - bytes4 private constant InterfaceId_ERC721Enumerable = 0x780e9d63; - /** - * 0x780e9d63 === - * bytes4(keccak256('totalSupply()')) ^ - * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^ - * bytes4(keccak256('tokenByIndex(uint256)')) - */ - - bytes4 private constant InterfaceId_ERC721Metadata = 0x5b5e139f; - /** - * 0x5b5e139f === - * bytes4(keccak256('name()')) ^ - * bytes4(keccak256('symbol()')) ^ - * bytes4(keccak256('tokenURI(uint256)')) - */ - - // Token name - string internal name_; - - // Token symbol - string internal symbol_; - - // Mapping from owner to list of owned token IDs - mapping(address => uint256[]) internal ownedTokens; - - // Mapping from token ID to index of the owner tokens list - mapping(uint256 => uint256) internal ownedTokensIndex; - - // Array with all token ids, used for enumeration - uint256[] internal allTokens; - - // Mapping from token id to position in the allTokens array - mapping(uint256 => uint256) internal allTokensIndex; - - // Optional mapping for token URIs - mapping(uint256 => string) internal tokenURIs; - - /** - * @dev Constructor function - */ - function initialize(string _name, string _symbol) public { - name_ = _name; - symbol_ = _symbol; - } - - /** - * @dev Gets the token name - * @return string representing the token name - */ - function name() external view returns (string) { - return name_; - } - - /** - * @dev Gets the token symbol - * @return string representing the token symbol - */ - function symbol() external view returns (string) { - return symbol_; - } - - /** - * @dev Returns an URI for a given token ID - * Throws if the token ID does not exist. May return an empty string. - * @param _tokenId uint256 ID of the token to query - */ - function tokenURI(uint256 _tokenId) public view returns (string) { - require(exists(_tokenId)); - return tokenURIs[_tokenId]; - } - - /** - * @dev Gets the token ID at a given index of the tokens list of the requested owner - * @param _owner address owning the tokens list to be accessed - * @param _index uint256 representing the index to be accessed of the requested tokens list - * @return uint256 token ID at the given index of the tokens list owned by the requested address - */ - function tokenOfOwnerByIndex( - address _owner, - uint256 _index - ) - public - view - returns (uint256) - { - require(_index < balanceOf(_owner)); - return ownedTokens[_owner][_index]; - } - - /** - * @dev Gets the total amount of tokens stored by the contract - * @return uint256 representing the total amount of tokens - */ - function totalSupply() public view returns (uint256) { - return allTokens.length; - } - - /** - * @dev Gets the token ID at a given index of all the tokens in this contract - * Reverts if the index is greater or equal to the total number of tokens - * @param _index uint256 representing the index to be accessed of the tokens list - * @return uint256 token ID at the given index of the tokens list - */ - function tokenByIndex(uint256 _index) public view returns (uint256) { - require(_index < totalSupply()); - return allTokens[_index]; - } - - /** - * @dev Internal function to set the token URI for a given token - * Reverts if the token ID does not exist - * @param _tokenId uint256 ID of the token to set its URI - * @param _uri string URI to assign - */ - function _setTokenURI(uint256 _tokenId, string _uri) internal { - require(exists(_tokenId)); - tokenURIs[_tokenId] = _uri; - } - - /** - * @dev Internal function to add a token ID to the list of a given address - * @param _to address representing the new owner of the given token ID - * @param _tokenId uint256 ID of the token to be added to the tokens list of the given address - */ - function addTokenTo(address _to, uint256 _tokenId) internal { - super.addTokenTo(_to, _tokenId); - uint256 length = ownedTokens[_to].length; - ownedTokens[_to].push(_tokenId); - ownedTokensIndex[_tokenId] = length; - } - - /** - * @dev Internal function to remove a token ID from the list of a given address - * @param _from address representing the previous owner of the given token ID - * @param _tokenId uint256 ID of the token to be removed from the tokens list of the given address - */ - function removeTokenFrom(address _from, uint256 _tokenId) internal { - super.removeTokenFrom(_from, _tokenId); - - uint256 tokenIndex = ownedTokensIndex[_tokenId]; - uint256 lastTokenIndex = safeSub(ownedTokens[_from].length, 1); - uint256 lastToken = ownedTokens[_from][lastTokenIndex]; - - ownedTokens[_from][tokenIndex] = lastToken; - ownedTokens[_from][lastTokenIndex] = 0; - // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to - // be zero. Then we can make sure that we will remove _tokenId from the ownedTokens list since we are first swapping - // the lastToken to the first position, and then dropping the element placed in the last position of the list - - ownedTokens[_from].length--; - ownedTokensIndex[_tokenId] = 0; - ownedTokensIndex[lastToken] = tokenIndex; - } - - /** - * @dev Internal function to mint a new token - * Reverts if the given token ID already exists - * @param _to address the beneficiary that will own the minted token - * @param _tokenId uint256 ID of the token to be minted by the msg.sender - */ - function _mint(address _to, uint256 _tokenId) internal { - super._mint(_to, _tokenId); - - allTokensIndex[_tokenId] = allTokens.length; - allTokens.push(_tokenId); - } - - /** - * @dev Internal function to burn a specific token - * Reverts if the token does not exist - * @param _owner owner of the token to burn - * @param _tokenId uint256 ID of the token being burned by the msg.sender - */ - function _burn(address _owner, uint256 _tokenId) internal { - super._burn(_owner, _tokenId); - - // Clear metadata (if any) - if (bytes(tokenURIs[_tokenId]).length != 0) { - delete tokenURIs[_tokenId]; - } - - // Reorg all tokens array - uint256 tokenIndex = allTokensIndex[_tokenId]; - uint256 lastTokenIndex = safeSub(allTokens.length, 1); - uint256 lastToken = allTokens[lastTokenIndex]; - - allTokens[tokenIndex] = lastToken; - allTokens[lastTokenIndex] = 0; - - allTokens.length--; - allTokensIndex[_tokenId] = 0; - allTokensIndex[lastToken] = tokenIndex; - } - -} \ No newline at end of file diff --git a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol b/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol deleted file mode 100644 index 65ea99d0c..000000000 --- a/packages/contracts/contracts/tokens/YesComplianceToken/YesComplianceToken.sol +++ /dev/null @@ -1,648 +0,0 @@ -pragma solidity ^0.4.24; - -import "./IYesComplianceToken.sol"; - -/** - * draft implementation of YES compliance token - * - * NOTE: i have done relatively few gas optimization tweaks (beyond using the sturctures necessary to avoid any - * linear time procedures). - * in some cases i am using a call structure which replicates some checks. this is for code clarity/security - - * i marked a few obvious ones which could be optimized for gas, but :meh: - * - * todo static owner should follow owner token? remove static owner? :security: :should: - * @author Tyson Malchow - */ -contract YesComplianceToken is YesComplianceTokenV1 { - - uint64 private constant MAX_TOKENS_PER_ENTITY = 10240; // completely arbitrary limit - uint64 private constant MAX_ENTITIES = 2**32-1; // bc using 32 bit index tracking - uint64 private constant MAX_VALIDATORS_PER_MARK = 2**32-1; // bc using 32 bit index tracking - uint64 private constant TOTAL_YES_MARKS = 255; // bc 'uint8 yes' - - // todo could shorten the entity IDs to anything 160+ to make this cheaper? - - /** @notice a single YES attestation */ - struct YesMark { - - /** @notice ISO-3166-1 country codes */ - uint16 countryCode; - - /** @notice the possibly-country-speicifc YES being marked. */ - uint8 yes; - - // 8 bits more space in this slot.. could upgrade yes to uint16? - - /** @notice the index of this mark in EntityRecord.yesMarks */ - uint32 yesMarkIdx; - - /** a list of the validator entities which have attested to this mark */ - uint256[] validatorEntityIds; - - /** @notice index of each validator entity ID in validatorEntityIds */ - mapping(uint256 => uint32) validatorEntityIdIdx; - - // uint8 entityListIdx; - } - - /** - * tracks the state for a single recognized entity - */ - struct EntityRecord { - - /** true marking this entity ID has been encountered */ - bool init; - - /** when true, this entity is effectively useless */ - bool locked; - - // 30 bits more space in this slot - - /** position of the entityId in allEntityIds */ - uint32 entityIdIdx; - - /** used for creating reliable token IDs, monotonically increasing */ - uint64 tokenIdCounter; - - /** indexed YES mark lookups */ - mapping(bytes4 => YesMark) yesMarkByKey; - - /** raw collection of all marks keys */ - bytes4[] yesMarkKeys; - - /** all tokens associated with this identity */ - uint256[] tokenIds; - - // trellis/tower connection ? - // civic connection ? - // erc725/735 connection ? - } - - /** - * @notice all fields we want to add per-token. - * - * there may never be more than just control flag, in which case it may make sense to collapse this - * to just a mapping(uint256 => bool) ? - */ - struct TokenRecord { - - /** position of the tokenId in EntityRecord.tokenIds */ - uint32 tokenIdIdx; - - /** true if this token has administrative superpowers (aka is _not_ limited) */ - bool control; - - /** true if this token cannot move */ - bool finalized; - - // 30 bits more in this slot - - // limitations: in/out? - } - - address public ownerAddress; - - mapping(uint256 => TokenRecord) public tokenRecordById; - mapping(uint256 => EntityRecord) public entityRecordById; - mapping(uint256 => uint256) public entityIdByTokenId; - - /** for entity enumeration. maximum of 2^256-1 total entities (i think we'll be ok) */ - uint256[] entityIds; - - constructor() public { - /* this space intentionally left blank */ - } - - /** - * constructor alternative: first-time initialization the contract/token (required because of upgradeability) - */ - function initialize(string _name, string _symbol) { - // require(super._symbol.length == 0 || _symbol == super._symbol); // cannot change symbol after first init bc that could fuck shit up - super.initialize(_name, _symbol); // init token info - - // grant the owner token - mint_I(ownerAddress, OWNER_ENTITY_ID, true); - - // ecosystem owner gets both owner and validator marks (self-attested) - setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_OWNER); - setYes_I(OWNER_ENTITY_ID, OWNER_ENTITY_ID, 0, YESMARK_VALIDATOR); - } - - /** - * executed in lieu of a constructor in a delegated context - */ - function _upgradeable_initialize() public { - - // some things are still tied to the owner (instead of the yesmark_owner :notsureif:) - ownerAddress = msg.sender; - } - - // YesComplianceTokenV1 Interface Methods -------------------------------------------------------------------------- - - function isYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view returns(bool) { - return isYes_I(_validatorEntityId, _address, _countryCode, _yes); - } - - function requireYes(uint256 _validatorEntityId, address _address, uint16 _countryCode, uint8 _yes) external view { - require(isYes_I(_validatorEntityId, _address, _countryCode, _yes)); - } - - function getYes(uint256 _validatorEntityId, address _address, uint16 _countryCode) external view returns(uint8[] memory) { - if(balanceOf(_address) == 0) - return new uint8[](0); - - uint256 entityId = entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; - EntityRecord storage e = entityRecordById[entityId]; - uint256 j = 0; - uint256 i; - - // locked always bails - if(e.locked) - return new uint8[](0); - - uint8[] memory r = new uint8[](e.yesMarkKeys.length); - - for(i = 0; i < e.yesMarkKeys.length; i++) { - YesMark storage m = e.yesMarkByKey[e.yesMarkKeys[i]]; - - // filter country code - if(m.countryCode != _countryCode) - continue; - - // filter explicit validator entity - if(_validatorEntityId > 0 - && m.validatorEntityIdIdx[_validatorEntityId] == 0 - && (m.validatorEntityIds.length == 0 || m.validatorEntityIds[0] == _validatorEntityId)) - continue; - - // matched, chyess - r[j++] = m.yes; - } - - // reduce array length - assembly { mstore(r, j) } - - return r; - } - - function mint(address _to, uint256 _entityId, bool _control) external returns (uint256) /* internally protected */{ - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - - // make sure caller has a control token, at the least - require(tokenRecordById[callerTokenId].control, 'control token required'); - - // determine/validate the entity being minted for - uint256 realEntityId; - if(_entityId == 0 || _entityId == callerEntityId) { - // unspecified entity, or caller entity, can do! - realEntityId = callerEntityId; - - } else { - // otherwise make sure caller is a VALIDATOR, else fail - require(senderIsControlValidator(), 'illegal entity id'); // some duplicate checks/lookups, gas leak - realEntityId = _entityId; - } - - return mint_I(_to, realEntityId, _control); - } - - function mint(address _to, uint256 _entityId, bool _control, uint16 _countryCode, uint8[] _yes) external returns (uint256) /* internally protected */ { - // lazy warning: this is a 90% copy/paste job from the mint directly above this - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - - // make sure caller has a control token, at the least - require(tokenRecordById[callerTokenId].control, 'control token required'); - - // determine/validate the entity being minted for - uint256 realEntityId; - if(_entityId == 0 || _entityId == callerEntityId) { - // unspecified entity, or caller entity, can do! - realEntityId = callerEntityId; - - } else { - // otherwise make sure caller is a VALIDATOR, else fail - require(senderIsControlValidator()); // some duplicate checks/lookups, gas leak - realEntityId = _entityId; - } - - // mint the coin - uint256 tokenId = mint_I(_to, realEntityId, _control); - - // now set the attestations - require(_yes.length <= TOTAL_YES_MARKS); // safety - for(uint256 i = 0; i<_yes.length; i++) { - setYes_I(_entityId, _countryCode, _yes[i]); - } - - return tokenId; - } - - function getEntityId(address _address) external view returns (uint256) { - return entityIdByTokenId[tokenOfOwnerByIndex(_address, 0)]; - } - - function burn(uint256 _tokenId) external permission_control_tokenId(_tokenId) { - uint256 entityId = entityIdByTokenId[_tokenId]; - - EntityRecord storage e = entity(entityId); - TokenRecord storage t = tokenRecordById[_tokenId]; - - // remove token from entity - e.tokenIds[t.tokenIdIdx] = e.tokenIds[e.tokenIds.length - 1]; - e.tokenIds.length--; - - // update tracked index (of swapped, if present) - if(e.tokenIds.length > t.tokenIdIdx) - tokenRecordById[e.tokenIds[t.tokenIdIdx]].tokenIdIdx = t.tokenIdIdx; - - // remove token record - delete tokenRecordById[_tokenId]; - - // burn the actual token - super._burn(tokenOwner[_tokenId], _tokenId); - } - - function burnEntity(uint256 _entityId) external permission_control_entityId(_entityId) { // self-burn allowed - EntityRecord storage e = entity(_entityId); - - // burn all the tokens - for(uint256 i = 0; i < e.tokenIds.length; i++) { - uint256 tokenId = e.tokenIds[i]; - super._burn(tokenOwner[tokenId], tokenId); - } - - // clear all the marks - clearYes_I(_entityId); - - // clear out entity record - e.init = false; - e.locked = false; - e.entityIdIdx = 0; - e.tokenIdCounter = 0; - - assert(e.yesMarkKeys.length == 0); - assert(e.tokenIds.length == 0); - } - - function setYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { - setYes_I(_entityId, _countryCode, _yes); - } - - function clearYes(uint256 _entityId, uint16 _countryCode, uint8 _yes) external permission_validator { - require(_yes > 0); - require(_yes != 128); - - // special check against reserved country code 0 - if(_countryCode == 0) - require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); // this is duplicating some things, gas leak - - EntityRecord storage e = entity(_entityId); - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - bytes4 key = yesKey(_countryCode, _yes); - - YesMark storage mark = e.yesMarkByKey[key]; - if(mark.yes == 0) - return; // not set by anyone, bail happily - - if(mark.validatorEntityIdIdx[callerEntityId] == 0 && - (mark.validatorEntityIds.length == 0 || mark.validatorEntityIds[0] != callerEntityId)) { - // set, but not by this validator, bail happily - return; - } - - clearYes_I(mark, e, callerEntityId); - } - - function clearYes(uint256 _entityId, uint16 _countryCode) external permission_validator { - // special check against 129 validator mark - if(_countryCode == 0) - require(senderIsEcosystemControl(), 'not authorized as ecosystem control (129)'); // this is duplicating some things, gas leak - - EntityRecord storage e = entity(_entityId); - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - uint256 i; - - for(i =0; i idx) - mark.validatorEntityIdIdx[mark.validatorEntityIds[idx]] = idx; - - // check if the entire mark needs deleting - if(mark.validatorEntityIds.length == 0) { - // yes, it does. swap/delete - idx = mark.yesMarkIdx; - e.yesMarkKeys[idx] = e.yesMarkKeys[e.yesMarkKeys.length - 1]; - e.yesMarkKeys.length--; - - // remap - if(e.yesMarkKeys.length > idx) - e.yesMarkByKey[e.yesMarkKeys[idx]].yesMarkIdx = idx; - - // delete mark - mark.countryCode = 0; - mark.yes = 0; - mark.yesMarkIdx = 0; - // assert(mark.validatorEntityIds.length == 0); - - return true; - } - - return false; - } - - function clearYes_I(uint256 _entityId) internal { - require(_entityId != OWNER_ENTITY_ID); - - EntityRecord storage e = entity(_entityId); - - // only ecosystem control can touch validators - if(!senderIsEcosystemControl()) - require(e.yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes == 0); - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - uint256 i; - - for(i =0; i 0 - || m.validatorEntityIds.length > 0 && m.validatorEntityIds[0] == _validatorEntityId; - } - - function setYes_I(uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { - require(_yes > 0); - require(_yes != 128); - - // special check against 129 validator mark - if(_yes == 129) - require(senderIsEcosystemControl()); // this is duplicating some checks, gas leak - - uint256 callerTokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 callerEntityId = entityIdByTokenId[callerTokenId]; - - setYes_I(callerEntityId, _entityId, _countryCode, _yes); - } - - function setYes_I(uint256 _validatorEntityId, uint256 _entityId, uint16 _countryCode, uint8 _yes) internal { - // assert(_yes > 0); - EntityRecord storage targetEntity = entity(_entityId); - - // locate existing mark - bytes4 key = yesKey(_countryCode, _yes); - YesMark storage mark = targetEntity.yesMarkByKey[key]; - - if(mark.yes == 0) { - require(targetEntity.yesMarkKeys.length < TOTAL_YES_MARKS); - - // new mark on the entity - mark.countryCode = _countryCode; - mark.yes = _yes; - mark.yesMarkIdx = uint32(targetEntity.yesMarkKeys.length); - targetEntity.yesMarkKeys.push(key); - - } else if(mark.validatorEntityIdIdx[_validatorEntityId] > 0 || - (mark.validatorEntityIds.length > 0 && mark.validatorEntityIds[0] == _validatorEntityId)) { - - // existing mark and the caller is already on it - /* - i'm inclined to make it do nothing in this case (instead of failing) since i'm not at this point positive how best - to distinguish error types to a caller, which would be required for a caller to know wtf to do in this case - (otherwise they need to query blockchain again) - (but that costs gas... :notsureif:) - */ - return; - } - - require(mark.validatorEntityIds.length < MAX_VALIDATORS_PER_MARK); - - // add this validator to the mark - mark.validatorEntityIdIdx[_validatorEntityId] = uint32(mark.validatorEntityIds.length); - mark.validatorEntityIds.push(_validatorEntityId); - } - - /** non-permissed internal minting impl */ - function mint_I(address _to, uint256 _entityId, bool _control) internal returns (uint256) { - EntityRecord storage e = entity(_entityId); - require(e.tokenIds.length < MAX_TOKENS_PER_ENTITY, 'token limit reached'); - require(e.tokenIdCounter < 2**64-1); // kind of ridiculous but whatever, safety first! - uint256 tokenId = uint256(keccak256(abi.encodePacked(_entityId, e.tokenIdCounter++))); - super._mint(_to, tokenId); - tokenRecordById[tokenId].tokenIdIdx = uint32(e.tokenIds.length); - tokenRecordById[tokenId].control = _control; - e.tokenIds.push(tokenId); - entityIdByTokenId[tokenId] = _entityId; - return tokenId; - } - - /** entity resolution (creation when needed) */ - function entity(uint256 _entityId) internal returns (EntityRecord storage) { - require(_entityId > 0); - EntityRecord storage e = entityRecordById[_entityId]; - if(e.init) return e; - require(entityIds.length < MAX_ENTITIES); - e.init = true; - e.entityIdIdx = uint32(entityIds.length); - entityIds.push(_entityId); - return e; - } - - /** override default addTokenTo for additional transaction limitations */ - function addTokenTo(address _to, uint256 _tokenId) internal { - uint256 entityId = entityIdByTokenId[_tokenId]; - - // ensure one owner cannot be associated with multiple entities - // NOTE: this breaks hotwallet integrations, at this point necessarily so - if(balanceOf(_to) > 0) { - uint256 prevEntityId = entityIdByTokenId[tokenOfOwnerByIndex(_to, 0)]; - require(prevEntityId == entityId, 'conflicting entities'); - } - - require(!tokenRecordById[_tokenId].finalized, 'token is finalized'); - - super.addTokenTo(_to, _tokenId); - } - - /** the sender is the same entity as the one specified */ - function senderIsEntity_ByEntityId(uint256 _entityId) internal view returns (bool) { - return _entityId == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; - } - - /** the sender is the same entity as the one specified, and the sender is a control for that entity */ - function senderIsControl_ByEntityId(uint256 _entityId) internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 senderEntityId = entityIdByTokenId[tokenId]; - return _entityId == senderEntityId && tokenRecordById[tokenId].control; - } - - /** the sender is a non-locked validator via control token */ - function senderIsControlValidator() internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - uint256 tokenId = tokenOfOwnerByIndex(msg.sender, 0); - uint256 senderEntityId = entityIdByTokenId[tokenId]; - EntityRecord storage e = entityRecordById[senderEntityId]; - return tokenRecordById[tokenId].control - && !e.locked - && entityRecordById[senderEntityId].yesMarkByKey[yesKey(0, YESMARK_VALIDATOR)].yes > 0; - } - - /** the sender is the same entity as the one tied to the token specified */ - function senderIsEntity_ByTokenId(uint256 _tokenId) internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - return entityIdByTokenId[_tokenId] == entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; - } - - /** the sender is the same entity as the one tied to the token specified, and the sender is a control for that entity */ - function senderIsControl_ByTokenId(uint256 _tokenId) internal view returns (bool) { - if(balanceOf(msg.sender) == 0) - return false; - uint256 senderEntityId = entityIdByTokenId[tokenOfOwnerByIndex(msg.sender, 0)]; - return entityIdByTokenId[_tokenId] == senderEntityId && tokenRecordById[_tokenId].control; - } - - /** checks if sender is the singular ecosystem owner */ - function senderIsEcosystemControl() internal view returns (bool) { - // todo deprecate ownerAddress ?! - return msg.sender == ownerAddress || senderIsControl_ByEntityId(OWNER_ENTITY_ID); - } - - /** a key for a YES attestation mark */ - function yesKey(uint16 _countryCode, uint8 _yes) internal pure returns(bytes4) { - return bytes4(keccak256(abi.encodePacked(_countryCode, _yes))); - } - - // PERMISSIONS MODIFIERS ---------------------------------------------------------------- - - modifier permission_validator { - require(senderIsControlValidator(), 'not authorized as validator'); - _; - } - - modifier permission_super { - require(senderIsEcosystemControl(), 'not authorized as ecosystem control'); - _; - } - -// modifier permission_access_entityId(uint256 _entityId) { -// require(senderIsEcosystemControl() || senderIsEntity_ByEntityId(_entityId)); -// _; -// } - - modifier permission_control_entityId(uint256 _entityId) { - require(senderIsEcosystemControl() || senderIsControl_ByEntityId(_entityId), 'not authorized entity controller'); - _; - } - - modifier permission_access_tokenId(uint256 _tokenId) { - require(senderIsEcosystemControl() || senderIsEntity_ByTokenId(_tokenId)); - _; - } - - modifier permission_control_tokenId(uint256 _tokenId) { - require(senderIsEcosystemControl() || senderIsControl_ByTokenId(_tokenId), 'not authorized token controller'); - _; - } - -} \ No newline at end of file diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index d0e902eda..8e78ed992 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -11,7 +11,6 @@ import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; import { ExchangeContract } from '../../generated-wrappers/exchange'; import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; -import { YesComplianceTokenContract } from '../../generated-wrappers/yes_compliance_token'; import { artifacts } from '../../src/artifacts'; import { @@ -112,6 +111,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ] = accounts); // Create wrappers erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); + let compliantAddresses = _.cloneDeepWith(usedAddresses); + _.remove(compliantAddresses, (address: string) => { + return address === nonCompliantAddress; + }); + const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner); // Deploy ERC20 tokens const numDummyErc20ToDeploy = 3; let erc20TokenA: DummyERC20TokenContract; @@ -123,12 +127,6 @@ describe.only(ContractName.BalanceThresholdFilter, () => { defaultMakerAssetAddress = erc20TokenA.address; defaultTakerAssetAddress = erc20TokenB.address; zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Deploy Yes Token - const yesTokenInstance = await YesComplianceTokenContract.deployFrom0xArtifactAsync( - artifacts.YesComplianceToken, - provider, - txDefaults, - ); // Create proxies const erc20Proxy = await erc20Wrapper.deployProxyAsync(); await erc20Wrapper.setBalancesAndAllowancesAsync(); @@ -146,14 +144,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { from: owner, }); // Deploy Compliant Forwarder - const erc721BalanceThreshold = new BigNumber(1); + const balanceThreshold = new BigNumber(1); + await erc721Wrapper.deployProxyAsync(); + const [balanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); + await erc721Wrapper.setBalancesAndAllowancesAsync(); + const balance = await balanceThresholdAsset.balanceOf.callAsync(compliantTakerAddress); compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, exchangeInstance.address, - yesTokenInstance.address, - erc721BalanceThreshold + balanceThresholdAsset.address, + balanceThreshold ); // Default order parameters const defaultOrderParams = { @@ -199,43 +201,6 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ); forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); */ - // Initialize Yes Token - await yesTokenInstance._upgradeable_initialize.sendTransactionAsync({ from: owner }); - const yesTokenName = 'YesToken'; - const yesTokenTicker = 'YEET'; - await yesTokenInstance.initialize.sendTransactionAsync(yesTokenName, yesTokenTicker, { from: owner }); - // Verify Maker / Taker - const addressesCanControlTheirToken = true; - const compliantMakerCountryCode = new BigNumber(519); - const compliantMakerYesMark = new BigNumber(1); - const compliantMakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantMakerAddress, - compliantMakerEntityId, - addressesCanControlTheirToken, - compliantMakerCountryCode, - [compliantMakerYesMark], - { from: owner }, - ); - const compliantTakerCountryCode = new BigNumber(519); - const compliantTakerYesMark = new BigNumber(1); - const compliantTakerEntityId = new BigNumber(2); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantTakerAddress, - compliantTakerEntityId, - addressesCanControlTheirToken, - compliantTakerCountryCode, - [compliantTakerYesMark], - { from: owner }, - ); - await yesTokenInstance.mint2.sendTransactionAsync( - compliantMakerAddress2, - compliantTakerEntityId, - addressesCanControlTheirToken, - compliantTakerCountryCode, - [compliantTakerYesMark], - { from: owner }, - ); // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); -- cgit v1.2.3 From 6d673ac942fc1d0df7f8eeb56168ba3a4029878e Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 17:28:20 -0800 Subject: Exchange Selectors - sorted --- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 203 ++++++++++----------- 1 file changed, 96 insertions(+), 107 deletions(-) (limited to 'packages') diff --git a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol index d4c9bef28..c361fd075 100644 --- a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol +++ b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol @@ -20,143 +20,132 @@ pragma solidity 0.4.24; contract ExchangeSelectors { - // filled - bytes4 constant filledSelector = 0x288cdc91; - bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); - // batchFillOrders - bytes4 constant batchFillOrdersSelector = 0x297bb70b; - bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + // allowedValidators + bytes4 constant allowedValidatorsSelector = 0x7b8e3514; + bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); - // cancelled - bytes4 constant cancelledSelector = 0x2ac12622; - bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); + // assetProxies + bytes4 constant assetProxiesSelector = 0x3fd3c997; + bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); - // preSign - bytes4 constant preSignSelector = 0x3683ef8e; - bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); + // batchCancelOrders + bytes4 constant batchCancelOrdersSelector = 0x4ac14782; + bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - // matchOrders - bytes4 constant matchOrdersSelector = 0x3c28d861; - bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); + // batchFillOrKillOrders + bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; + bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - // fillOrderNoThrow - bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; - bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + // batchFillOrders + bytes4 constant batchFillOrdersSelector = 0x297bb70b; + bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - // assetProxies - bytes4 constant assetProxiesSelector = 0x3fd3c997; - bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); + // batchFillOrdersNoThrow + bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; + bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - // batchCancelOrders - bytes4 constant batchCancelOrdersSelector = 0x4ac14782; - bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + // cancelOrder + bytes4 constant cancelOrderSelector = 0xd46b02c3; + bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - // batchFillOrKillOrders - bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; - bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + // cancelOrdersUpTo + bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; + bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); - // cancelOrdersUpTo - bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; - bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); + // cancelled + bytes4 constant cancelledSelector = 0x2ac12622; + bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); - // batchFillOrdersNoThrow - bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; - bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); + // currentContextAddress + bytes4 constant currentContextAddressSelector = 0xeea086ba; + bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); - // getAssetProxy - bytes4 constant getAssetProxySelector = 0x60704108; - bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); + // executeTransaction + bytes4 constant executeTransactionSelector = 0xbfc8bfce; + bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); - // transactions - bytes4 constant transactionsSelector = 0x642f2eaf; - bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); + // fillOrKillOrder + bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; + bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - // fillOrKillOrder - bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; - bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + // fillOrder + bytes4 constant fillOrderSelector = 0xb4be83d5; + bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - // setSignatureValidatorApproval - bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; - bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); + // fillOrderNoThrow + bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; + bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - // allowedValidators - bytes4 constant allowedValidatorsSelector = 0x7b8e3514; - bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); + // filled + bytes4 constant filledSelector = 0x288cdc91; + bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); - // marketSellOrders - bytes4 constant marketSellOrdersSelector = 0x7e1d9808; - bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // getAssetProxy + bytes4 constant getAssetProxySelector = 0x60704108; + bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); - // getOrdersInfo - bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; - bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); + // getOrderInfo + bytes4 constant getOrderInfoSelector = 0xc75e0a81; + bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - // preSigned - bytes4 constant preSignedSelector = 0x82c174d0; - bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); + // getOrdersInfo + bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; + bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - // owner - bytes4 constant ownerSelector = 0x8da5cb5b; - bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); + // isValidSignature + bytes4 constant isValidSignatureSelector = 0x93634702; + bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); - // isValidSignature - bytes4 constant isValidSignatureSelector = 0x93634702; - bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); + // marketBuyOrders + bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; + bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // marketBuyOrdersNoThrow - bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; - bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // marketBuyOrdersNoThrow + bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; + bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // fillOrder - bytes4 constant fillOrderSelector = 0xb4be83d5; - bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); + // marketSellOrders + bytes4 constant marketSellOrdersSelector = 0x7e1d9808; + bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // executeTransaction - bytes4 constant executeTransactionSelector = 0xbfc8bfce; - bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); + // marketSellOrdersNoThrow + bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; + bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - // registerAssetProxy - bytes4 constant registerAssetProxySelector = 0xc585bb93; - bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); + // matchOrders + bytes4 constant matchOrdersSelector = 0x3c28d861; + bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); - // getOrderInfo - bytes4 constant getOrderInfoSelector = 0xc75e0a81; - bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + // orderEpoch + bytes4 constant orderEpochSelector = 0xd9bfa73e; + bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); - // cancelOrder - bytes4 constant cancelOrderSelector = 0xd46b02c3; - bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); + // owner + bytes4 constant ownerSelector = 0x8da5cb5b; + bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); - // orderEpoch - bytes4 constant orderEpochSelector = 0xd9bfa73e; - bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); + // preSign + bytes4 constant preSignSelector = 0x3683ef8e; + bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); - // ZRX_ASSET_DATA - bytes4 constant ZRX_ASSET_DATASelector = 0xdb123b1a; - bytes4 constant ZRX_ASSET_DATASelectorGenerator = bytes4(keccak256('ZRX_ASSET_DATA()')); + // preSigned + bytes4 constant preSignedSelector = 0x82c174d0; + bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); - // marketSellOrdersNoThrow - bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; - bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // registerAssetProxy + bytes4 constant registerAssetProxySelector = 0xc585bb93; + bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); - // EIP712_DOMAIN_HASH - bytes4 constant EIP712_DOMAIN_HASHSelector = 0xe306f779; - bytes4 constant EIP712_DOMAIN_HASHSelectorGenerator = bytes4(keccak256('EIP712_DOMAIN_HASH()')); + // setSignatureValidatorApproval + bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; + bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); - // marketBuyOrders - bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; - bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); + // transactions + bytes4 constant transactionsSelector = 0x642f2eaf; + bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); - // currentContextAddress - bytes4 constant currentContextAddressSelector = 0xeea086ba; - bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); - - // transferOwnership - bytes4 constant transferOwnershipSelector = 0xf2fde38b; - bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); - - // VERSION - bytes4 constant VERSIONSelector = 0xffa1ad74; - bytes4 constant VERSIONSelectorGenerator = bytes4(keccak256('VERSION()')); + // transferOwnership + bytes4 constant transferOwnershipSelector = 0xf2fde38b; + bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); } \ No newline at end of file -- cgit v1.2.3 From 8799f9bb90c6bcfffc74550d74ed87422467f9e1 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Mon, 10 Dec 2018 18:25:19 -0800 Subject: Test for ERC20 balance threshold --- .../test/extensions/balance_threshold_filter.ts | 260 ++++++++++++--------- 1 file changed, 146 insertions(+), 114 deletions(-) (limited to 'packages') diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts index 8e78ed992..c4723e7aa 100644 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ b/packages/contracts/test/extensions/balance_threshold_filter.ts @@ -1,6 +1,6 @@ import { BlockchainLifecycle } from '@0x/dev-utils'; import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder } from '@0x/types'; +import { RevertReason, SignedOrder, Order } from '@0x/types'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; @@ -64,9 +64,10 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let nonCompliantOrderFactory: OrderFactory; let erc20Wrapper: ERC20Wrapper; let erc20Balances: ERC20BalancesByOwner; - let takerBalanceThresholdWrapper: BalanceThresholdWrapper; - let makerBalanceThresholdWrapper: BalanceThresholdWrapper; - let nonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc20TakerBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc721TakerBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc721MakerBalanceThresholdWrapper: BalanceThresholdWrapper; + let erc721NonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; let takerTransactionFactory: TransactionFactory; let makerTransactionFactory: TransactionFactory; @@ -77,11 +78,14 @@ describe.only(ContractName.BalanceThresholdFilter, () => { let logDecoder: LogDecoder; let exchangeInternals: TestExchangeInternalsContract; + let defaultOrderParams: Partial; + const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - let compliantForwarderInstance: BalanceThresholdFilterContract; + let erc721CompliantForwarderInstance: BalanceThresholdFilterContract; + let erc20CompliantForwarderInstance: BalanceThresholdFilterContract; const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => { expect(txReceipt.logs.length).to.be.gte(1); @@ -117,10 +121,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner); // Deploy ERC20 tokens - const numDummyErc20ToDeploy = 3; + const numDummyErc20ToDeploy = 4; let erc20TokenA: DummyERC20TokenContract; let erc20TokenB: DummyERC20TokenContract; - [erc20TokenA, erc20TokenB, zrxToken] = await erc20Wrapper.deployDummyTokensAsync( + let erc20BalanceThresholdAsset: DummyERC20TokenContract; + [erc20TokenA, erc20TokenB, zrxToken, erc20BalanceThresholdAsset] = await erc20Wrapper.deployDummyTokensAsync( numDummyErc20ToDeploy, constants.DUMMY_TOKEN_DECIMALS, ); @@ -144,21 +149,30 @@ describe.only(ContractName.BalanceThresholdFilter, () => { from: owner, }); // Deploy Compliant Forwarder - const balanceThreshold = new BigNumber(1); + const erc721alanceThreshold = new BigNumber(1); await erc721Wrapper.deployProxyAsync(); - const [balanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); + const [erc721BalanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); await erc721Wrapper.setBalancesAndAllowancesAsync(); - const balance = await balanceThresholdAsset.balanceOf.callAsync(compliantTakerAddress); - compliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + erc721CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( + artifacts.BalanceThresholdFilter, + provider, + txDefaults, + exchangeInstance.address, + erc721BalanceThresholdAsset.address, + erc721alanceThreshold + ); + const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10); + erc20CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( artifacts.BalanceThresholdFilter, provider, txDefaults, exchangeInstance.address, - balanceThresholdAsset.address, - balanceThreshold + erc20BalanceThresholdAsset.address, + erc20BalanceThreshold ); + // Default order parameters - const defaultOrderParams = { + defaultOrderParams = { exchangeAddress: exchangeInstance.address, feeRecipientAddress, makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), @@ -167,7 +181,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { takerAssetAmount, makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, }; const defaultOrderParams1 = { makerAddress: compliantMakerAddress, @@ -192,20 +206,11 @@ describe.only(ContractName.BalanceThresholdFilter, () => { defaultOrderParams, }; nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams); - - /* - const compliantForwarderContract = new BalanceThresholdFilterContract( - compliantForwarderInstance.abi, - compliantForwarderInstance.address, - provider, - ); - forwarderWrapper = new ForwarderWrapper(compliantForwarderContract, provider); - */ // Create Valid/Invalid orders const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, }); const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( compliantSignedOrder, @@ -219,35 +224,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { compliantSignedOrderWithoutExchangeAddressData, ); - /* - _.each(exchangeInstance.abi, (abiDefinition: AbiDefinition) => { - try { - const method = new Method(abiDefinition as MethodAbi); - console.log(method.getSignature()); - if (!method.getSignature().startsWith('matchOrders')) { - return; - } - console.log(`FOUND IT`); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - compliantSignedOrder, - ); - const args = [signedOrderWithoutExchangeAddress, signedOrderWithoutExchangeAddress, compliantSignedOrder.signature, compliantSignedOrder.signature]; - console.log(method.encode(args, {annotate: true})); - //console.log('\n', `// ${method.getDataItem().name}`); - //console.log(`bytes4 constant ${method.getDataItem().name}Selector = ${method.getSelector()};`); - //console.log(`bytes4 constant ${method.getDataItem().name}SelectorGenerator = byes4(keccak256('${method.getSignature()}'));`); - } catch(e) { - console.log(`encoding failed: ${e}`); - } - }); - throw new Error(`w`);*/ logDecoder = new LogDecoder(web3Wrapper); - takerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - makerBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); + erc20TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc20CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + erc721TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); + erc721MakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); + erc721NonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - nonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(compliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - - // Instantiate internal exchange contract exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( artifacts.TestExchangeInternals, @@ -262,7 +244,57 @@ describe.only(ContractName.BalanceThresholdFilter, () => { await blockchainLifecycle.revertAsync(); }); - describe('General Sanity Checks', () => { + describe.only('General Sanity Checks', () => { + beforeEach(async () => { + erc20Balances = await erc20Wrapper.getBalancesAsync(); + compliantSignedOrder = await orderFactory.newSignedOrderAsync(); + compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); + }); + it.only('should transfer the correct amounts and validate both maker/taker when both maker and taker exceed the balance threshold of an ERC20 token', async () => { + const compliantSignedOrderERC20Sender = await orderFactory.newSignedOrderAsync({ + ... + defaultOrderParams, + makerAddress: compliantMakerAddress, + senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(), + }); + // Execute a valid fill + const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrderERC20Sender, compliantTakerAddress, {takerAssetFillAmount}); + // Assert validated addresses + const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; + assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); + // Check balances + const newBalances = await erc20Wrapper.getBalancesAsync(); + const makerAssetFillAmount = takerAssetFillAmount + .times(compliantSignedOrder.makerAssetAmount) + .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); + const makerFeePaid = compliantSignedOrder.makerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + const takerFeePaid = compliantSignedOrder.takerFee + .times(makerAssetFillAmount) + .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); + expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), + ); + expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), + ); + expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), + ); + expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), + ); + expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( + erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), + ); + }); it('should revert if the signed transaction is not intended for supported', async () => { // Create signed order without the fillOrder function selector const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); @@ -273,7 +305,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( compliantSignedFillOrderTx.salt, compliantSignedFillOrderTx.signerAddress, txDataBufWithBadSelectorHex, @@ -298,7 +330,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { signedOrderWithoutExchangeAddressData, ); // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(compliantForwarderInstance.executeTransaction.sendTransactionAsync( + return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( signedFillOrderTx.salt, signedFillOrderTx.signerAddress, signedFillOrderTx.data, @@ -318,7 +350,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -380,7 +412,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrdersAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -392,7 +424,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( orders, nonCompliantAddress, {takerAssetFillAmounts} @@ -412,7 +444,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -474,7 +506,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -486,7 +518,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( orders, nonCompliantAddress, {takerAssetFillAmounts} @@ -506,7 +538,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -568,7 +600,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -580,7 +612,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, nonCompliantAddress, {takerAssetFillAmounts} @@ -593,7 +625,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( + erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmounts} @@ -610,7 +642,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await takerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -650,12 +682,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrderAsync( + erc721TakerBalanceThresholdWrapper.fillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -665,7 +697,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrderAsync( + erc721NonCompliantBalanceThresholdWrapper.fillOrderAsync( compliantSignedOrder, nonCompliantAddress, {takerAssetFillAmount} @@ -682,7 +714,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill - const txReceipt = await takerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -722,12 +754,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrderNoThrowAsync( + erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -737,7 +769,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( compliantSignedOrder, nonCompliantAddress, {takerAssetFillAmount} @@ -755,7 +787,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { // Execute a valid fill const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await takerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -795,12 +827,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrKillOrderAsync( + erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( signedOrderWithBadMakerAddress, compliantTakerAddress, {takerAssetFillAmount} @@ -810,7 +842,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( + erc721NonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( compliantSignedOrder, nonCompliantAddress, {takerAssetFillAmount} @@ -821,7 +853,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if takerAssetFillAmount is not fully filled', async () => { const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.fillOrKillOrderAsync( + erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: tooBigTakerAssetFillAmount} @@ -841,7 +873,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -901,7 +933,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketSellOrdersAsync( + erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -912,7 +944,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if taker does not meet the balance threshold', async () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( orders, nonCompliantAddress, {takerAssetFillAmount} @@ -932,7 +964,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute a valid fill const orders = [compliantSignedOrder, compliantSignedOrder2]; const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -992,7 +1024,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( + erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, compliantTakerAddress, {takerAssetFillAmount} @@ -1003,7 +1035,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if taker does not meet the balance threshold', async () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( orders, nonCompliantAddress, {takerAssetFillAmount} @@ -1027,7 +1059,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1084,7 +1116,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketBuyOrdersAsync( + erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1096,7 +1128,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( orders, nonCompliantAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1120,7 +1152,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { .times(compliantSignedOrder.makerAssetAmount) .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); + const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); // Assert validated addresses const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1177,7 +1209,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { // Execute transaction const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, compliantTakerAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1189,7 +1221,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { const orders = [compliantSignedOrder, compliantSignedOrder2]; const dummyMakerAssetFillAmount = new BigNumber(0); return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( + erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( orders, nonCompliantAddress, {makerAssetFillAmount: dummyMakerAssetFillAmount} @@ -1240,7 +1272,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% }; - const txReceipt = await takerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); + const txReceipt = await erc721TakerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); // Assert validated addresses const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); @@ -1292,12 +1324,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if left maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.matchOrdersAsync( + erc721TakerBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, signedOrderWithBadMakerAddress, compliantTakerAddress, @@ -1308,12 +1340,12 @@ describe.only(ContractName.BalanceThresholdFilter, () => { it('should revert if right maker does not meet the balance threshold', async () => { // Create signed order with non-compliant maker address const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: compliantForwarderInstance.address, + senderAddress: erc721CompliantForwarderInstance.address, makerAddress: nonCompliantAddress }); // Execute transaction return expectTransactionFailedAsync( - takerBalanceThresholdWrapper.matchOrdersAsync( + erc721TakerBalanceThresholdWrapper.matchOrdersAsync( signedOrderWithBadMakerAddress, compliantSignedOrder, compliantTakerAddress, @@ -1323,7 +1355,7 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('should revert if taker does not meet the balance threshold', async () => { return expectTransactionFailedAsync( - nonCompliantBalanceThresholdWrapper.matchOrdersAsync( + erc721NonCompliantBalanceThresholdWrapper.matchOrdersAsync( compliantSignedOrder, compliantSignedOrder, nonCompliantAddress, @@ -1341,30 +1373,30 @@ describe.only(ContractName.BalanceThresholdFilter, () => { }); it('Should successfully cancel order if maker meets balance threshold', async () => { // Verify order is not cancelled - const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); // Cancel - const txReceipt = await makerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); it('Should successfully cancel order if maker does not meet balance threshold', async () => { // Create order where maker does not meet balance threshold const signedOrderWithBadMakerAddress = await nonCompliantOrderFactory.newSignedOrderAsync({}); // Verify order is not cancelled - const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); // Cancel - const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1382,17 +1414,17 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel - const txReceipt = await makerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1405,17 +1437,17 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel - const txReceipt = await nonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); }); }); @@ -1434,18 +1466,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await makerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); + const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await makerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) + const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) const saltAsBigNumber = new BigNumber(salt); if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); @@ -1463,18 +1495,18 @@ describe.only(ContractName.BalanceThresholdFilter, () => { ]; // Verify orders are not cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); }); // Cancel const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await nonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); + const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); // Assert validated addresses const expectedValidatedAddresseses: string[] = []; assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); // Check that order was cancelled await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await nonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) + const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) const saltAsBigNumber = new BigNumber(salt); if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); -- cgit v1.2.3 From 4417c76b130655da3b14209148191d43037e8f8a Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 14:47:52 -0800 Subject: Ported Balance Threshold Filter to new contract directory structure --- .../BalanceThresholdFilter.sol | 44 - .../MixinBalanceThresholdFilterCore.sol | 319 ---- .../interfaces/IThresholdAsset.sol | 30 - .../mixins/MBalanceThresholdFilterCore.sol | 81 -- .../test/extensions/balance_threshold_filter.ts | 1521 -------------------- .../test/utils/balance_threshold_wrapper.ts | 247 ---- 6 files changed, 2242 deletions(-) delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol delete mode 100644 packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol delete mode 100644 packages/contracts/test/extensions/balance_threshold_filter.ts delete mode 100644 packages/contracts/test/utils/balance_threshold_wrapper.ts (limited to 'packages') diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol deleted file mode 100644 index bf4a94509..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/BalanceThresholdFilter.sol +++ /dev/null @@ -1,44 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "../../protocol/Exchange/interfaces/IExchange.sol"; -import "./interfaces/IThresholdAsset.sol"; -import "./MixinBalanceThresholdFilterCore.sol"; - - -contract BalanceThresholdFilter is MixinBalanceThresholdFilterCore { - - /// @dev Constructs BalanceThresholdFilter. - /// @param exchange Address of 0x exchange. - /// @param thresholdAsset The asset that must be held by makers/takers. - /// @param thresholdBalance The minimum balance of `thresholdAsset` that must be held by makers/takers. - constructor( - address exchange, - address thresholdAsset, - uint256 thresholdBalance - ) - public - { - EXCHANGE = IExchange(exchange); - THRESHOLD_ASSET = IThresholdAsset(thresholdAsset); - THRESHOLD_BALANCE = thresholdBalance; - } -} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol deleted file mode 100644 index 0ad8ccddf..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/MixinBalanceThresholdFilterCore.sol +++ /dev/null @@ -1,319 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "./mixins/MBalanceThresholdFilterCore.sol"; - - -contract MixinBalanceThresholdFilterCore is MBalanceThresholdFilterCore { - - /// @dev Executes an Exchange transaction iff the maker and taker meet - /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR - /// the exchange function is a cancellation. - /// Supported Exchange functions: - /// - batchFillOrders - /// - batchFillOrdersNoThrow - /// - batchFillOrKillOrders - /// - fillOrder - /// - fillOrderNoThrow - /// - fillOrKillOrder - /// - marketBuyOrders - /// - marketBuyOrdersNoThrow - /// - marketSellOrders - /// - marketSellOrdersNoThrow - /// - matchOrders - /// - cancelOrder - /// - batchCancelOrders - /// - cancelOrdersUpTo - /// Trying to call any other exchange function will throw. - /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signerAddress Address of transaction signer. - /// @param signedExchangeTransaction AbiV2 encoded calldata. - /// @param signature Proof of signer transaction by signer. - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external - { - // Validate addresses. - validateBalanceThresholdsOrRevert(); - - // All addresses are valid. Execute fillOrder. - EXCHANGE.executeTransaction( - salt, - signerAddress, - signedExchangeTransaction, - signature - ); - } - - /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` - /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold - /// then this function will revert. Which addresses are validated depends on - /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). - /// No parameters are taken as this function reads arguments directly from calldata, to save gas. - /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all - /// of the addresses whose balance thresholds it checked. - function validateBalanceThresholdsOrRevert() - internal - { - // Addresses that are validated below. - address[] memory validatedAddresses; - - ///// Do not add variables after this point. ///// - ///// The assembly block may overwrite their values. ///// - - // Validate addresses - assembly { - /// @dev Emulates the `calldataload` opcode on the embedded Exchange calldata, - /// which is accessed through `signedExchangeTransaction`. - /// @param offset - Offset into the Exchange calldata. - /// @return value - Corresponding 32 byte value stored at `offset`. - function exchangeCalldataload(offset) -> value { - // Pointer to exchange transaction - // 0x04 for calldata selector - // 0x40 to access `signedExchangeTransaction`, which is the third parameter - let exchangeTxPtr := calldataload(0x44) - - // Offset into Exchange calldata - // We compute this by adding 0x24 to the `exchangeTxPtr` computed above. - // 0x04 for calldata selector - // 0x20 for length field of `signedExchangeTransaction` - let exchangeCalldataOffset := add(exchangeTxPtr, add(0x24, offset)) - value := calldataload(exchangeCalldataOffset) - } - - /// @dev Convenience function that skips the 4 byte selector when loading - /// from the embedded Exchange calldata. - /// @param offset - Offset into the Exchange calldata (minus the 4 byte selector) - /// @return value - Corresponding 32 byte value stored at `offset` + 4. - function loadExchangeData(offset) -> value { - value := exchangeCalldataload(add(offset, 0x4)) - } - - /// @dev A running list is maintained of addresses to validate. - /// This function records an address in this array. - /// @param addressToValidate - Address to record for validation. - function recordAddressToValidate(addressToValidate) { - // Compute `addressesToValidate` memory offset - let addressesToValidate_ := mload(0x40) - let nAddressesToValidate_ := mload(addressesToValidate_) - - // Increment length - nAddressesToValidate_ := add(mload(addressesToValidate_), 0x01) - mstore(addressesToValidate_, nAddressesToValidate_) - - // Append address to validate - let offset := mul(nAddressesToValidate_, 0x20) - mstore(add(addressesToValidate_, offset), addressToValidate) - } - - /// @dev Extracts the maker address from an order stored in the Exchange calldata - /// (which is embedded in `signedExchangeTransaction`), and records it in - /// the running list of addresses to validate. - /// @param orderParamIndex - Index of the order in the Exchange function's signature - function recordMakerAddressFromOrder(orderParamIndex) { - let orderPtr := loadExchangeData(mul(orderParamIndex, 0x20)) - let makerAddress := loadExchangeData(orderPtr) - recordAddressToValidate(makerAddress) - } - - /// @dev Extracts the maker addresses from an array of orders stored in the Exchange calldata - /// (which is embedded in `signedExchangeTransaction`), and records them in - /// the running list of addresses to validate. - /// @param orderArrayParamIndex - Index of the order array in the Exchange function's signature - function recordMakerAddressesFromOrderArray(orderArrayParamIndex) { - let orderArrayPtr := loadExchangeData(mul(orderArrayParamIndex, 0x20)) - let orderArrayLength := loadExchangeData(orderArrayPtr) - let orderArrayElementPtr := add(orderArrayPtr, 0x20) - let orderArrayElementEndPtr := add(orderArrayElementPtr, mul(orderArrayLength, 0x20)) - for {let orderPtrOffset := orderArrayElementPtr} lt(orderPtrOffset, orderArrayElementEndPtr) {orderPtrOffset := add(orderPtrOffset, 0x20)} { - let orderPtr := loadExchangeData(orderPtrOffset) - let makerAddress := loadExchangeData(add(orderPtr, orderArrayElementPtr)) - recordAddressToValidate(makerAddress) - } - } - - /// @dev Records address of signer in the running list of addresses to validate. - /// @note: We cannot access `signerAddress` directly from within the asm function, - /// so it is loaded from the calldata. - function recordSignerAddress() { - // Load the signer address from calldata - // 0x04 for selector - // 0x20 to access `signerAddress`, which is the second parameter. - let signerAddress_ := calldataload(0x24) - recordAddressToValidate(signerAddress_) - } - - /// @dev Records addresses to be validated when Exchange transaction is a batch fill variant. - /// This is one of: batchFillOrders, batchFillOrKillOrders, batchFillNoThrow - /// Reference signature: (Order[],uint256[],bytes[]) - function recordAddressesForBatchFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /// @dev Records addresses to be validated when Exchange transaction is a fill order variant. - /// This is one of: fillOrder, fillOrKillOrder, fillOrderNoThrow - /// Reference signature: (Order,uint256,bytes) - function recordAddressesForFillOrderVariant() { - // Record maker address from the order (param index 0) - // The signer is the taker for this order and must also be validated. - recordMakerAddressFromOrder(0) - recordSignerAddress() - } - - /// @dev Records addresses to be validated when Exchange transaction is a market fill variant. - /// This is one of: marketBuyOrders, marketBuyOrdersNoThrow, marketSellOrders, marketSellOrdersNoThrow - /// Reference signature: (Order[],uint256,bytes[]) - function recordAddressesForMarketFillVariant() { - // Record maker addresses from order array (parameter index 0) - // The signer is the taker for these orders and must also be validated. - recordMakerAddressesFromOrderArray(0) - recordSignerAddress() - } - - /// @dev Records addresses to be validated when Exchange transaction is matchOrders. - /// Reference signature: matchOrders(Order,Order) - function recordAddressesForMatchOrders() { - // Record maker address from both orders (param indices 0 & 1). - // The signer is the taker and must also be validated. - recordMakerAddressFromOrder(0) - recordMakerAddressFromOrder(1) - recordSignerAddress() - } - - ///// Record Addresses to Validate ///// - - // Addresses needing validation depends on which Exchange function is being called. - // Step 1/2 Read the exchange function selector. - let exchangeFunctionSelector := and( - exchangeCalldataload(0x0), - 0xffffffff00000000000000000000000000000000000000000000000000000000 - ) - - // Step 2/2 Extract addresses to validate based on this selector. - // See ../../utils/ExchangeSelectors/ExchangeSelectors.sol for selectors - switch exchangeFunctionSelector - case 0x297bb70b00000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrders - case 0x50dde19000000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrdersNoThrow - case 0x4d0ae54600000000000000000000000000000000000000000000000000000000 { recordAddressesForBatchFillVariant() } // batchFillOrKillOrders - case 0xb4be83d500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrder - case 0x3e228bae00000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrderNoThrow - case 0x64a3bc1500000000000000000000000000000000000000000000000000000000 { recordAddressesForFillOrderVariant() } // fillOrKillOrder - case 0xe5fa431b00000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrders - case 0xa3e2038000000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketBuyOrdersNoThrow - case 0x7e1d980800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrders - case 0xdd1c7d1800000000000000000000000000000000000000000000000000000000 { recordAddressesForMarketFillVariant() } // marketSellOrdersNoThrow - case 0x3c28d86100000000000000000000000000000000000000000000000000000000 { recordAddressesForMatchOrders() } // matchOrders - case 0xd46b02c300000000000000000000000000000000000000000000000000000000 {} // cancelOrder - case 0x4ac1478200000000000000000000000000000000000000000000000000000000 {} // batchCancelOrders - case 0x4f9559b100000000000000000000000000000000000000000000000000000000 {} // cancelOrdersUpTo - default { - // Revert with `Error("INVALID_OR_BLOCKED_EXCHANGE_SELECTOR")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x00000024494e56414c49445f4f525f424c4f434b45445f45584348414e47455f) - mstore(0x60, 0x53454c4543544f52000000000000000000000000000000000000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(INVALID_OR_BLOCKED_EXCHANGE_SELECTOR) rounded up to nearest 32-byte word. - revert(0, 132) - } - - ///// Validate Recorded Addresses ///// - - // Load from memory the addresses to validate - let addressesToValidate := mload(0x40) - let addressesToValidateLength := mload(addressesToValidate) - let addressesToValidateElementPtr := add(addressesToValidate, 0x20) - let addressesToValidateElementEndPtr := add(addressesToValidateElementPtr, mul(addressesToValidateLength, 0x20)) - - // Set free memory pointer to after `addressesToValidate` array. - // This is to avoid corruption when making calls in the loop below. - let freeMemPtr := addressesToValidateElementEndPtr - mstore(0x40, freeMemPtr) - - // Validate addresses - let thresholdAssetAddress := sload(THRESHOLD_ASSET_slot) - let thresholdBalance := sload(THRESHOLD_BALANCE_slot) - for {let addressToValidate := addressesToValidateElementPtr} lt(addressToValidate, addressesToValidateElementEndPtr) {addressToValidate := add(addressToValidate, 0x20)} { - // Construct calldata for `THRESHOLD_ASSET.balanceOf` - mstore(freeMemPtr, 0x70a0823100000000000000000000000000000000000000000000000000000000) - mstore(add(freeMemPtr, 0x04), mload(addressToValidate)) - - // call `THRESHOLD_ASSET.balanceOf` - let success := call( - gas, // forward all gas - thresholdAssetAddress, // call address of asset proxy - 0, // don't send any ETH - freeMemPtr, // pointer to start of input - 0x24, // length of input (one padded address) - freeMemPtr, // write output to next free memory offset - 0x20 // reserve space for return balance (0x20 bytes) - ) - if eq(success, 0) { - // @TODO Revert with `Error("BALANCE_QUERY_FAILED")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000001442414c414e43455f51554552595f4641494c45440000000000000000) - mstore(0x60, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 32 -- strlen(BALANCE_QUERY_FAILED) rounded up to nearest 32-byte word. - revert(0, 100) - } - - // Revert if balance not held - let addressBalance := mload(freeMemPtr) - if lt(addressBalance, thresholdBalance) { - // Revert with `Error("AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD")` - mstore(0x00, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x20, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(0x40, 0x0000003441545f4c454153545f4f4e455f414444524553535f444f45535f4e4f) - mstore(0x60, 0x545f4d4545545f42414c414e43455f5448524553484f4c440000000000000000) - mstore(0x80, 0x00000000) - // Revert length calculation: - // 4 -- error selector - // 32 -- offset to string - // 32 -- string length field - // 64 -- strlen(AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD) rounded up to nearest 32-byte word. - revert(0, 132) - } - } - - // Record validated addresses - validatedAddresses := addressesToValidate - } - - ///// If we hit this point then all addresses are valid ///// - emit ValidatedAddresses(validatedAddresses); - } -} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol deleted file mode 100644 index 61acaba0a..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/interfaces/IThresholdAsset.sol +++ /dev/null @@ -1,30 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - -contract IThresholdAsset { - - /// @param _owner The address from which the balance will be retrieved - /// @return Balance of owner - function balanceOf(address _owner) - external - view - returns (uint256); - -} \ No newline at end of file diff --git a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol b/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol deleted file mode 100644 index 612fd481c..000000000 --- a/packages/contracts/contracts/extensions/BalanceThresholdFilter/mixins/MBalanceThresholdFilterCore.sol +++ /dev/null @@ -1,81 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; -pragma experimental ABIEncoderV2; - -import "../../../protocol/Exchange/interfaces/IExchange.sol"; -import "../interfaces/IThresholdAsset.sol"; - - -contract MBalanceThresholdFilterCore { - - // Points to 0x exchange contract - IExchange internal EXCHANGE; - - // The asset that must be held by makers/takers - IThresholdAsset internal THRESHOLD_ASSET; - - // The minimum balance of `THRESHOLD_ASSET` that must be held by makers/takers - uint256 internal THRESHOLD_BALANCE; - - // Addresses that hold at least `THRESHOLD_BALANCE` of `THRESHOLD_ASSET` - event ValidatedAddresses ( - address[] addresses - ); - - /// @dev Executes an Exchange transaction iff the maker and taker meet - /// the hold at least `BALANCE_THRESHOLD` of the asset `THRESHOLD_ASSET` OR - /// the exchange function is a cancellation. - /// Supported Exchange functions: - /// - batchFillOrders - /// - batchFillOrdersNoThrow - /// - batchFillOrKillOrders - /// - fillOrder - /// - fillOrderNoThrow - /// - fillOrKillOrder - /// - marketBuyOrders - /// - marketBuyOrdersNoThrow - /// - marketSellOrders - /// - marketSellOrdersNoThrow - /// - matchOrders - /// - cancelOrder - /// - batchCancelOrders - /// - cancelOrdersUpTo - /// Trying to call any other exchange function will throw. - /// @param salt Arbitrary number to ensure uniqueness of transaction hash. - /// @param signerAddress Address of transaction signer. - /// @param signedExchangeTransaction AbiV2 encoded calldata. - /// @param signature Proof of signer transaction by signer. - function executeTransaction( - uint256 salt, - address signerAddress, - bytes signedExchangeTransaction, - bytes signature - ) - external; - - /// @dev Validates addresses meet the balance threshold specified by `BALANCE_THRESHOLD` - /// for the asset `THRESHOLD_ASSET`. If one address does not meet the thresold - /// then this function will revert. Which addresses are validated depends on - /// which Exchange function is to be called (defined by `signedExchangeTransaction` above). - /// No parameters are taken as this function reads arguments directly from calldata, to save gas. - /// If all addresses are valid then this function emits a ValidatedAddresses event, listing all - /// of the addresses whose balance thresholds it checked. - function validateBalanceThresholdsOrRevert() internal; -} \ No newline at end of file diff --git a/packages/contracts/test/extensions/balance_threshold_filter.ts b/packages/contracts/test/extensions/balance_threshold_filter.ts deleted file mode 100644 index c4723e7aa..000000000 --- a/packages/contracts/test/extensions/balance_threshold_filter.ts +++ /dev/null @@ -1,1521 +0,0 @@ -import { BlockchainLifecycle } from '@0x/dev-utils'; -import { assetDataUtils } from '@0x/order-utils'; -import { RevertReason, SignedOrder, Order } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import * as chai from 'chai'; -import * as ethUtil from 'ethereumjs-util'; -import * as _ from 'lodash'; -import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; - -import { DummyERC20TokenContract } from '../../generated-wrappers/dummy_erc20_token'; -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; - -import { artifacts } from '../../src/artifacts'; -import { - expectTransactionFailedAsync, - expectTransactionFailedWithoutReasonAsync, -} from '../utils/assertions'; -import { chaiSetup } from '../utils/chai_setup'; -import { constants } from '../utils/constants'; -import { ERC20Wrapper } from '../utils/erc20_wrapper'; -import { ExchangeWrapper } from '../utils/exchange_wrapper'; -import { MatchOrderTester } from '../utils/match_order_tester'; -import { OrderFactory } from '../utils/order_factory'; -import { orderUtils } from '../utils/order_utils'; -import { TransactionFactory } from '../utils/transaction_factory'; -import { BalanceThresholdWrapper } from '../utils/balance_threshold_wrapper'; -import { ContractName, ERC20BalancesByOwner, SignedTransaction, OrderStatus } from '../utils/types'; -import { provider, txDefaults, web3Wrapper } from '../utils/web3_wrapper'; -import { TestExchangeInternalsContract } from '../../generated-wrappers/test_exchange_internals'; - -import { MethodAbi, AbiDefinition } from 'ethereum-types'; -import { AbiEncoder } from '@0x/utils'; -import { Method } from '@0x/utils/lib/src/abi_encoder'; -import { LogDecoder } from '../utils/log_decoder'; -import { ERC721Wrapper } from '../utils/erc721_wrapper'; - -chaiSetup.configure(); -const expect = chai.expect; -const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper); -const DECIMALS_DEFAULT = 18; - -interface ValidatedAddressesLog { - args: {addresses: string[]} -} - -describe.only(ContractName.BalanceThresholdFilter, () => { - let compliantMakerAddress: string; - let compliantMakerAddress2: string; - let owner: string; - let compliantTakerAddress: string; - let feeRecipientAddress: string; - let nonCompliantAddress: string; - let defaultMakerAssetAddress: string; - let defaultTakerAssetAddress: string; - let zrxAssetData: string; - let zrxToken: DummyERC20TokenContract; - let exchangeInstance: ExchangeContract; - let exchangeWrapper: ExchangeWrapper; - - let orderFactory: OrderFactory; - let orderFactory2: OrderFactory; - let nonCompliantOrderFactory: OrderFactory; - let erc20Wrapper: ERC20Wrapper; - let erc20Balances: ERC20BalancesByOwner; - let erc20TakerBalanceThresholdWrapper: BalanceThresholdWrapper; - let erc721TakerBalanceThresholdWrapper: BalanceThresholdWrapper; - let erc721MakerBalanceThresholdWrapper: BalanceThresholdWrapper; - let erc721NonCompliantBalanceThresholdWrapper: BalanceThresholdWrapper; - - let takerTransactionFactory: TransactionFactory; - let makerTransactionFactory: TransactionFactory; - let compliantSignedOrder: SignedOrder; - let compliantSignedOrder2: SignedOrder; - let compliantSignedFillOrderTx: SignedTransaction; - - let logDecoder: LogDecoder; - let exchangeInternals: TestExchangeInternalsContract; - - let defaultOrderParams: Partial; - - const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(500), DECIMALS_DEFAULT); - const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(1000), DECIMALS_DEFAULT); - const takerAssetFillAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(250), DECIMALS_DEFAULT); - - let erc721CompliantForwarderInstance: BalanceThresholdFilterContract; - let erc20CompliantForwarderInstance: BalanceThresholdFilterContract; - - const assertValidatedAddressesLog = async (txReceipt: TransactionReceiptWithDecodedLogs, expectedValidatedAddresses: string[]) => { - expect(txReceipt.logs.length).to.be.gte(1); - const validatedAddressesLog = (txReceipt.logs[0] as any) as ValidatedAddressesLog; - const validatedAddresses = validatedAddressesLog.args.addresses; - // @HACK-hysz: Nested addresses are not translated to lower-case but this will change once - // the new ABI Encoder/Decoder is used by the contract templates. - let validatedAddressesNormalized: string[] = []; - _.each(validatedAddresses, (address) => { - const normalizedAddress = _.toLower(address); - validatedAddressesNormalized.push(normalizedAddress); - }); - expect(validatedAddressesNormalized).to.be.deep.equal(expectedValidatedAddresses); - }; - - before(async () => { - // Create accounts - await blockchainLifecycle.startAsync(); - const accounts = await web3Wrapper.getAvailableAddressesAsync(); - const usedAddresses = ([ - owner, - compliantMakerAddress, - compliantMakerAddress2, - compliantTakerAddress, - feeRecipientAddress, - nonCompliantAddress, - ] = accounts); - // Create wrappers - erc20Wrapper = new ERC20Wrapper(provider, usedAddresses, owner); - let compliantAddresses = _.cloneDeepWith(usedAddresses); - _.remove(compliantAddresses, (address: string) => { - return address === nonCompliantAddress; - }); - const erc721Wrapper = new ERC721Wrapper(provider, compliantAddresses, owner); - // Deploy ERC20 tokens - const numDummyErc20ToDeploy = 4; - let erc20TokenA: DummyERC20TokenContract; - let erc20TokenB: DummyERC20TokenContract; - let erc20BalanceThresholdAsset: DummyERC20TokenContract; - [erc20TokenA, erc20TokenB, zrxToken, erc20BalanceThresholdAsset] = await erc20Wrapper.deployDummyTokensAsync( - numDummyErc20ToDeploy, - constants.DUMMY_TOKEN_DECIMALS, - ); - defaultMakerAssetAddress = erc20TokenA.address; - defaultTakerAssetAddress = erc20TokenB.address; - zrxAssetData = assetDataUtils.encodeERC20AssetData(zrxToken.address); - // Create proxies - const erc20Proxy = await erc20Wrapper.deployProxyAsync(); - await erc20Wrapper.setBalancesAndAllowancesAsync(); - // Deploy Exchange congtract - exchangeInstance = await ExchangeContract.deployFrom0xArtifactAsync( - artifacts.Exchange, - provider, - txDefaults, - zrxAssetData, - ); - exchangeWrapper = new ExchangeWrapper(exchangeInstance, provider); - // Register proxies - await exchangeWrapper.registerAssetProxyAsync(erc20Proxy.address, owner); - await erc20Proxy.addAuthorizedAddress.sendTransactionAsync(exchangeInstance.address, { - from: owner, - }); - // Deploy Compliant Forwarder - const erc721alanceThreshold = new BigNumber(1); - await erc721Wrapper.deployProxyAsync(); - const [erc721BalanceThresholdAsset] = await erc721Wrapper.deployDummyTokensAsync(); - await erc721Wrapper.setBalancesAndAllowancesAsync(); - erc721CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( - artifacts.BalanceThresholdFilter, - provider, - txDefaults, - exchangeInstance.address, - erc721BalanceThresholdAsset.address, - erc721alanceThreshold - ); - const erc20BalanceThreshold = Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 10); - erc20CompliantForwarderInstance = await BalanceThresholdFilterContract.deployFrom0xArtifactAsync( - artifacts.BalanceThresholdFilter, - provider, - txDefaults, - exchangeInstance.address, - erc20BalanceThresholdAsset.address, - erc20BalanceThreshold - ); - - // Default order parameters - defaultOrderParams = { - exchangeAddress: exchangeInstance.address, - feeRecipientAddress, - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - makerAssetAmount, - takerAssetAmount, - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), DECIMALS_DEFAULT), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(150), DECIMALS_DEFAULT), - senderAddress: erc721CompliantForwarderInstance.address, - }; - const defaultOrderParams1 = { - makerAddress: compliantMakerAddress, - ... - defaultOrderParams, - } - const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress)]; - takerTransactionFactory = new TransactionFactory(makerPrivateKey, exchangeInstance.address); - orderFactory = new OrderFactory(makerPrivateKey, defaultOrderParams1); - const defaultOrderParams2 = { - makerAddress: compliantMakerAddress2, - ... - defaultOrderParams, - } - const secondMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantMakerAddress2)]; - orderFactory2 = new OrderFactory(secondMakerPrivateKey, defaultOrderParams2); - - const nonCompliantPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(nonCompliantAddress)]; - const defaultNonCompliantOrderParams = { - makerAddress: nonCompliantAddress, - ... - defaultOrderParams, - }; - nonCompliantOrderFactory = new OrderFactory(nonCompliantPrivateKey, defaultNonCompliantOrderParams); - // Create Valid/Invalid orders - const takerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(compliantTakerAddress)]; - takerTransactionFactory = new TransactionFactory(takerPrivateKey, exchangeInstance.address); - compliantSignedOrder = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - }); - const compliantSignedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - compliantSignedOrder, - ); - const compliantSignedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - compliantSignedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - compliantSignedFillOrderTx = takerTransactionFactory.newSignedTransaction( - compliantSignedOrderWithoutExchangeAddressData, - ); - - logDecoder = new LogDecoder(web3Wrapper); - erc20TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc20CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - erc721TakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(takerPrivateKey, exchangeInstance.address), provider); - erc721MakerBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(makerPrivateKey, exchangeInstance.address), provider); - erc721NonCompliantBalanceThresholdWrapper = new BalanceThresholdWrapper(erc721CompliantForwarderInstance, exchangeInstance, new TransactionFactory(nonCompliantPrivateKey, exchangeInstance.address), provider); - - // Instantiate internal exchange contract - exchangeInternals = await TestExchangeInternalsContract.deployFrom0xArtifactAsync( - artifacts.TestExchangeInternals, - provider, - txDefaults, - ); - }); - beforeEach(async () => { - await blockchainLifecycle.startAsync(); - }); - afterEach(async () => { - await blockchainLifecycle.revertAsync(); - }); - - describe.only('General Sanity Checks', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it.only('should transfer the correct amounts and validate both maker/taker when both maker and taker exceed the balance threshold of an ERC20 token', async () => { - const compliantSignedOrderERC20Sender = await orderFactory.newSignedOrderAsync({ - ... - defaultOrderParams, - makerAddress: compliantMakerAddress, - senderAddress: erc20TakerBalanceThresholdWrapper.getBalanceThresholdAddress(), - }); - // Execute a valid fill - const txReceipt = await erc20TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrderERC20Sender, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if the signed transaction is not intended for supported', async () => { - // Create signed order without the fillOrder function selector - const txDataBuf = ethUtil.toBuffer(compliantSignedFillOrderTx.data); - const selectorLengthInBytes = 4; - const txDataBufMinusSelector = txDataBuf.slice(selectorLengthInBytes); - const badSelector = '0x00000000'; - const badSelectorBuf = ethUtil.toBuffer(badSelector); - const txDataBufWithBadSelector = Buffer.concat([badSelectorBuf, txDataBufMinusSelector]); - const txDataBufWithBadSelectorHex = ethUtil.bufferToHex(txDataBufWithBadSelector); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( - compliantSignedFillOrderTx.salt, - compliantSignedFillOrderTx.signerAddress, - txDataBufWithBadSelectorHex, - compliantSignedFillOrderTx.signature, - )); - }); - it('should revert if senderAddress is not set to the compliant forwarding contract', async () => { - // Create signed order with incorrect senderAddress - const notBalanceThresholdFilterAddress = zrxToken.address; - const signedOrderWithBadSenderAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: notBalanceThresholdFilterAddress, - }); - const signedOrderWithoutExchangeAddress = orderUtils.getOrderWithoutExchangeAddress( - signedOrderWithBadSenderAddress, - ); - const signedOrderWithoutExchangeAddressData = exchangeInstance.fillOrder.getABIEncodedTransactionData( - signedOrderWithoutExchangeAddress, - takerAssetFillAmount, - compliantSignedOrder.signature, - ); - const signedFillOrderTx = takerTransactionFactory.newSignedTransaction( - signedOrderWithoutExchangeAddressData, - ); - // Call compliant forwarder - return expectTransactionFailedWithoutReasonAsync(erc721CompliantForwarderInstance.executeTransaction.sendTransactionAsync( - signedFillOrderTx.salt, - signedFillOrderTx.signerAddress, - signedFillOrderTx.data, - signedFillOrderTx.signature, - )); - }); - // @TODO - greater than 1 balance - }); - - describe('batchFillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) - .times(2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('batchFillOrdersNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) - .times(2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrdersNoThrowAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('batchFillOrKillOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const txReceipt = await erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmounts}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const cumulativeTakerAssetFillAmount = takerAssetFillAmount.times(2); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount) - .times(2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount.times(2)), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.times(2).add(takerFeePaid)), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, takerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmounts} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if one takerAssetFillAmount is not fully filled', async () => { - const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const takerAssetFillAmounts = [takerAssetFillAmount, tooBigTakerAssetFillAmount]; - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.batchFillOrKillOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmounts} - ), - RevertReason.FailedExecution - ); - }); - }); - - describe('fillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrderAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('fillOrderNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrderNoThrowAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrderNoThrowAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('fillOrKillOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both maker/taker when both maker and taker meet the balance threshold', async () => { - // Execute a valid fill - const takerAssetFillAmount_ = compliantSignedOrder.takerAssetAmount; - const txReceipt = await erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync(compliantSignedOrder, compliantTakerAddress, {takerAssetFillAmount: takerAssetFillAmount_}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount = takerAssetFillAmount_ - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid = compliantSignedOrder.makerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee - .times(makerAssetFillAmount) - .dividedToIntegerBy(compliantSignedOrder.makerAssetAmount); - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(makerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(takerAssetFillAmount_), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(makerFeePaid), - ); - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(takerAssetFillAmount_), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(makerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(makerFeePaid.add(takerFeePaid)), - ); - }); - it('should revert if maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( - signedOrderWithBadMakerAddress, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if takerAssetFillAmount is not fully filled', async () => { - const tooBigTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.times(2); - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.fillOrKillOrderAsync( - compliantSignedOrder, - compliantTakerAddress, - {takerAssetFillAmount: tooBigTakerAssetFillAmount} - ), - RevertReason.FailedExecution - ); - }); - }); - - describe('marketSellOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketSellOrdersAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('marketSellOrdersNoThrow', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync(orders, compliantTakerAddress, {takerAssetFillAmount: cumulativeTakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketSellOrdersNoThrowAsync( - orders, - nonCompliantAddress, - {takerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('marketBuyOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketBuyOrdersAsync( - orders, - compliantTakerAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersAsync( - orders, - nonCompliantAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('marketBuyOrdersNoThrowAsync', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('should transfer the correct amounts and validate both makers/taker when both makers and taker meet the balance threshold', async () => { - // Execute a valid fill - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const cumulativeTakerAssetFillAmount = compliantSignedOrder.takerAssetAmount.plus(takerAssetFillAmount); - const makerAssetFillAmount2 = takerAssetFillAmount - .times(compliantSignedOrder.makerAssetAmount) - .dividedToIntegerBy(compliantSignedOrder.takerAssetAmount); - const cumulativeMakerAssetFillAmount = compliantSignedOrder.makerAssetAmount.plus(makerAssetFillAmount2); - const txReceipt = await erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync(orders, compliantTakerAddress, {makerAssetFillAmount: cumulativeMakerAssetFillAmount}); - // Assert validated addresses - const expectedValidatedAddresseses = [compliantSignedOrder.makerAddress, compliantSignedOrder2.makerAddress, compliantSignedFillOrderTx.signerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - const makerFeePaid2 = compliantSignedOrder2.makerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid2 = compliantSignedOrder2.takerFee - .times(makerAssetFillAmount2) - .dividedToIntegerBy(compliantSignedOrder2.makerAssetAmount); - const takerFeePaid = compliantSignedOrder.takerFee.plus(takerFeePaid2); - // Maker #1 - expect(newBalances[compliantMakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultMakerAssetAddress].minus(compliantSignedOrder.makerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][defaultTakerAssetAddress].add(compliantSignedOrder.takerAssetAmount), - ); - expect(newBalances[compliantMakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress][zrxToken.address].minus(compliantSignedOrder.makerFee), - ); - // Maker #2 - expect(newBalances[compliantMakerAddress2][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultMakerAssetAddress].minus(makerAssetFillAmount2), - ); - expect(newBalances[compliantMakerAddress2][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][defaultTakerAssetAddress].add(takerAssetFillAmount), - ); - expect(newBalances[compliantMakerAddress2][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantMakerAddress2][zrxToken.address].minus(makerFeePaid2), - ); - // Taker - expect(newBalances[compliantTakerAddress][defaultTakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultTakerAssetAddress].minus(cumulativeTakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][defaultMakerAssetAddress]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(cumulativeMakerAssetFillAmount), - ); - expect(newBalances[compliantTakerAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[compliantTakerAddress][zrxToken.address].minus(takerFeePaid), - ); - // Fee recipient - expect(newBalances[feeRecipientAddress][zrxToken.address]).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(compliantSignedOrder.makerFee).add(makerFeePaid2).add(takerFeePaid), - ); - }); - it('should revert if one maker does not meet the balance threshold', async () => { - // Create order set with one non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - makerAddress: nonCompliantAddress - }); - const orders = [compliantSignedOrder, signedOrderWithBadMakerAddress]; - // Execute transaction - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( - orders, - compliantTakerAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - const orders = [compliantSignedOrder, compliantSignedOrder2]; - const dummyMakerAssetFillAmount = new BigNumber(0); - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.marketBuyOrdersNoThrowAsync( - orders, - nonCompliantAddress, - {makerAssetFillAmount: dummyMakerAssetFillAmount} - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('matchOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('Should transfer correct amounts when both makers and taker meet the balance threshold', async () => { - // Test values/results taken from Match Orders test: - // 'Should transfer correct amounts when right order is fully filled and values pass isRoundingErrorFloor but fail isRoundingErrorCeil' - // Create orders to match - const signedOrderLeft = await orderFactory.newSignedOrderAsync({ - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(17), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(98), 0), - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - feeRecipientAddress: feeRecipientAddress, - }); - const signedOrderRight = await orderFactory2.newSignedOrderAsync({ - makerAssetData: assetDataUtils.encodeERC20AssetData(defaultTakerAssetAddress), - takerAssetData: assetDataUtils.encodeERC20AssetData(defaultMakerAssetAddress), - makerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - takerAssetAmount: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - makerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - takerFee: Web3Wrapper.toBaseUnitAmount(new BigNumber(1), 18), - feeRecipientAddress: feeRecipientAddress, - }); - // Compute expected transfer amounts - const expectedTransferAmounts = { - // Left Maker - amountSoldByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - amountBoughtByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - feePaidByLeftMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.4705882352941176'), 16), // 76.47% - // Right Maker - amountSoldByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(75), 0), - amountBoughtByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(13), 0), - feePaidByRightMaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - // Taker - amountReceivedByTaker: Web3Wrapper.toBaseUnitAmount(new BigNumber(0), 0), - feePaidByTakerLeft: Web3Wrapper.toBaseUnitAmount(new BigNumber('76.5306122448979591'), 16), // 76.53% - feePaidByTakerRight: Web3Wrapper.toBaseUnitAmount(new BigNumber(100), 16), // 100% - }; - const txReceipt = await erc721TakerBalanceThresholdWrapper.matchOrdersAsync(signedOrderLeft, signedOrderRight, compliantTakerAddress); - // Assert validated addresses - const expectedValidatedAddresseses = [signedOrderLeft.makerAddress, signedOrderRight.makerAddress, compliantTakerAddress]; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check balances - const newBalances = await erc20Wrapper.getBalancesAsync(); - expect( - newBalances[signedOrderLeft.makerAddress][defaultMakerAssetAddress], - 'Checking left maker egress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultMakerAssetAddress].sub(expectedTransferAmounts.amountSoldByLeftMaker)); - expect( - newBalances[signedOrderRight.makerAddress][defaultTakerAssetAddress], - 'Checking right maker ingress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][defaultTakerAssetAddress].sub(expectedTransferAmounts.amountSoldByRightMaker)); - expect( - newBalances[compliantTakerAddress][defaultMakerAssetAddress], - 'Checking taker ingress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountReceivedByTaker)); - expect( - newBalances[signedOrderLeft.makerAddress][defaultTakerAssetAddress], - 'Checking left maker ingress ERC20 account balance', - ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][defaultTakerAssetAddress].add(expectedTransferAmounts.amountBoughtByLeftMaker)); - expect( - newBalances[signedOrderRight.makerAddress][defaultMakerAssetAddress], - 'Checking right maker egress ERC20 account balance', - ).to.be.bignumber.equal( - erc20Balances[signedOrderRight.makerAddress][defaultMakerAssetAddress].add(expectedTransferAmounts.amountBoughtByRightMaker), - ); - // Paid fees - expect( - newBalances[signedOrderLeft.makerAddress][zrxToken.address], - 'Checking left maker egress ERC20 account fees', - ).to.be.bignumber.equal(erc20Balances[signedOrderLeft.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByLeftMaker)); - expect( - newBalances[signedOrderRight.makerAddress][zrxToken.address], - 'Checking right maker egress ERC20 account fees', - ).to.be.bignumber.equal(erc20Balances[signedOrderRight.makerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByRightMaker)); - expect( - newBalances[compliantTakerAddress][zrxToken.address], - 'Checking taker egress ERC20 account fees', - ).to.be.bignumber.equal(erc20Balances[compliantTakerAddress][zrxToken.address].minus(expectedTransferAmounts.feePaidByTakerLeft).sub(expectedTransferAmounts.feePaidByTakerRight)); - // Received fees - expect( - newBalances[signedOrderLeft.feeRecipientAddress][zrxToken.address], - 'Checking left fee recipient ingress ERC20 account fees', - ).to.be.bignumber.equal( - erc20Balances[feeRecipientAddress][zrxToken.address].add(expectedTransferAmounts.feePaidByLeftMaker).add(expectedTransferAmounts.feePaidByRightMaker).add(expectedTransferAmounts.feePaidByTakerLeft).add(expectedTransferAmounts.feePaidByTakerRight), - ); - }); - it('should revert if left maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.matchOrdersAsync( - compliantSignedOrder, - signedOrderWithBadMakerAddress, - compliantTakerAddress, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if right maker does not meet the balance threshold', async () => { - // Create signed order with non-compliant maker address - const signedOrderWithBadMakerAddress = await orderFactory.newSignedOrderAsync({ - senderAddress: erc721CompliantForwarderInstance.address, - makerAddress: nonCompliantAddress - }); - // Execute transaction - return expectTransactionFailedAsync( - erc721TakerBalanceThresholdWrapper.matchOrdersAsync( - signedOrderWithBadMakerAddress, - compliantSignedOrder, - compliantTakerAddress, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - it('should revert if taker does not meet the balance threshold', async () => { - return expectTransactionFailedAsync( - erc721NonCompliantBalanceThresholdWrapper.matchOrdersAsync( - compliantSignedOrder, - compliantSignedOrder, - nonCompliantAddress, - ), - RevertReason.AtLeastOneAddressDoesNotMeetBalanceThreshold - ); - }); - }); - - describe('cancelOrder', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - compliantSignedOrder = await orderFactory.newSignedOrderAsync(); - compliantSignedOrder2 = await orderFactory2.newSignedOrderAsync(); - }); - it('Should successfully cancel order if maker meets balance threshold', async () => { - // Verify order is not cancelled - const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - // Cancel - const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrderAsync(compliantSignedOrder, compliantSignedOrder.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - it('Should successfully cancel order if maker does not meet balance threshold', async () => { - // Create order where maker does not meet balance threshold - const signedOrderWithBadMakerAddress = await nonCompliantOrderFactory.newSignedOrderAsync({}); - // Verify order is not cancelled - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) - expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - // Cancel - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrderAsync(signedOrderWithBadMakerAddress, signedOrderWithBadMakerAddress.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(signedOrderWithBadMakerAddress) - expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - }); - - describe('batchCancelOrders', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('Should successfully batch cancel orders if maker meets balance threshold', async () => { - // Create orders to cancel - const compliantSignedOrders = [ - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - await orderFactory.newSignedOrderAsync(), - ]; - // Verify orders are not cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const txReceipt = await erc721MakerBalanceThresholdWrapper.batchCancelOrdersAsync(compliantSignedOrders, compliantSignedOrder.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - }); - it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { - // Create orders to cancel - const nonCompliantSignedOrders = [ - await nonCompliantOrderFactory.newSignedOrderAsync(), - await nonCompliantOrderFactory.newSignedOrderAsync(), - await nonCompliantOrderFactory.newSignedOrderAsync(), - ]; - // Verify orders are not cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.batchCancelOrdersAsync(nonCompliantSignedOrders, nonCompliantAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - }); - }); - }); - - describe('cancelOrdersUpTo', () => { - beforeEach(async () => { - erc20Balances = await erc20Wrapper.getBalancesAsync(); - }); - it('Should successfully batch cancel orders if maker meets balance threshold', async () => { - // Create orders to cancel - const compliantSignedOrders = [ - await orderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), - await orderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), - await orderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), - ]; - // Verify orders are not cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await erc721MakerBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, compliantSignedOrder.makerAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(compliantSignedOrders, async (compliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await erc721MakerBalanceThresholdWrapper.getOrderInfoAsync(compliantSignedOrder) - const saltAsBigNumber = new BigNumber(salt); - if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - } else { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - } - }); - }); - it('Should successfully batch cancel order if maker does not meet balance threshold', async () => { - // Create orders to cancel - const nonCompliantSignedOrders = [ - await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(0)}), - await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(1)}), - await nonCompliantOrderFactory.newSignedOrderAsync({salt: new BigNumber(2)}), - ]; - // Verify orders are not cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder) => { - const orderInfoBeforeCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - return expect(orderInfoBeforeCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - }); - // Cancel - const cancelOrdersUpToThisSalt = new BigNumber(1); - const txReceipt = await erc721NonCompliantBalanceThresholdWrapper.cancelOrdersUpToAsync(cancelOrdersUpToThisSalt, nonCompliantAddress); - // Assert validated addresses - const expectedValidatedAddresseses: string[] = []; - assertValidatedAddressesLog(txReceipt, expectedValidatedAddresseses); - // Check that order was cancelled - await _.each(nonCompliantSignedOrders, async (nonCompliantSignedOrder, salt: number) => { - const orderInfoAfterCancelling = await erc721NonCompliantBalanceThresholdWrapper.getOrderInfoAsync(nonCompliantSignedOrder) - const saltAsBigNumber = new BigNumber(salt); - if (saltAsBigNumber.lessThanOrEqualTo(cancelOrdersUpToThisSalt)) { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.CANCELLED); - } else { - return expect(orderInfoAfterCancelling.orderStatus).to.be.equal(OrderStatus.FILLABLE); - } - }); - }); - }); -}); -// tslint:disable:max-file-line-count -// tslint:enable:no-unnecessary-type-assertion diff --git a/packages/contracts/test/utils/balance_threshold_wrapper.ts b/packages/contracts/test/utils/balance_threshold_wrapper.ts deleted file mode 100644 index cff40aa52..000000000 --- a/packages/contracts/test/utils/balance_threshold_wrapper.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { SignedOrder } from '@0x/types'; -import { BigNumber } from '@0x/utils'; -import { Web3Wrapper } from '@0x/web3-wrapper'; -import { Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; -import * as _ from 'lodash'; - -import { ExchangeContract } from '../../generated-wrappers/exchange'; -import { BalanceThresholdFilterContract } from '../../generated-wrappers/balance_threshold_filter'; - -import { formatters } from './formatters'; -import { LogDecoder } from './log_decoder'; -import { orderUtils } from './order_utils'; -import { TransactionFactory } from '../utils/transaction_factory'; -import { OrderInfo } from './types'; - -export class BalanceThresholdWrapper { - private readonly _balanceThresholdFilter: BalanceThresholdFilterContract; - private readonly _signerTransactionFactory: TransactionFactory; - private readonly _exchange: ExchangeContract; - private readonly _web3Wrapper: Web3Wrapper; - private readonly _logDecoder: LogDecoder; - constructor(balanceThresholdFilter: BalanceThresholdFilterContract, exchangeContract: ExchangeContract, signerTransactionFactory: TransactionFactory, provider: Provider) { - this._balanceThresholdFilter = balanceThresholdFilter; - this._exchange = exchangeContract; - this._signerTransactionFactory = signerTransactionFactory; - this._web3Wrapper = new Web3Wrapper(provider); - this._logDecoder = new LogDecoder(this._web3Wrapper); - } - public async fillOrderAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const data = this._exchange.fillOrder.getABIEncodedTransactionData( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async fillOrKillOrderAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const data = this._exchange.fillOrKillOrder.getABIEncodedTransactionData( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async fillOrderNoThrowAsync( - signedOrder: SignedOrder, - from: string, - opts: { takerAssetFillAmount?: BigNumber; gas?: number } = {}, - ): Promise { - const params = orderUtils.createFill(signedOrder, opts.takerAssetFillAmount); - const data = this._exchange.fillOrderNoThrow.getABIEncodedTransactionData( - params.order, - params.takerAssetFillAmount, - params.signature, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async batchFillOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[] } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const data = this._exchange.batchFillOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async batchFillOrKillOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[] } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async batchFillOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmounts?: BigNumber[]; gas?: number } = {}, - ): Promise { - const params = formatters.createBatchFill(orders, opts.takerAssetFillAmounts); - const data = this._exchange.batchFillOrKillOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmounts, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async marketSellOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmount: BigNumber }, - ): Promise { - const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); - const data = this._exchange.marketSellOrders.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async marketSellOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { takerAssetFillAmount: BigNumber; gas?: number }, - ): Promise { - const params = formatters.createMarketSellOrders(orders, opts.takerAssetFillAmount); - const data = this._exchange.marketSellOrdersNoThrow.getABIEncodedTransactionData( - params.orders, - params.takerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async marketBuyOrdersAsync( - orders: SignedOrder[], - from: string, - opts: { makerAssetFillAmount: BigNumber }, - ): Promise { - const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); - const data = this._exchange.marketBuyOrders.getABIEncodedTransactionData( - params.orders, - params.makerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async marketBuyOrdersNoThrowAsync( - orders: SignedOrder[], - from: string, - opts: { makerAssetFillAmount: BigNumber; gas?: number }, - ): Promise { - const params = formatters.createMarketBuyOrders(orders, opts.makerAssetFillAmount); - const data = this._exchange.marketBuyOrdersNoThrow.getABIEncodedTransactionData( - params.orders, - params.makerAssetFillAmount, - params.signatures, - ); - const txReceipt = this._executeTransaction(data, from, opts.gas); - return txReceipt; - } - public async cancelOrderAsync(signedOrder: SignedOrder, from: string): Promise { - const params = orderUtils.createCancel(signedOrder); - const data = this._exchange.cancelOrder.getABIEncodedTransactionData(params.order); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async batchCancelOrdersAsync( - orders: SignedOrder[], - from: string, - ): Promise { - const params = formatters.createBatchCancel(orders); - const data = this._exchange.batchCancelOrders.getABIEncodedTransactionData(params.orders); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async cancelOrdersUpToAsync(salt: BigNumber, from: string): Promise { - const data = this._exchange.cancelOrdersUpTo.getABIEncodedTransactionData(salt); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public async getTakerAssetFilledAmountAsync(orderHashHex: string): Promise { - const filledAmount = await this._exchange.filled.callAsync(orderHashHex); - return filledAmount; - } - public async isCancelledAsync(orderHashHex: string): Promise { - const isCancelled = await this._exchange.cancelled.callAsync(orderHashHex); - return isCancelled; - } - public async getOrderEpochAsync(makerAddress: string, senderAddress: string): Promise { - const orderEpoch = await this._exchange.orderEpoch.callAsync(makerAddress, senderAddress); - return orderEpoch; - } - public async getOrderInfoAsync(signedOrder: SignedOrder): Promise { - const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo; - return orderInfo; - } - public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise { - const ordersInfo = (await this._exchange.getOrdersInfo.callAsync(signedOrders)) as OrderInfo[]; - return ordersInfo; - } - public async matchOrdersAsync( - signedOrderLeft: SignedOrder, - signedOrderRight: SignedOrder, - from: string, - ): Promise { - const params = orderUtils.createMatchOrders(signedOrderLeft, signedOrderRight); - const data = await this._exchange.matchOrders.getABIEncodedTransactionData( - params.left, - params.right, - params.leftSignature, - params.rightSignature - ); - const txReceipt = this._executeTransaction(data, from); - return txReceipt; - } - public getBalanceThresholdAddress(): string { - return this._balanceThresholdFilter.address; - } - public getExchangeAddress(): string { - return this._exchange.address; - } - // Exchange functions - //abiEncodeFillOrder - //getFillOrderResultsAsync - // - private async _executeTransaction(abiEncodedExchangeTxData: string, from: string, gas?: number): Promise { - const signedExchangeTx = this._signerTransactionFactory.newSignedTransaction(abiEncodedExchangeTxData); - const txOpts = _.isUndefined(gas) ? {from} : {from, gas}; - const txHash = await this._balanceThresholdFilter.executeTransaction.sendTransactionAsync( - signedExchangeTx.salt, - signedExchangeTx.signerAddress, - signedExchangeTx.data, - signedExchangeTx.signature, - txOpts, - ); - const txReceipt = await this._logDecoder.getTxWithDecodedLogsAsync(txHash); - return txReceipt; - } -} -- cgit v1.2.3 From 6a0f5f39eea5fef285a1e2e962be03c2fa8847ab Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 15:55:36 -0800 Subject: Prettier / Linter fixes for TS --- packages/types/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 4d5b6e1f2..4470dd501 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -246,7 +246,7 @@ export enum RevertReason { // Balance Threshold Filter InvalidOrBlockedExchangeSelector = 'INVALID_OR_BLOCKED_EXCHANGE_SELECTOR', BalanceQueryFailed = 'BALANCE_QUERY_FAILED', - AtLeastOneAddressDoesNotMeetBalanceThreshold= 'AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD', + AtLeastOneAddressDoesNotMeetBalanceThreshold = 'AT_LEAST_ONE_ADDRESS_DOES_NOT_MEET_BALANCE_THRESHOLD', } export enum StatusCodes { -- cgit v1.2.3 From 34ff7fae9cffa7aaba5f0ba0060ed063a855f8e9 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Dec 2018 16:07:19 -0800 Subject: Removed deprecated README + comments --- packages/contracts/contracts/tokens/README.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 packages/contracts/contracts/tokens/README.md (limited to 'packages') diff --git a/packages/contracts/contracts/tokens/README.md b/packages/contracts/contracts/tokens/README.md deleted file mode 100644 index b54f0e046..000000000 --- a/packages/contracts/contracts/tokens/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Contracts from https://github.com/sendwyre/yes-compliance-token -Modified to compile in our codebase. -- cgit v1.2.3 From f91781a0605f46ee1a40bf979d22ff510f48d464 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Thu, 13 Dec 2018 13:52:20 -0800 Subject: Less Assembly. More Solidity. Less Efficiency. More Readability. --- .../utils/ExchangeSelectors/ExchangeSelectors.sol | 151 --------------------- 1 file changed, 151 deletions(-) delete mode 100644 packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol (limited to 'packages') diff --git a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol b/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol deleted file mode 100644 index c361fd075..000000000 --- a/packages/contracts/contracts/utils/ExchangeSelectors/ExchangeSelectors.sol +++ /dev/null @@ -1,151 +0,0 @@ -/* - - Copyright 2018 ZeroEx Intl. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -pragma solidity 0.4.24; - - -contract ExchangeSelectors { - - // allowedValidators - bytes4 constant allowedValidatorsSelector = 0x7b8e3514; - bytes4 constant allowedValidatorsSelectorGenerator = bytes4(keccak256('allowedValidators(address,address)')); - - // assetProxies - bytes4 constant assetProxiesSelector = 0x3fd3c997; - bytes4 constant assetProxiesSelectorGenerator = bytes4(keccak256('assetProxies(bytes4)')); - - // batchCancelOrders - bytes4 constant batchCancelOrdersSelector = 0x4ac14782; - bytes4 constant batchCancelOrdersSelectorGenerator = bytes4(keccak256('batchCancelOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - - // batchFillOrKillOrders - bytes4 constant batchFillOrKillOrdersSelector = 0x4d0ae546; - bytes4 constant batchFillOrKillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrKillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - - // batchFillOrders - bytes4 constant batchFillOrdersSelector = 0x297bb70b; - bytes4 constant batchFillOrdersSelectorGenerator = bytes4(keccak256('batchFillOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - - // batchFillOrdersNoThrow - bytes4 constant batchFillOrdersNoThrowSelector = 0x50dde190; - bytes4 constant batchFillOrdersNoThrowSelectorGenerator = bytes4(keccak256('batchFillOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256[],bytes[])')); - - // cancelOrder - bytes4 constant cancelOrderSelector = 0xd46b02c3; - bytes4 constant cancelOrderSelectorGenerator = bytes4(keccak256('cancelOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - - // cancelOrdersUpTo - bytes4 constant cancelOrdersUpToSelector = 0x4f9559b1; - bytes4 constant cancelOrdersUpToSelectorGenerator = bytes4(keccak256('cancelOrdersUpTo(uint256)')); - - // cancelled - bytes4 constant cancelledSelector = 0x2ac12622; - bytes4 constant cancelledSelectorGenerator = bytes4(keccak256('cancelled(bytes32)')); - - // currentContextAddress - bytes4 constant currentContextAddressSelector = 0xeea086ba; - bytes4 constant currentContextAddressSelectorGenerator = bytes4(keccak256('currentContextAddress()')); - - // executeTransaction - bytes4 constant executeTransactionSelector = 0xbfc8bfce; - bytes4 constant executeTransactionSelectorGenerator = bytes4(keccak256('executeTransaction(uint256,address,bytes,bytes)')); - - // fillOrKillOrder - bytes4 constant fillOrKillOrderSelector = 0x64a3bc15; - bytes4 constant fillOrKillOrderSelectorGenerator = bytes4(keccak256('fillOrKillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - - // fillOrder - bytes4 constant fillOrderSelector = 0xb4be83d5; - bytes4 constant fillOrderSelectorGenerator = bytes4(keccak256('fillOrder((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - - // fillOrderNoThrow - bytes4 constant fillOrderNoThrowSelector = 0x3e228bae; - bytes4 constant fillOrderNoThrowSelectorGenerator = bytes4(keccak256('fillOrderNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),uint256,bytes)')); - - // filled - bytes4 constant filledSelector = 0x288cdc91; - bytes4 constant filledSelectorGenerator = bytes4(keccak256('filled(bytes32)')); - - // getAssetProxy - bytes4 constant getAssetProxySelector = 0x60704108; - bytes4 constant getAssetProxySelectorGenerator = bytes4(keccak256('getAssetProxy(bytes4)')); - - // getOrderInfo - bytes4 constant getOrderInfoSelector = 0xc75e0a81; - bytes4 constant getOrderInfoSelectorGenerator = bytes4(keccak256('getOrderInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes))')); - - // getOrdersInfo - bytes4 constant getOrdersInfoSelector = 0x7e9d74dc; - bytes4 constant getOrdersInfoSelectorGenerator = bytes4(keccak256('getOrdersInfo((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[])')); - - // isValidSignature - bytes4 constant isValidSignatureSelector = 0x93634702; - bytes4 constant isValidSignatureSelectorGenerator = bytes4(keccak256('isValidSignature(bytes32,address,bytes)')); - - // marketBuyOrders - bytes4 constant marketBuyOrdersSelector = 0xe5fa431b; - bytes4 constant marketBuyOrdersSelectorGenerator = bytes4(keccak256('marketBuyOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // marketBuyOrdersNoThrow - bytes4 constant marketBuyOrdersNoThrowSelector = 0xa3e20380; - bytes4 constant marketBuyOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketBuyOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // marketSellOrders - bytes4 constant marketSellOrdersSelector = 0x7e1d9808; - bytes4 constant marketSellOrdersSelectorGenerator = bytes4(keccak256('marketSellOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // marketSellOrdersNoThrow - bytes4 constant marketSellOrdersNoThrowSelector = 0xdd1c7d18; - bytes4 constant marketSellOrdersNoThrowSelectorGenerator = bytes4(keccak256('marketSellOrdersNoThrow((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes)[],uint256,bytes[])')); - - // matchOrders - bytes4 constant matchOrdersSelector = 0x3c28d861; - bytes4 constant matchOrdersSelectorGenerator = bytes4(keccak256('matchOrders((address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),(address,address,address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes,bytes),bytes,bytes)')); - - // orderEpoch - bytes4 constant orderEpochSelector = 0xd9bfa73e; - bytes4 constant orderEpochSelectorGenerator = bytes4(keccak256('orderEpoch(address,address)')); - - // owner - bytes4 constant ownerSelector = 0x8da5cb5b; - bytes4 constant ownerSelectorGenerator = bytes4(keccak256('owner()')); - - // preSign - bytes4 constant preSignSelector = 0x3683ef8e; - bytes4 constant preSignSelectorGenerator = bytes4(keccak256('preSign(bytes32,address,bytes)')); - - // preSigned - bytes4 constant preSignedSelector = 0x82c174d0; - bytes4 constant preSignedSelectorGenerator = bytes4(keccak256('preSigned(bytes32,address)')); - - // registerAssetProxy - bytes4 constant registerAssetProxySelector = 0xc585bb93; - bytes4 constant registerAssetProxySelectorGenerator = bytes4(keccak256('registerAssetProxy(address)')); - - // setSignatureValidatorApproval - bytes4 constant setSignatureValidatorApprovalSelector = 0x77fcce68; - bytes4 constant setSignatureValidatorApprovalSelectorGenerator = bytes4(keccak256('setSignatureValidatorApproval(address,bool)')); - - // transactions - bytes4 constant transactionsSelector = 0x642f2eaf; - bytes4 constant transactionsSelectorGenerator = bytes4(keccak256('transactions(bytes32)')); - - // transferOwnership - bytes4 constant transferOwnershipSelector = 0xf2fde38b; - bytes4 constant transferOwnershipSelectorGenerator = bytes4(keccak256('transferOwnership(address)')); -} \ No newline at end of file -- cgit v1.2.3 From e2510ed28f97feb33404ec0cb773214236620343 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 19 Dec 2018 10:44:32 +0000 Subject: Add temporary console.log to test failing on CI --- packages/order-watcher/test/order_watcher_web_socket_server_test.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages') diff --git a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts index fd388e907..fa64ac305 100644 --- a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts @@ -278,6 +278,7 @@ describe('OrderWatcherWebSocketServer', async () => { for (const client of [wsClient, wsClientTwo]) { const updateMsg = await _onMessageAsync(client); const updateData = JSON.parse(updateMsg.data); + console.log('-------------------------- UPDATE_DATA: ', updateData); const orderState = updateData.result as OrderStateValid; expect(orderState.isValid).to.be.true(); expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); -- cgit v1.2.3 From 84c8b83694cc16ed42cb01315803f124c582aab7 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 19 Dec 2018 12:18:53 +0000 Subject: Fix WS tests to remove race-condition and be more specific about the message expected --- .../test/order_watcher_web_socket_server_test.ts | 57 +++++++++++++--------- 1 file changed, 34 insertions(+), 23 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts index fa64ac305..8070860e7 100644 --- a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts @@ -26,7 +26,7 @@ interface WsMessage { data: string; } -describe('OrderWatcherWebSocketServer', async () => { +describe.only('OrderWatcherWebSocketServer', async () => { let contractWrappers: ContractWrappers; let wsServer: OrderWatcherWebSocketServer; let wsClient: WebSocket.w3cwebsocket; @@ -49,9 +49,14 @@ describe('OrderWatcherWebSocketServer', async () => { // HACK: createFillableSignedOrderAsync is Promise-based, which forces us // to use Promises instead of the done() callbacks for tests. // onmessage callback must thus be wrapped as a Promise. - const _onMessageAsync = async (client: WebSocket.w3cwebsocket) => + const _getOnMessagePromise = async (client: WebSocket.w3cwebsocket, method: string | null) => new Promise(resolve => { - client.onmessage = (msg: WsMessage) => resolve(msg); + client.onmessage = (msg: WsMessage) => { + const data = JSON.parse(msg.data); + if (data.method === method) { + resolve(msg); + } + }; }); before(async () => { @@ -106,20 +111,20 @@ describe('OrderWatcherWebSocketServer', async () => { isVerbose: true, }; wsServer = new OrderWatcherWebSocketServer(provider, networkId, contractAddresses, orderWatcherConfig); - wsServer.start(); }); after(async () => { await blockchainLifecycle.revertAsync(); - wsServer.stop(); }); beforeEach(async () => { + wsServer.start(); await blockchainLifecycle.startAsync(); wsClient = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); logUtils.log(`${new Date()} [Client] Connected.`); }); afterEach(async () => { - await blockchainLifecycle.revertAsync(); wsClient.close(); + await blockchainLifecycle.revertAsync(); + wsServer.stop(); logUtils.log(`${new Date()} [Client] Closed.`); }); @@ -147,7 +152,7 @@ describe('OrderWatcherWebSocketServer', async () => { method: 'BAD_METHOD', }; wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); - const errorMsg = await _onMessageAsync(wsClient); + const errorMsg = await _getOnMessagePromise(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -163,7 +168,7 @@ describe('OrderWatcherWebSocketServer', async () => { method: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); - const errorMsg = await _onMessageAsync(wsClient); + const errorMsg = await _getOnMessagePromise(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.method).to.be.null; @@ -179,7 +184,7 @@ describe('OrderWatcherWebSocketServer', async () => { orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); + const errorMsg = await _getOnMessagePromise(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -199,7 +204,7 @@ describe('OrderWatcherWebSocketServer', async () => { }, }; wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); - const errorMsg = await _onMessageAsync(wsClient); + const errorMsg = await _getOnMessagePromise(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -210,15 +215,16 @@ describe('OrderWatcherWebSocketServer', async () => { it('executes addOrder and removeOrder requests correctly', async () => { wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - const addOrderMsg = await _onMessageAsync(wsClient); + const addOrderMsg = await _getOnMessagePromise(wsClient, OrderWatcherMethod.AddOrder); const addOrderData = JSON.parse(addOrderMsg.data); expect(addOrderData.method).to.be.eq('ADD_ORDER'); expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ [orderHash]: signedOrder, }); + const clientOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.RemoveOrder); wsClient.send(JSON.stringify(removeOrderPayload)); - const removeOrderMsg = await _onMessageAsync(wsClient); + const removeOrderMsg = await clientOnMessagePromise; const removeOrderData = JSON.parse(removeOrderMsg.data); expect(removeOrderData.method).to.be.eq('REMOVE_ORDER'); expect((wsServer as any)._orderWatcher._orderByOrderHash).to.not.deep.include({ @@ -229,13 +235,13 @@ describe('OrderWatcherWebSocketServer', async () => { it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { // Add the regular order wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - await _onMessageAsync(wsClient); + const clientOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.Update); // Set the allowance to 0 await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); // Ensure that orderStateInvalid message is received. - const orderWatcherUpdateMsg = await _onMessageAsync(wsClient); + const orderWatcherUpdateMsg = await clientOnMessagePromise; const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); const invalidOrderState = orderWatcherUpdateData.result as OrderStateInvalid; @@ -269,20 +275,25 @@ describe('OrderWatcherWebSocketServer', async () => { wsClientTwo = new WebSocket.w3cwebsocket('ws://127.0.0.1:8080/'); logUtils.log(`${new Date()} [Client] Connected.`); wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); - await _onMessageAsync(wsClientTwo); + + const clientOneOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.Update); + const clientTwoOnMessagePromise = _getOnMessagePromise(wsClientTwo, OrderWatcherMethod.Update); // Change the allowance await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); // Check that both clients receive the emitted event - for (const client of [wsClient, wsClientTwo]) { - const updateMsg = await _onMessageAsync(client); - const updateData = JSON.parse(updateMsg.data); - console.log('-------------------------- UPDATE_DATA: ', updateData); - const orderState = updateData.result as OrderStateValid; - expect(orderState.isValid).to.be.true(); - expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); - } + let updateMsg = await clientOneOnMessagePromise; + let updateData = JSON.parse(updateMsg.data); + let orderState = updateData.result as OrderStateValid; + expect(orderState.isValid).to.be.true(); + expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); + + updateMsg = await clientTwoOnMessagePromise; + updateData = JSON.parse(updateMsg.data); + orderState = updateData.result as OrderStateValid; + expect(orderState.isValid).to.be.true(); + expect(orderState.orderRelevantState.makerFeeProxyAllowance).to.be.eq('0'); wsClientTwo.close(); logUtils.log(`${new Date()} [Client] Closed.`); -- cgit v1.2.3 From 90ee70db23251bfc68c7d4235be1bf1c4c6e6a92 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 19 Dec 2018 12:49:51 +0000 Subject: Move onMessageAsync outside of tests and add comments --- .../test/order_watcher_web_socket_server_test.ts | 53 ++++++++++++---------- 1 file changed, 30 insertions(+), 23 deletions(-) (limited to 'packages') diff --git a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts index 8070860e7..578e0de61 100644 --- a/packages/order-watcher/test/order_watcher_web_socket_server_test.ts +++ b/packages/order-watcher/test/order_watcher_web_socket_server_test.ts @@ -46,18 +46,6 @@ describe.only('OrderWatcherWebSocketServer', async () => { let removeOrderPayload: RemoveOrderRequest; const decimals = constants.ZRX_DECIMALS; const fillableAmount = Web3Wrapper.toBaseUnitAmount(new BigNumber(5), decimals); - // HACK: createFillableSignedOrderAsync is Promise-based, which forces us - // to use Promises instead of the done() callbacks for tests. - // onmessage callback must thus be wrapped as a Promise. - const _getOnMessagePromise = async (client: WebSocket.w3cwebsocket, method: string | null) => - new Promise(resolve => { - client.onmessage = (msg: WsMessage) => { - const data = JSON.parse(msg.data); - if (data.method === method) { - resolve(msg); - } - }; - }); before(async () => { // Set up constants @@ -152,7 +140,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { method: 'BAD_METHOD', }; wsClient.onopen = () => wsClient.send(JSON.stringify(invalidMethodPayload)); - const errorMsg = await _getOnMessagePromise(wsClient, null); + const errorMsg = await onMessageAsync(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -168,7 +156,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { method: 'GET_STATS', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noJsonRpcPayload)); - const errorMsg = await _getOnMessagePromise(wsClient, null); + const errorMsg = await onMessageAsync(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.method).to.be.null; @@ -184,7 +172,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { orderHash: '0x7337e2f2a9aa2ed6afe26edc2df7ad79c3ffa9cf9b81a964f707ea63f5272355', }; wsClient.onopen = () => wsClient.send(JSON.stringify(noSignedOrderAddOrderPayload)); - const errorMsg = await _getOnMessagePromise(wsClient, null); + const errorMsg = await onMessageAsync(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -204,7 +192,7 @@ describe.only('OrderWatcherWebSocketServer', async () => { }, }; wsClient.onopen = () => wsClient.send(JSON.stringify(invalidAddOrderPayload)); - const errorMsg = await _getOnMessagePromise(wsClient, null); + const errorMsg = await onMessageAsync(wsClient, null); const errorData = JSON.parse(errorMsg.data); // tslint:disable-next-line:no-unused-expression expect(errorData.id).to.be.null; @@ -215,14 +203,14 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('executes addOrder and removeOrder requests correctly', async () => { wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - const addOrderMsg = await _getOnMessagePromise(wsClient, OrderWatcherMethod.AddOrder); + const addOrderMsg = await onMessageAsync(wsClient, OrderWatcherMethod.AddOrder); const addOrderData = JSON.parse(addOrderMsg.data); expect(addOrderData.method).to.be.eq('ADD_ORDER'); expect((wsServer as any)._orderWatcher._orderByOrderHash).to.deep.include({ [orderHash]: signedOrder, }); - const clientOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.RemoveOrder); + const clientOnMessagePromise = onMessageAsync(wsClient, OrderWatcherMethod.RemoveOrder); wsClient.send(JSON.stringify(removeOrderPayload)); const removeOrderMsg = await clientOnMessagePromise; const removeOrderData = JSON.parse(removeOrderMsg.data); @@ -235,12 +223,16 @@ describe.only('OrderWatcherWebSocketServer', async () => { it('broadcasts orderStateInvalid message when makerAddress allowance set to 0 for watched order', async () => { // Add the regular order wsClient.onopen = () => wsClient.send(JSON.stringify(addOrderPayload)); - const clientOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.Update); + + // We register the onMessage callback before calling `setProxyAllowanceAsync` which we + // expect will cause a message to be emitted. We do now "await" here, since we want to + // check for messages _after_ calling `setProxyAllowanceAsync` + const clientOnMessagePromise = onMessageAsync(wsClient, OrderWatcherMethod.Update); // Set the allowance to 0 await contractWrappers.erc20Token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, new BigNumber(0)); - // Ensure that orderStateInvalid message is received. + // We now await the `onMessage` promise to check for the message const orderWatcherUpdateMsg = await clientOnMessagePromise; const orderWatcherUpdateData = JSON.parse(orderWatcherUpdateMsg.data); expect(orderWatcherUpdateData.method).to.be.eq('UPDATE'); @@ -276,13 +268,14 @@ describe.only('OrderWatcherWebSocketServer', async () => { logUtils.log(`${new Date()} [Client] Connected.`); wsClientTwo.onopen = () => wsClientTwo.send(JSON.stringify(nonZeroMakerFeeOrderPayload)); - const clientOneOnMessagePromise = _getOnMessagePromise(wsClient, OrderWatcherMethod.Update); - const clientTwoOnMessagePromise = _getOnMessagePromise(wsClientTwo, OrderWatcherMethod.Update); + // Setup the onMessage callbacks, but don't await them yet + const clientOneOnMessagePromise = onMessageAsync(wsClient, OrderWatcherMethod.Update); + const clientTwoOnMessagePromise = onMessageAsync(wsClientTwo, OrderWatcherMethod.Update); // Change the allowance await contractWrappers.erc20Token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress, new BigNumber(0)); - // Check that both clients receive the emitted event + // Check that both clients receive the emitted event by awaiting the onMessageAsync promises let updateMsg = await clientOneOnMessagePromise; let updateData = JSON.parse(updateMsg.data); let orderState = updateData.result as OrderStateValid; @@ -299,3 +292,17 @@ describe.only('OrderWatcherWebSocketServer', async () => { logUtils.log(`${new Date()} [Client] Closed.`); }); }); + +// HACK: createFillableSignedOrderAsync is Promise-based, which forces us +// to use Promises instead of the done() callbacks for tests. +// onmessage callback must thus be wrapped as a Promise. +async function onMessageAsync(client: WebSocket.w3cwebsocket, method: string | null): Promise { + return new Promise(resolve => { + client.onmessage = (msg: WsMessage) => { + const data = JSON.parse(msg.data); + if (data.method === method) { + resolve(msg); + } + }; + }); +} -- cgit v1.2.3 From 5c24596d812a80011f9e92a13bf91923fbcc2a64 Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Wed, 19 Dec 2018 13:21:06 +0000 Subject: Add missing CHANGELOG entry for OrderWatcher WS interface --- packages/order-watcher/CHANGELOG.json | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'packages') diff --git a/packages/order-watcher/CHANGELOG.json b/packages/order-watcher/CHANGELOG.json index c1fd8d4a9..304dc45fd 100644 --- a/packages/order-watcher/CHANGELOG.json +++ b/packages/order-watcher/CHANGELOG.json @@ -1,4 +1,14 @@ [ + { + "version": "2.3.0", + "changes": [ + { + "note": + "Added a WebSocket interface to OrderWatcher so that it can be used by a client written in any language", + "pr": 1427 + } + ] + }, { "version": "2.2.8", "changes": [ -- cgit v1.2.3