Skip to content
This repository has been archived by the owner on Jan 15, 2021. It is now read-only.

Commit

Permalink
952/set slippage (#1011)
Browse files Browse the repository at this point in the history
* MaximumSlippage: create comp

1. dumb for now

* Tooltip size prop

* text

* moved MaximumSlippage to own file

1. minor css changes

* const: slippage map and default slip

* PriceSlippage reducer & actions

* Price: move MaximumSlippage state inside

1. MaximumSlippage remains dumb
2. added slippage label next to price title for clarity

* MaximumSlippage: passed state setters / CSS

1. passed custom slippage setters from parent containers
2. css fixes

* const: fixed exports + added new const

1. BigNumber ONE & ONE_HUNDRED exports

* usePriceEstimation: added slippage price calc

* just use index as key

* css for dark mode

* remove slippage from app because :(

* commented in useful code for if we reinstantiate

* move price checking function to utils/price

* usePriceEstimation: removed price slippage code

1. also removed reducer code for slippage
  • Loading branch information
W3stside authored May 25, 2020
1 parent b8611f8 commit c25ae32
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 7 deletions.
7 changes: 4 additions & 3 deletions src/components/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import styled from 'styled-components'
import { isElement, isFragment } from 'react-is'

// assets
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome'
import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'

// components
Expand Down Expand Up @@ -194,6 +194,7 @@ interface HelpTooltipProps {
tooltip: ReactNode
placement?: Placement
offset?: number
iconSize?: FontAwesomeIconProps['size']
}

const HelperSpan = styled.span`
Expand All @@ -212,7 +213,7 @@ export const HelpTooltipContainer = styled(LongTooltipContainer)`
color: black;
`

export const HelpTooltip: React.FC<HelpTooltipProps> = ({ tooltip, placement = 'top', offset }) => {
export const HelpTooltip: React.FC<HelpTooltipProps> = ({ tooltip, placement = 'top', offset, iconSize }) => {
const {
targetProps: { ref, onClick },
tooltipProps,
Expand All @@ -229,7 +230,7 @@ export const HelpTooltip: React.FC<HelpTooltipProps> = ({ tooltip, placement = '
return (
<>
<HelperSpan ref={ref} onClick={handleClick}>
<FontAwesomeIcon icon={faQuestionCircle} />
<FontAwesomeIcon icon={faQuestionCircle} size={iconSize} />
</HelperSpan>
<Tooltip {...tooltipProps} bgColor="#bfd6ef">
{tooltip}
Expand Down
182 changes: 182 additions & 0 deletions src/components/TradeWidget/MaximumSlippage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import React from 'react'
import styled from 'styled-components'

import { MEDIA, SLIPPAGE_MAP } from 'const'
import { PriceSlippageState } from 'reducers-actions/priceSlippage'

const Wrapper = styled.div`
font-size: 1.6rem;
width: 100%;
> strong {
display: flex;
align-items: center;
text-transform: capitalize;
color: var(--color-text-primary);
width: 100%;
margin: 0 0 1rem;
padding: 0;
box-sizing: border-box;
font-size: 1.5rem;
@media ${MEDIA.mobile} {
font-size: 1.3rem;
}
}
> div {
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: space-evenly;
margin: 1rem auto 0.2rem;
height: 4.4rem;
width: 100%;
> button {
border-radius: 3rem;
> small {
font-size: x-small;
margin-left: 0.4rem;
}
}
> button,
> label > input {
display: flex;
flex: 1;
align-items: center;
justify-content: space-evenly;
background: var(--color-background-input);
color: var(--color-text-primary);
font-size: inherit;
font-weight: normal;
height: 100%;
padding: 0.65rem 1.5rem;
&:not(:last-child) {
margin-right: 1rem;
}
white-space: nowrap;
&.selected,
&.selected ~ small,
&:hover:not(input),
&:focus,
&:focus ~ small {
color: var(--color-text-button-hover);
}
&:hover:not(input):not(.selected),
&:focus {
background-color: var(--color-background-button-hover);
}
&.selected {
background: var(--color-text-active);
}
transition: all 0.2s ease-in-out;
}
> label {
position: relative;
flex: 1.6;
height: 100%;
> small {
position: absolute;
right: 2rem;
top: 0;
bottom: 0;
margin: auto;
display: flex;
align-items: center;
opacity: 0.75;
color: var(--color-text-primary);
letter-spacing: -0.05rem;
text-align: right;
font-weight: var(--font-weight-bold);
@media ${MEDIA.mobile} {
font-size: 1rem;
letter-spacing: 0.03rem;
}
}
> input {
border-radius: var(--border-radius-top);
margin: 0;
padding-right: 3.4rem;
width: 100%;
&::placeholder {
opacity: 0.6;
}
}
}
}
`

interface MaximumSlippageProps {
priceSlippage: PriceSlippageState
setNewSlippage: (customSlippage: string | React.ChangeEvent<HTMLInputElement>) => void
}

const slippagePercentages = Array.from(SLIPPAGE_MAP.keys())

const checkCustomPriceSlippage = (slippagePercentage: string): boolean =>
!!slippagePercentage && !SLIPPAGE_MAP.has(slippagePercentage)

const MaximumSlippage: React.FC<MaximumSlippageProps> = ({ setNewSlippage, priceSlippage }) => {
return (
<Wrapper>
<strong>Limit additional price slippage</strong>
<div>
{slippagePercentages.map((slippage, index) => (
<button
key={index}
type="button"
onClick={(): void => setNewSlippage(slippage)}
className={slippage === priceSlippage ? 'selected' : ''}
>
{slippage}%{SLIPPAGE_MAP.get(slippage) && <small>(suggested)</small>}
</button>
))}
<label>
<input
type="number"
step="0.1"
placeholder="Custom"
value={priceSlippage}
className={checkCustomPriceSlippage(priceSlippage) ? 'selected' : ''}
onChange={(e): void => setNewSlippage(e.target.value)}
/>
<small>%</small>
</label>
</div>
</Wrapper>
)
}

/*
// PRICE SLIPPAGE
const dispatchNewSlippage = (payload: string): void => dispatch(setPriceSlippage(payload))
// to add in Price component to show current selected slippage
{priceSlippage && (
<FormMessage className="warning">
<small>{priceSlippage}% slippage</small>
</FormMessage>
)}
*/

export default MaximumSlippage
12 changes: 11 additions & 1 deletion src/components/TradeWidget/Price.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { OrderBookBtn } from 'components/OrderBookBtn'

// TradeWidget: subcomponents
import { TradeFormData } from 'components/TradeWidget'
import { FormInputError } from 'components/TradeWidget/FormMessage'
import FormMessage, { FormInputError } from 'components/TradeWidget/FormMessage'
import { useNumberInput } from 'components/TradeWidget/useNumberInput'

const Wrapper = styled.div`
Expand All @@ -25,17 +25,26 @@ const Wrapper = styled.div`
> strong {
display: flex;
align-items: center;
text-transform: capitalize;
color: var(--color-text-primary);
width: 100%;
margin: 0 0 1rem;
padding: 0;
box-sizing: border-box;
font-size: 1.5rem;
@media ${MEDIA.mobile} {
font-size: 1.3rem;
}
> ${FormMessage} {
width: min-content;
white-space: nowrap;
font-size: x-small;
margin: 0 0.5rem;
}
> button {
background: none;
border: 0;
Expand Down Expand Up @@ -235,6 +244,7 @@ const Price: React.FC<Props> = ({ sellToken, receiveToken, priceInputId, priceIn
</label>
<FormInputError errorMessage={errorPriceInverse?.message} />
</PriceInputBox>
{/* MAX SLIPPAGE CONTROL */}
</Wrapper>
)
}
Expand Down
15 changes: 14 additions & 1 deletion src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,21 @@ export {
FEE_PERCENTAGE,
DEFAULT_DECIMALS,
DEFAULT_PRECISION,
ZERO,
ONE,
TWO,
TEN,
ALLOWANCE_MAX_VALUE,
ALLOWANCE_FOR_ENABLED_TOKEN,
} from '@gnosis.pm/dex-js'
export { ZERO, ONE, TWO, TEN, ALLOWANCE_MAX_VALUE, ALLOWANCE_FOR_ENABLED_TOKEN } from '@gnosis.pm/dex-js'
import { BATCH_TIME } from '@gnosis.pm/dex-js'

export const BATCH_TIME_IN_MS = BATCH_TIME * 1000

export const ZERO_BIG_NUMBER = new BigNumber(0)
export const ONE_BIG_NUMBER = new BigNumber(1)
export const TEN_BIG_NUMBER = new BigNumber(10)
export const ONE_HUNDRED_BIG_NUMBER = new BigNumber(100)

// How much of the order needs to be matched to consider it filled
// Will divide the total sell amount by this factor.
Expand Down Expand Up @@ -143,6 +150,12 @@ export const WETH_ADDRESS_RINKEBY = '0xc778417E063141139Fce010982780140Aa0cD5Ab'
export const ORDER_BOOK_HOPS_DEFAULT = 2
export const ORDER_BOOK_HOPS_MAX = 2

export const SLIPPAGE_MAP = new Map([
['0.1', false],
['0.5', true],
['1', false],
])
export const DEFAULT_SUGGESTED_SLIPPAGE = '0.5'
// Delay disabling loading indicators, since in a normal workflow, when a transaction is mined, the spinner is stopped,
// however, the new state, that flows top down once a bock is mined, can have a small delayed
// This delay mitigates the strange effect of stopping the loading before the data is updated
Expand Down
6 changes: 6 additions & 0 deletions src/hooks/usePriceEstimation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ interface Result {
isPriceLoading: boolean
}

/*
PRICE ESTIMATION
*
BACKEND APPLIES A 0.1% SAFETY MARGIN ON ALL PRICES TO ACCOUNT FOR ROUNDING BY THE SOLVER
*/

export function usePriceEstimation(params: Params): Result {
const { baseTokenId, quoteTokenId } = params
const [isPriceLoading, setIsPriceLoading] = useSafeState(true)
Expand Down
4 changes: 4 additions & 0 deletions src/reducers-actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export interface Actions<T, P> {
payload: P
}

export type ActionCreator<T, P> = (payload: P) => Actions<T, P>

export type ReducerCreator<S, A> = (state: S, action: A) => S

export interface GlobalState {
tokens: TokenLocalState
pendingOrders: PendingOrdersState
Expand Down
30 changes: 30 additions & 0 deletions src/reducers-actions/priceSlippage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Actions, ActionCreator, ReducerCreator } from 'reducers-actions'
import { DEFAULT_SUGGESTED_SLIPPAGE } from 'const'

const ActionsList = ['SET_PRICE_SLIPPAGE'] as const

type PriceSlippageActions = typeof ActionsList[number]
type PriceSlippagePayload = string

type PriceSlippageState = PriceSlippagePayload

const setPriceSlippage: ActionCreator<PriceSlippageActions, PriceSlippagePayload> = payload => ({
type: ActionsList[0],
payload,
})

const PRICE_SLIPPAGE_INITIAL_STATE = DEFAULT_SUGGESTED_SLIPPAGE

const reducer: ReducerCreator<PriceSlippageState, Actions<PriceSlippageActions, PriceSlippagePayload>> = (
state,
action,
) => {
switch (action.type) {
case ActionsList[0]:
return action.payload
default:
return state
}
}

export { setPriceSlippage, reducer, PriceSlippageState, PRICE_SLIPPAGE_INITIAL_STATE }
1 change: 1 addition & 0 deletions src/styles/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ const variables = css`
// BORDERS
// ------------------------------
--border-radius: 0.4375rem;
--border-radius-top: 0.6rem 0.6rem 0 0;
// ------------------------------
// BOX-SHADOW
Expand Down
26 changes: 24 additions & 2 deletions src/utils/price.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import BN from 'bn.js'
import { assert, ONE_BIG_NUMBER } from '@gnosis.pm/dex-js'
import { TEN, UNLIMITED_ORDER_AMOUNT_BIGNUMBER } from 'const'
import BigNumber from 'bignumber.js'
import { assert, ONE_BIG_NUMBER } from '@gnosis.pm/dex-js'
import { parseBigNumber } from './format'
import { TEN, UNLIMITED_ORDER_AMOUNT_BIGNUMBER, ONE_HUNDRED_BIG_NUMBER } from 'const'

interface AdjustAmountParams {
amount: BN
Expand Down Expand Up @@ -99,3 +100,24 @@ export function maxAmountsForSpread({

return { buyAmount: bigNumberToBN(buyAmount), sellAmount: bigNumberToBN(sellAmount) }
}

/**
* @name checkSlippageAgainstPrice
*
* @param slippage - user set slippage as string
* @param prePrice - pre-slippape adjusted price as BigNumber or null
* @returns [BigNumber | null] - pre-price adjusted for slippage as BigNumber or null
*/
export function checkSlippageAgainstPrice(slippage: string, prePrice: BigNumber | null): BigNumber | null {
if (!prePrice) return null
const slippageAsBigNumber = parseBigNumber(slippage)
// if price slippage is not a BigNumber e.g 'abc' return prePrice
if (!slippageAsBigNumber) return prePrice

// slippageAsBigNumber here is defined and is indeed a valid number
// convert slippage into fraction: (1 - (0.5/100)) = (1 - 0.005) = 99.995
const slippageAsFraction = ONE_BIG_NUMBER.minus(slippageAsBigNumber.div(ONE_HUNDRED_BIG_NUMBER))
const postSlippagePrice = prePrice.times(slippageAsFraction)

return postSlippagePrice
}

0 comments on commit c25ae32

Please sign in to comment.