Skip to content

Commit

Permalink
🎛️ Add support for the Argo bridge constraints proposal (#4856)
Browse files Browse the repository at this point in the history
* Update schema

* Add the AnonymousAccount component

* Add the AddressesPreview renderer

* Display UpdateArgoBridgeConstraints proposals

* Patch `@joystream/types`

* Improve account components stories

* Add the FieldList component

* Simplify the isValidAddress function

* Adjust the FieldList component

* Add UpdateArgoBridgeConstraints proposal creation

* Add a component story for `UpdateArgoBridgeConstraints`

* Test the UpdateArgoBridgeConstraints proposal creation

* Fix transaction button

* Fix validation

* Upgrade `@joystream/types` to `4.6.0`

* Fix linting

* Improve account components stories

* Fix the `UpdateArgoBridgeConstraints` stories name
  • Loading branch information
thesan committed Jun 17, 2024
1 parent d0847b7 commit 2dda054
Show file tree
Hide file tree
Showing 54 changed files with 993 additions and 240 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@babel/parser": "~7.21.0",
"@babel/traverse": "~7.21.0",
"@babel/types": "~7.21.0",
"@joystream/types": "4.5.0",
"@joystream/types": "4.6.0",
"@polkadot/api": "10.7.1",
"@polkadot/api-contract": "10.7.1",
"@polkadot/api-derive": "10.7.1",
Expand Down
19 changes: 19 additions & 0 deletions packages/ui/src/accounts/components/AccountInfo.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Meta } from '@storybook/react'

import { AccountInfo } from './AccountInfo'

export default {
title: 'Accounts/AccountInfo',
component: AccountInfo,
args: {
account: {
name: 'Alice',
address: 'j4VdDQVdwFYfQ2MvEdLT2EYZx4ALPQQ6yMyZopKoZEQmXcJrT',
},
lockType: 'Invitation',
},
} as Meta

export const Default = {
name: 'AccountInfo',
}
32 changes: 32 additions & 0 deletions packages/ui/src/accounts/components/AnonymousAccount.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Meta, StoryObj } from '@storybook/react'
import BN from 'bn.js'
import React from 'react'

import { Row } from '@/common/components/storybookParts/previewStyles'
import { joy } from '@/mocks/helpers'

import { AnonymousAccount } from './AnonymousAccount'

type Args = {
address: string
amount?: number
}

export default {
title: 'Accounts/AnonymousAccount',
component: AnonymousAccount,
args: {
address: 'j4VdDQVdwFYfQ2MvEdLT2EYZx4ALPQQ6yMyZopKoZEQmXcJrT',
amount: 10,
},
} as Meta<Args>

export const Default: StoryObj<Args> = {
name: 'AnonymousAccount',
render: ({ address, amount }) => (
<Row>
<AnonymousAccount address={address} amount={amount ? new BN(joy(amount)) : undefined} />
<AnonymousAccount address={address} />
</Row>
),
}
59 changes: 59 additions & 0 deletions packages/ui/src/accounts/components/AnonymousAccount.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Identicon } from '@polkadot/react-identicon'
import BN from 'bn.js'
import React from 'react'
import styled from 'styled-components'

import { CopyComponent } from '@/common/components/CopyComponent'
import { AccountRow, InfoTitle, InfoValue } from '@/common/components/Modal'
import { TokenValue } from '@/common/components/typography'
import { Colors } from '@/common/constants'
import { shortenAddress } from '@/common/model/formatters'

type Props = {
address: string
amount?: BN
addressLength?: number
}

export const AnonymousAccount = ({ address, amount, addressLength }: Props) => {
return (
<StyledAccountRow>
<Identicon size={40} theme={'beachball'} value={address} />
<Info>
<AccountCopyAddress altText={shortenAddress(address, addressLength)} copyText={address} />
{amount && (
<BalanceInfo>
<InfoTitle>Total Balance: </InfoTitle>
<InfoValue>
<TokenValue value={amount} size="xs" />
</InfoValue>
</BalanceInfo>
)}
</Info>
</StyledAccountRow>
)
}

const StyledAccountRow = styled(AccountRow)`
display: grid;
grid-template-columns: 40px 1fr;
gap: 12px;
align-items: center;
`

const Info = styled.div`
display: flex;
flex-direction: column;
gap: 4px;
`

const AccountCopyAddress = styled(CopyComponent)`
font-size: 16px;
color: ${Colors.Black[900]};
`

const BalanceInfo = styled.div`
display: flex;
align-items: center;
gap: 4px;
`
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { isValidAddress } from '@/accounts/model/isValidAddress'
import { RecoveryConditions } from '@/accounts/model/lockTypes'
import { Account, AccountOption, LockType } from '@/accounts/types'
import { Select, SelectedOption, SelectProps } from '@/common/components/selects'
import { useKeyring } from '@/common/hooks/useKeyring'
import { Address } from '@/common/types'

import { filterByText } from './helpers'
Expand Down Expand Up @@ -41,17 +40,22 @@ interface BaseSelectAccountProps extends SelectAccountProps {

const BaseSelectAccount = React.memo(
({ id, onChange, accounts, filter, selected, disabled, onBlur, isForStaking, variant }: BaseSelectAccountProps) => {
const options = accounts.filter(filter || (() => true))
const options = !filter
? accounts
: accounts.filter(
(account) =>
account.address === selected?.address || // Always keep the selected account (otherwise the select behavior is strange)
filter(account)
)

const [search, setSearch] = useState('')

const filteredOptions = useMemo(() => filterByText(options, search), [search, options])
const keyring = useKeyring()

const notSelected = !selected || selected.address !== search

useEffect(() => {
if (filteredOptions.length === 0 && isValidAddress(search, keyring) && notSelected) {
if (filteredOptions.length === 0 && isValidAddress(search) && notSelected) {
onChange?.(accountOrNamed(accounts, search, 'Unsaved account'))
}
}, [filteredOptions, search, notSelected])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { BalanceInfoInRow, InfoValue } from '@/common/components/Modal'
import { ColumnGapBlock } from '@/common/components/page/PageContent'
import { Option, OptionsListComponent, Select, SelectedOption } from '@/common/components/selects'
import { TextMedium, TokenValue } from '@/common/components/typography'
import { useKeyring } from '@/common/hooks/useKeyring'

interface SelectVestingAccountProps {
selected?: Account
Expand All @@ -29,12 +28,11 @@ export const SelectVestingAccount = ({ selected, onChange, id, disabled }: Selec
const [search, setSearch] = useState('')

const filteredOptions = useMemo(() => filterByText(options, search), [search, options])
const keyring = useKeyring()

const notSelected = !selected || selected?.address !== search

useEffect(() => {
if (filteredOptions.length === 0 && isValidAddress(search, keyring) && notSelected) {
if (filteredOptions.length === 0 && isValidAddress(search) && notSelected) {
onChange?.(accountOrNamed(options, search, 'Unsaved account'))
}
}, [filteredOptions, search, notSelected])
Expand Down
7 changes: 3 additions & 4 deletions packages/ui/src/accounts/model/isValidAddress.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { KeyringInstance } from '@polkadot/keyring/types'
import { KeyringStruct } from '@polkadot/ui-keyring/types'
import { encodeAddress, decodeAddress } from '@polkadot/util-crypto'

import { Address } from '../../common/types'

export function isValidAddress(address: Address, keyring: KeyringInstance | KeyringStruct) {
export function isValidAddress(address: Address) {
try {
keyring.encodeAddress(keyring.decodeAddress(address))
encodeAddress(decodeAddress(address))
} catch (e) {
return false
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BN_ZERO } from '@polkadot/util'
import React, { useMemo, useState } from 'react'
import styled from 'styled-components'

import { AnonymousAccount } from '@/accounts/components/AnonymousAccount'
import { useBalances } from '@/accounts/hooks/useBalance'
import { useVotingOptOutAccounts } from '@/accounts/hooks/useVotingOptOutAccounts'
import { PageHeaderRow, PageHeaderWrapper, PageLayout } from '@/app/components/PageLayout'
Expand All @@ -16,8 +17,6 @@ import { Warning } from '@/common/components/Warning'

import { ElectionTabs } from '../components/ElectionTabs'

import { BlacklistedAccount } from './BlacklistedAccount'

export const BlacklistedAccounts = () => {
const ACCOUNTS_PER_PAGE = 18
const [page, setPage] = useState(1)
Expand Down Expand Up @@ -75,7 +74,7 @@ export const BlacklistedAccounts = () => {
<h6>Accounts ({votingOptOutAccounts?.length})</h6>
<BlacklistedAccountsList>
{paginatedAccounts.map((account, i) => (
<BlacklistedAccount key={i} account={account} />
<AnonymousAccount key={i} address={account.address} amount={account.balance} />
))}
</BlacklistedAccountsList>
<Pagination
Expand Down
65 changes: 65 additions & 0 deletions packages/ui/src/app/pages/Proposals/CurrentProposals.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { member } from '@/mocks/data/members'
import { generateProposals, MAX_ACTIVE_PROPOSAL, proposalsPagesChain } from '@/mocks/data/proposals'
import {
Container,
findBySelector,
getButtonByText,
getEditorByLabel,
isoDate,
Expand Down Expand Up @@ -115,6 +116,10 @@ export default {
mocks: ({ args, parameters }: StoryContext<Args>): MocksParameters => {
const alice = member('alice', { isCouncilMember: args.isCouncilMember })

const aliceAddress = alice.controllerAccount
const bobAddress = member('bob').controllerAccount
const charlieAddress = member('charlie').controllerAccount

const forumWG = {
id: 'forumWorkingGroup',
name: 'forumWorkingGroup',
Expand Down Expand Up @@ -166,6 +171,7 @@ export default {
members: {
stakingAccountIdMemberStatus: parameters.stakingAccountIdMemberStatus,
},

projectToken: {
palletFrozen: args.palletFrozen,

Expand All @@ -179,6 +185,14 @@ export default {
minSaleDuration: 300,
salePlatformFee: 30_000,
},

argoBridge: {
operatorAccount: aliceAddress,
pauserAccounts: [bobAddress, charlieAddress],
bridgingFee: joy(0.1),
thawnDuration: 200,
remoteChains: [37, 42],
},
},
tx: {
proposalsEngine: {
Expand Down Expand Up @@ -1682,3 +1696,54 @@ export const SpecificUpdateTokenPalletTokenConstraints: Story = {
}
),
}

export const SpecificParametersUpdateArgoBridgeConstraints: Story = {
play: specificParametersTest('Update Argo Bridge Constraints', async ({ args, createProposal, modal, step }) => {
const aliceAddress = alice.controllerAccount
const bobAddress = member('bob').controllerAccount
const daveAddress = member('dave').controllerAccount

await createProposal(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))

// Set Bob as operator
const operatorAccountSelector = await modal.findByTestId('operatorAccount')
await userEvent.click(await findBySelector(operatorAccountSelector, '.ui-toggle'))
await userEvent.type(await modal.findByTestId('operatorAccount-input'), bobAddress)

// Set Alice and Dave as pausers, and remove Charlie
const pauserAccountSection = await modal.findByTestId('pauserAccounts')
await selectFromDropdown(modal, await modal.findByTestId('pauserAccounts-0'), 'alice')
const addPauserAccountButton = await within(pauserAccountSection).findByRole('button', { name: 'Add account' })
await userEvent.click(addPauserAccountButton)
await userEvent.type(await modal.findByTestId('pauserAccounts-2-input'), daveAddress)
await userEvent.click(await within(pauserAccountSection).findByTestId('pauserAccounts-1-remove'))

// Set bridging fee to 0.2 JOY
const bridgingFeeAmount = await modal.findByLabelText('Bridging fee')
await userEvent.clear(bridgingFeeAmount)
await userEvent.type(bridgingFeeAmount, '0.2')

// Set thawn duration to 100 blocks
const thawnDuration = await modal.findByLabelText('Thawn duration')
await userEvent.clear(thawnDuration)
await userEvent.type(thawnDuration, '100')

// Add remote chain 123 (leave the rest)
const addRemoteChainButton = await modal.findByRole('button', { name: 'Add chain' })
await userEvent.click(addRemoteChainButton)
await userEvent.type(await modal.findByTestId('remoteChains-2'), '123')
})

await step('Transaction parameters', () => {
const [, , specificParameters] = args.onCreateProposal.mock.calls.at(-1)
expect(specificParameters.toJSON().updateArgoBridgeConstraints).toEqual({
operatorAccount: bobAddress,
pauserAccounts: [aliceAddress, daveAddress],
bridgingFee: Number(joy(0.2)),
thawnDuration: 100,
remoteChains: [37, 42, 123],
})
})
}),
}
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,9 @@ export const DecreaseCouncilBudget: Story = {
export const UpdateTokenPalletTokenConstraints: Story = {
args: { type: 'UpdateTokenPalletTokenConstraintsProposalDetails' },
}
export const UpdateArgoBridgeConstraints: Story = {
args: { type: 'UpdateArgoBridgeConstraintsProposalDetails' },
}

// Disabled proposals
export const Veto: Story = {
Expand Down
Loading

0 comments on commit 2dda054

Please sign in to comment.