Skip to content
This repository has been archived by the owner on Jun 24, 2022. It is now read-only.

Commit

Permalink
[CoW Subsidy] Base PR (#2549)
Browse files Browse the repository at this point in the history
* CoW subsidy modal + sub components

* force commit

* [CoW Subsidy] Connect modal and add comp to swap mod (#2551)

* revert !account test check

* add cow subsidy modal type to state

* add component to swapmod

* use AutoRow

* [Cow Subsidy] - Get user's (v)COW balance fn + tests (#2557)

* create discount utl fn & tests

* move constants to file closer to subsidy code

* minor changes to SubsidyTable

* fix broken path

* PR comments - ease up logic and data

* [CoW Subsidy] - Highlight current subsidy relative to CoW balance and show correct discount on SwapMod (#2558)

* add selected tier styling to table

* change return of util

* pass tier (mock) to modal

* more serious hook impl.

* change type to subsidy and use hook

* fix test

* use atom units for better math

- simpler for..of loop
- format table units down

* better tests and fix logic

* type change and minor logic lean

* fix test tier with lt change

* lt inclusive in loop

* fix typo

* dont show empty container

* hide balance on !account
  • Loading branch information
W3stside authored Mar 22, 2022
1 parent 54406f9 commit 9b55468
Show file tree
Hide file tree
Showing 13 changed files with 399 additions and 19 deletions.
42 changes: 42 additions & 0 deletions src/custom/components/CowBalance/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import styled from 'styled-components/macro'
import { Trans } from '@lingui/macro'

import { AMOUNT_PRECISION } from 'constants/index'
import { ClaimSummaryTitle, ClaimTotal, ClaimSummary as ClaimSummaryWrapper } from 'pages/Claim/styled'
import { formatMax, formatSmartLocaleAware } from 'utils/format'
import CowProtocolLogo from 'components/CowProtocolLogo'
import { CowSubsidyInfoProps } from 'components/CowSubsidyModal'

const Wrapper = styled(ClaimSummaryWrapper)`
border-radius: 100px;
`

type CowBalanceProps = Omit<CowSubsidyInfoProps, 'subsidy'> & {
title?: string
}

const CowBalance = ({ balance, title }: CowBalanceProps) => {
return (
<Wrapper>
<CowProtocolLogo size={100} />

{title && (
<ClaimSummaryTitle>
<Trans>{title}</Trans>
</ClaimSummaryTitle>
)}

<div>
<ClaimTotal>
<b>Your combined balance</b>
<p title={`${balance ? formatMax(balance, balance.currency.decimals) : '0'} vCOW`}>
{' '}
{formatSmartLocaleAware(balance, AMOUNT_PRECISION) || '0'} (v)COW
</p>
</ClaimTotal>
</div>
</Wrapper>
)
}

export default CowBalance
94 changes: 94 additions & 0 deletions src/custom/components/CowSubsidyModal/SubsidyTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import styled from 'styled-components/macro'
import { formatSmartLocaleAware } from 'utils/format'
import { ClaimTr } from 'pages/Claim/ClaimsTable'
import { COW_SUBSIDY_DATA } from './constants'
import { CowSubsidy } from '.'
import { transparentize } from 'polished'
import { FlyoutRowActiveIndicator } from '../Header/NetworkSelector'

import { BigNumber } from 'bignumber.js'
import { formatUnits } from '@ethersproject/units'
import { V_COW } from 'constants/tokens'
import { SupportedChainId } from 'constants/chains'

const StyledSubsidyTable = styled.table`
width: 100%;
`

const SubsidyTr = styled(ClaimTr)<{ selected?: boolean }>`
position: relative;
${({ selected, theme }) =>
selected &&
`
background: ${transparentize(0.7, theme.primary1)};
> td {
border: 1.2px solid ${theme.primary1};
color: ${theme.primary1};
font-weight: 500;
> ${FlyoutRowActiveIndicator} {
position: absolute;
left: 8%;
background-color: ${theme.primary1};
box-shadow: 0px 0px 8px ${transparentize(0.3, theme.primary1)};
}
&:first-child {
border-right: none;
display: flex;
align-items: center;
justify-content: center;
}
&:last-child {
border-left: none;
}
}
`}
> th {
font-weight: 300;
}
> td,
th {
padding: 10px;
text-align: center;
}
`

const COW_DECIMALS = V_COW[SupportedChainId.MAINNET].decimals

function SubsidyTable({ discount }: CowSubsidy) {
return (
<StyledSubsidyTable>
<thead>
<SubsidyTr>
<th>(v)COW balance</th>
<th>Fee discount</th>
</SubsidyTr>
</thead>
<tbody>
{/* DATA IS IN ATOMS */}
{COW_SUBSIDY_DATA.map(([threshold, thresholdDiscount], i) => {
const selected = discount === thresholdDiscount
const formattedThreshold = new BigNumber(formatUnits(threshold, COW_DECIMALS))

return (
<SubsidyTr key={i} selected={selected}>
<td>
{selected && <FlyoutRowActiveIndicator active />}
<span>{i && '>' + formatSmartLocaleAware(formattedThreshold)}</span>
</td>
<td>{thresholdDiscount}%</td>
</SubsidyTr>
)
})}
</tbody>
</StyledSubsidyTable>
)
}

export default SubsidyTable
13 changes: 13 additions & 0 deletions src/custom/components/CowSubsidyModal/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// used in modal and popover
export const SUBSIDY_INFO_MESSAGE = 'As a (v)COW token holder you will be eligible for a fee discount.'
export const COW_SUBSIDY_DATA: [string, number][] = [
['0', 0],
// 100
['100000000000000000000', 10],
// 1000
['1000000000000000000000', 15],
// 10000
['10000000000000000000000', 25],
// 100000
['100000000000000000000000', 35],
]
78 changes: 78 additions & 0 deletions src/custom/components/CowSubsidyModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useCallback } from 'react'
import { CurrencyAmount, Currency } from '@uniswap/sdk-core'
import {
ConfirmationModalContent,
ConfirmationModalContentProps,
ConfirmationModalProps,
} from 'components/TransactionConfirmationModal'
import { useActiveWeb3React } from 'hooks/web3'
import { GpModal } from 'components/Modal'
import { AutoColumn } from 'components/SearchModal/CommonBases'
import { Text } from 'rebass'

import Row from 'components/Row'
import { ExternalLink } from 'components/Link'

import CowBalance from '../CowBalance'
import SubsidyTable from './SubsidyTable'
import { SUBSIDY_INFO_MESSAGE } from './constants'

import useCowBalanceAndSubsidy from 'hooks/useCowBalanceAndSubsidy'

export type CowSubsidy = { tier: number; discount: number }
export interface CowSubsidyInfoProps {
account?: string
balance?: CurrencyAmount<Currency>
subsidy: CowSubsidy
}

const CowSubsidyInfo = ({ account, balance, subsidy }: CowSubsidyInfoProps) => (
<AutoColumn style={{ marginTop: 20, padding: '2rem 0' }} gap="24px" justify="center">
<Text fontWeight={500} fontSize={16} style={{ textAlign: 'center', width: '85%', wordBreak: 'break-word' }}>
{SUBSIDY_INFO_MESSAGE}
</Text>
{/* VCOW LOGO */}
{account && <CowBalance account={account} balance={balance} />}
<SubsidyTable {...subsidy} />
</AutoColumn>
)

export default function CowSubsidyModal({
isOpen,
onDismiss,
...restProps
}: Pick<ConfirmationModalProps, 'isOpen'> & Omit<ConfirmationModalContentProps, 'title' | 'topContent'>) {
const { account, chainId } = useActiveWeb3React()

// TODO: update with latest code
const { subsidy, balance } = useCowBalanceAndSubsidy()

const TopContent = useCallback(
() => <CowSubsidyInfo account={account ?? undefined} balance={balance} subsidy={subsidy} />,
[account, balance, subsidy]
)

const BottomContent = useCallback(
() => (
<Row style={{ justifyContent: 'center' }}>
{/* TODO: incoming blogpost URL */}
<ExternalLink href="/#/">Read more about the tokenomics</ExternalLink>
</Row>
),
[]
)

if (!chainId) return null

return (
<GpModal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
<ConfirmationModalContent
{...restProps}
title="CoWmunity fees discount"
onDismiss={onDismiss}
topContent={TopContent}
bottomContent={BottomContent}
/>
</GpModal>
)
}
47 changes: 47 additions & 0 deletions src/custom/components/CowSubsidyModal/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @jest-environment ./custom-test-env.js
*/
import { BigNumber } from 'bignumber.js'
import { formatUnits } from '@ethersproject/units'
import { getDiscountFromBalance } from './utils'

const BALANCES_IN_TIER = [
{ balance: '0', tier: 0 },
{ balance: '100000000000000000', tier: 0 }, // 0.1
{ balance: '99990000000000000000', tier: 0 }, // 99.99
{ balance: '100000000000000000000', tier: 1 }, // 100
{ balance: '100001000000000000000', tier: 1 }, // 100.001
{ balance: '1500000000000000000000', tier: 2 }, // 1500
{ balance: '11000000000000000000000', tier: 3 }, // 11000
{ balance: '174330000000000000000000', tier: 4 }, // 174330
]

const INCORRECT_BALANCES_IN_TIER = [
{ balance: '0', tier: 2 },
{ balance: '100000000000000000', tier: 4 }, // 0.1
{ balance: '99990000000000000000', tier: 2 }, // 99.99
{ balance: '100000000000000000000', tier: 2 }, // 100
{ balance: '150000000000000000000', tier: 0 }, // 150
{ balance: '1500000000000000000000', tier: 4 }, // 1500
{ balance: '174330000000000000000000', tier: 3 }, // 174330
]

describe('FEE DISCOUNT TIERS', () => {
describe('CORRECT DISCOUNTS', () => {
BALANCES_IN_TIER.forEach(({ balance, tier }) => {
it(`USER BALANCE: [${formatUnits(balance, 18)}] equals TIER ${tier}`, () => {
const BALANCE_BN = new BigNumber(balance)

expect(getDiscountFromBalance(BALANCE_BN).tier).toEqual(tier)
})
})
})
describe('INCORRECT DISCOUNTS', () => {
INCORRECT_BALANCES_IN_TIER.forEach(({ balance, tier }) => {
it(`USER BALANCE: [${formatUnits(balance, 18)}] does NOT equal TIER ${tier}`, () => {
const BALANCE_BN = new BigNumber(balance)
expect(getDiscountFromBalance(BALANCE_BN).tier).not.toEqual(tier)
})
})
})
})
24 changes: 24 additions & 0 deletions src/custom/components/CowSubsidyModal/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { BigNumber } from 'bignumber.js'
import { COW_SUBSIDY_DATA } from './constants'

const SLICED_DATA = COW_SUBSIDY_DATA.slice(1)

export function getDiscountFromBalance(balanceAtomsBn: BigNumber) {
let tier = 0
let [, discount] = COW_SUBSIDY_DATA[tier]
// Here we use a sliced verison of our data without index 0 (0 amt tier)
// because loop-wise a balance less than or equal to 0 and 100 (indices 0 and 1, respectively) are the same
for (const [threshold, thresholdDiscount] of SLICED_DATA) {
// Increase our tier number only if we're not at the end of our list
// Is balance less than or equal to threshold?
// return our subsidy information
const thresholdBn = new BigNumber(threshold)
if (balanceAtomsBn.lt(thresholdBn)) return { discount, tier }

// Else assign the current discount as the threshold and iterate one tier
discount = thresholdDiscount
tier++
}

return { discount, tier }
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const FlyoutRow = styled.div<{ active: boolean }>`
}
transition: background 0.13s ease-in-out;
`
const FlyoutRowActiveIndicator = styled.div<{ active: boolean }>`
export const FlyoutRowActiveIndicator = styled.div<{ active: boolean }>`
background-color: ${({ active, theme }) => (active ? theme.green1 : '#a7a7a7')};
border-radius: 50%;
height: 9px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
OperationType,
} from '.' // mod

const Wrapper = styled.div`
export const Wrapper = styled.div`
width: 100%;
padding: 1rem;
display: flex; /* MOD */
Expand Down Expand Up @@ -361,7 +361,7 @@ function L2Content({
)
}

interface ConfirmationModalProps {
export interface ConfirmationModalProps {
isOpen: boolean
onDismiss: () => void
hash?: string | undefined
Expand Down
27 changes: 27 additions & 0 deletions src/custom/hooks/useCowBalanceAndSubsidy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useMemo } from 'react'
import { CurrencyAmount } from '@uniswap/sdk-core'
import { BigNumber } from 'bignumber.js'
import { parseUnits } from '@ethersproject/units'

import { getDiscountFromBalance } from 'components/CowSubsidyModal/utils'
import { SupportedChainId } from 'constants/chains'
import { V_COW } from 'constants/tokens'
import { useActiveWeb3React } from '.'

// TODO: get real balance
const MOCK_BALANCE = CurrencyAmount.fromRawAmount(
V_COW[SupportedChainId.RINKEBY],
parseUnits('111100.97', V_COW[SupportedChainId.MAINNET].decimals).toString()
)
export default function useCowBalanceAndSubsidy() {
const { chainId } = useActiveWeb3React()

// TODO: vcow and cow balance from @nenadV91
/* const vCow = */ chainId ? V_COW[chainId] : undefined
const balance = MOCK_BALANCE // useTotalCowBalance(account || undefined, vCow)

return useMemo(() => {
const balanceBn = new BigNumber(balance.quotient.toString())
return { subsidy: getDiscountFromBalance(balanceBn), balance }
}, [balance])
}
2 changes: 1 addition & 1 deletion src/custom/pages/Claim/ClaimsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type ClaimsTableRowProps = EnhancedUserClaimData & {
isPendingClaim: boolean
}

const ClaimTr = styled.tr<{ isPending?: boolean }>`
export const ClaimTr = styled.tr<{ isPending?: boolean }>`
> td {
background-color: ${({ isPending }) => (isPending ? '#221954' : 'rgb(255 255 255 / 6%)')};
cursor: ${({ isPending }) => (isPending ? 'pointer' : 'initial')};
Expand Down
Loading

0 comments on commit 9b55468

Please sign in to comment.