Skip to content

Commit

Permalink
Disabled users from unbonding when node is active (#267)
Browse files Browse the repository at this point in the history
* Disabled users from unbonding when node is active

* case for sign_membership & in standyby mode:
  • Loading branch information
Thorian1te authored May 31, 2024
1 parent 250bf9f commit 98621a4
Show file tree
Hide file tree
Showing 21 changed files with 183 additions and 31 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

- Fix UTXO send price fetch [256](https://github.com/asgardex/asgardex-desktop/pull/257)
- Fix Maya lp Bugs & BSC.USDT [#255](https://github.com/asgardex/asgardex-desktop/pull/258)
- Update Thornode Leedger to use ClientLedger from xchainjs [#259](https://github.com/asgardex/asgardex-desktop/pull/260)
- Update Thornode Ledger to use ClientLedger from xchainjs [#259](https://github.com/asgardex/asgardex-desktop/pull/260)
- Disable Unbonding if node is active [#233] (https://github.com/asgardex/asgardex-desktop/pull/263)

# 1.21.7 (2024-05-23)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "asgardex",
"productName": "ASGARDEX",
"version": "1.21.7",
"version": "1.21.8",
"description": "WALLET AND EXCHANGE CLIENT FOR THORCHAIN",
"main": "index.js",
"scripts": {
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/components/Bonds/Bonds.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const mockNodeInfo = (address: Address) => ({
bondProviders: {
nodeOperatorFee: baseAmount(100000000 * 400000),
providers: []
}
},
signMembership: []
})
const addressValidation: AddressValidation = (_) => true

Expand Down
3 changes: 2 additions & 1 deletion src/renderer/components/Bonds/table/BondsTable.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const mockNodeInfo = (address: Address) => ({
bondProviders: {
nodeOperatorFee: baseAmount(100000000 * 400000),
providers: []
}
},
signMembership: []
})

export const Default: Story = () => {
Expand Down
14 changes: 10 additions & 4 deletions src/renderer/components/Bonds/table/BondsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useMemo, useState } from 'react'

import { Network } from '@xchainjs/xchain-client'
import type * as TN from '@xchainjs/xchain-thornode'
import { Address, baseAmount, BaseAmount } from '@xchainjs/xchain-util'
import { ColumnType } from 'antd/lib/table'
import * as FP from 'fp-ts/function'
Expand Down Expand Up @@ -30,6 +31,7 @@ interface CustomExpandIconProps {
onExpand: (record: string, event: React.MouseEvent<HTMLSpanElement, MouseEvent>) => void // Adjusted for <span>// Adjust YourRecordType accordingly
record: string
}
type ProvidersWithStatus = Providers & { status: TN.NodeStatusEnum; signMembership: string[]; nodeAddress: Address }

export const BondsTable: React.FC<Props> = ({
nodes,
Expand Down Expand Up @@ -100,7 +102,7 @@ export const BondsTable: React.FC<Props> = ({
[intl, network, goToNode]
)

const expandedTableColumns: ColumnType<Providers>[] = [
const expandedTableColumns: ColumnType<ProvidersWithStatus>[] = [
{
title: intl.formatMessage({ id: 'bonds.bondProvider' }),
dataIndex: 'bondAddress',
Expand All @@ -111,11 +113,12 @@ export const BondsTable: React.FC<Props> = ({
title: intl.formatMessage({ id: 'common.action' }),
dataIndex: 'action',
key: 'action',
render: (_, { bondAddress, bond }) => {
render: (_, record) => {
const { bondAddress, bond, status, signMembership, nodeAddress } = record
const isWalletAddress =
walletAddresses.THOR.some((walletInfo) => walletInfo.address === bondAddress) ||
walletAddresses.MAYA.some((walletInfo) => walletInfo.address === bondAddress)

const unbondDisabled = status === 'Active' || (signMembership.includes(nodeAddress) && status === 'Standby')
return (
<div className="flex">
<TextButton
Expand All @@ -126,7 +129,7 @@ export const BondsTable: React.FC<Props> = ({
</TextButton>
<div className="flex border-solid border-gray1 dark:border-gray1d">
<TextButton
disabled={!isWalletAddress}
disabled={!isWalletAddress || unbondDisabled}
size="normal"
onClick={() => goToAction('unbond', matchedNodeAddress, bond)}>
{intl.formatMessage({ id: 'deposit.interact.actions.unbond' })}
Expand Down Expand Up @@ -244,6 +247,9 @@ export const BondsTable: React.FC<Props> = ({
dataSource={record.bondProviders.providers.map((provider: Providers, index: string) => ({
bondAddress: provider.bondAddress,
bond: provider.bond,
status: record.status,
member: record.signMembership,
nodeAddres: record.nodeAddress,
key: `${record.address}-${index}`
}))}
pagination={false}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { bn } from '@xchainjs/xchain-util'
import type * as TN from '@xchainjs/xchain-thornode'
import { baseAmount, bn } from '@xchainjs/xchain-util'
import * as O from 'fp-ts/lib/Option'

import { NodeInfo } from '../../../../services/thorchain/types'
import {
getInteractTypeFromNullableString,
isInteractType,
validateCustomAmountInput,
validateUnboundAmountInput
validateUnboundAmountInput,
findNodeIndex
} from './Interact.helpers'

describe('wallet/interact/helpers', () => {
Expand Down Expand Up @@ -130,4 +133,68 @@ describe('wallet/interact/helpers', () => {
expect(result).toBeNone()
})
})

describe('findNodeIndex', () => {
const nodes: NodeInfo[] = [
{
address: 'thor10czf2s89h79fsjmqqck85cdqeq536hw5ngz4lt',
bond: baseAmount(100000000 * 40000000),
award: baseAmount(100000000 * 400000),
status: 'Active' as TN.NodeStatusEnum,
bondProviders: { providers: [], nodeOperatorFee: baseAmount(100000000 * 400000) }, // Mock bondProviders
signMembership: []
},
{
address: 'thor16ery22gma35h2fduxr0swdfvz4s6yvy6yhskf6',
bond: baseAmount(100000000 * 40000000), // Mock bond value
award: baseAmount(100000000 * 400000), // Mock award value
status: 'Standby' as TN.NodeStatusEnum,
bondProviders: { providers: [], nodeOperatorFee: baseAmount(100000000 * 400000) }, // Mock bondProviders
signMembership: ['thor16ery22gma35h2fduxr0swdfvz4s6yvy6yhskf6']
},
{
address: 'thor13uy6szawgsj9xjs0gq2xddzmcup3zl63khp6gq',
bond: baseAmount(100000000 * 40000000), // Mock bond value
award: baseAmount(100000000 * 400000), // Mock award value
status: 'Standby' as TN.NodeStatusEnum,
bondProviders: { providers: [], nodeOperatorFee: baseAmount(100000000 * 400000) }, // Mock bondProviders
signMembership: []
}
]

it('should find an active node', () => {
const result = findNodeIndex(nodes, 'thor10czf2s89h79fsjmqqck85cdqeq536hw5ngz4lt')
expect(result).toEqual(0)
})

it('should find a standby node with the address in signMembership', () => {
const result = findNodeIndex(nodes, 'thor16ery22gma35h2fduxr0swdfvz4s6yvy6yhskf6')
expect(result).toEqual(1)
})

it('should not find a node if the address does not match any active or standby nodes', () => {
const result = findNodeIndex(nodes, 'thor1invalidaddress1234567890')
expect(result).toEqual(-1)
})

it('should not find a node if the address matches a standby node but is not in signMembership', () => {
const result = findNodeIndex(nodes, 'thor18df3c5lhdskfsjmqqck85cdqeq536hw5ngz4lt')
expect(result).toEqual(-1)
})

it('should not find a node if the status does not match active or standby', () => {
const modifiedNodes = [
{
address: 'thor1nprw0w6ex8xh4tfl3vtkhqnjvds68kwshq9ax9',
bond: baseAmount(100000000 * 40000000), // Mock bond value
award: baseAmount(100000000 * 400000), // Mock award value
status: 'Disabled' as TN.NodeStatusEnum,
bondProviders: { providers: [], nodeOperatorFee: baseAmount(100000000 * 400000) }, // Mock bondProviders
signMembership: []
}
]
const result = findNodeIndex(modifiedNodes, 'thor1nprw0w6ex8xh4tfl3vtkhqnjvds68kwshq9ax9')
expect(result).toEqual(-1)
})
})
})
10 changes: 9 additions & 1 deletion src/renderer/components/wallet/txs/interact/Interact.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IntlShape } from 'react-intl'
import { optionFromNullableString } from '../../../../../shared/utils/fp'
import { greaterThan, greaterThanEqualTo, validateBN } from '../../../../helpers/form/validation'
import { emptyString } from '../../../../helpers/stringHelper'
import { InteractState } from '../../../../services/thorchain/types'
import { InteractState, NodeInfos } from '../../../../services/thorchain/types'
import { InteractType } from './Interact.types'

export const getInteractiveDescription = ({ state, intl }: { state: InteractState; intl: IntlShape }): string => {
Expand Down Expand Up @@ -89,3 +89,11 @@ export const isInteractType = (u: unknown): u is InteractType =>

export const getInteractTypeFromNullableString = (s?: string): O.Option<InteractType> =>
FP.pipe(s, optionFromNullableString, O.chain(O.fromPredicate(isInteractType)))

export const findNodeIndex = (nodes: NodeInfos, inputaddress: string) => {
return nodes.findIndex(
({ address, status, signMembership }) =>
(address.toLowerCase() === inputaddress && status === 'Active') ||
(signMembership.includes(inputaddress) && status === 'Standby')
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { useState } from 'react'

import * as RD from '@devexperts/remote-data-ts'
import { Meta } from '@storybook/react'
import { Network, TxHash } from '@xchainjs/xchain-client'
import { assetAmount, assetToBase, BaseAmount, baseAmount } from '@xchainjs/xchain-util'
import { NodeStatusEnum } from '@xchainjs/xchain-thornode'
import { Address, assetAmount, assetToBase, BaseAmount, baseAmount } from '@xchainjs/xchain-util'
import * as FP from 'fp-ts/lib/function'
import * as O from 'fp-ts/lib/Option'
import * as Rx from 'rxjs'
Expand All @@ -25,6 +29,18 @@ type Args = {
walletType: WalletType
}

const mockNodeInfo = (address: Address) => ({
bond: baseAmount(100000000 * 40000000),
award: baseAmount(100000000 * 400000),
status: NodeStatusEnum.Active,
address,
bondProviders: {
nodeOperatorFee: baseAmount(100000000 * 400000),
providers: []
},
signMembership: []
})

const Template = ({ interactType, txRDStatus, feeRDStatus, balance, validAddress, walletType }: Args) => {
const interact$: InteractStateHandler = (_) => {
const getCurrentStep = () => {
Expand Down Expand Up @@ -55,7 +71,7 @@ const Template = ({ interactType, txRDStatus, feeRDStatus, balance, validAddress
})
}
const { thorchainQuery } = useThorchainQueryContext()

const [nodesList] = useState<Address[]>([])
const feeRD: FeeRD = FP.pipe(
feeRDStatus,
getMockRDValueFactory<Error, BaseAmount>(
Expand Down Expand Up @@ -90,6 +106,7 @@ const Template = ({ interactType, txRDStatus, feeRDStatus, balance, validAddress
poolDetails={[]}
nodeAddress=""
bondAmount=""
nodes={RD.success(nodesList.map((address) => mockNodeInfo(address)))}
/>
)
}
Expand Down
29 changes: 22 additions & 7 deletions src/renderer/components/wallet/txs/interact/InteractFormThor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { useSubscriptionState } from '../../../../hooks/useSubscriptionState'
import { FeeRD } from '../../../../services/chain/types'
import { AddressValidation, GetExplorerTxUrl, OpenExplorerTxUrl } from '../../../../services/clients'
import { INITIAL_INTERACT_STATE } from '../../../../services/thorchain/const'
import { InteractState, InteractStateHandler } from '../../../../services/thorchain/types'
import { InteractState, InteractStateHandler, NodeInfos, NodeInfosRD } from '../../../../services/thorchain/types'
import { ValidatePasswordHandler, WalletBalance } from '../../../../services/wallet/types'
import { LedgerConfirmationModal, WalletPasswordConfirmationModal } from '../../../modal/confirmation'
import { TxModal } from '../../../modal/tx'
Expand Down Expand Up @@ -88,6 +88,7 @@ type Props = {
poolDetails: PoolDetails
nodeAddress: string | null
bondAmount: string | null
nodes: NodeInfosRD
}
export const InteractFormThor: React.FC<Props> = (props) => {
const {
Expand All @@ -107,7 +108,8 @@ export const InteractFormThor: React.FC<Props> = (props) => {
thorchainQuery,
network,
nodeAddress,
bondAmount
bondAmount,
nodes: nodesRD
} = props
const intl = useIntl()

Expand All @@ -118,6 +120,14 @@ export const InteractFormThor: React.FC<Props> = (props) => {

const [_amountToSend, setAmountToSend] = useState<BaseAmount>(ZERO_BASE_AMOUNT)

const nodes: NodeInfos = useMemo(
() =>
FP.pipe(
nodesRD,
RD.getOrElse(() => [] as NodeInfos)
),
[nodesRD]
)
const [memo, setMemo] = useState<string>('')
const amountToSend = useMemo(() => {
switch (interactType) {
Expand Down Expand Up @@ -374,8 +384,13 @@ export const InteractFormThor: React.FC<Props> = (props) => {
)

const addressValidator = useCallback(
async (_: unknown, value: string) =>
FP.pipe(
async (_: unknown, value: string) => {
const inputAddres = value.toLowerCase()
const nodeIndex = H.findNodeIndex(nodes, inputAddres)
if (interactType === 'unbond' && nodeIndex > -1) {
return Promise.reject(intl.formatMessage({ id: 'bonds.validations.bondStatusActive' }))
}
return FP.pipe(
value,
validateAddress(
addressValidation,
Expand All @@ -386,10 +401,10 @@ export const InteractFormThor: React.FC<Props> = (props) => {
(e) => Promise.reject(e),
() => Promise.resolve()
)
),
[addressValidation, intl]
)
},
[addressValidation, interactType, intl, nodes]
)

// Send tx start time
const [sendTxStartTime, setSendTxStartTime] = useState<number>(0)

Expand Down
3 changes: 2 additions & 1 deletion src/renderer/i18n/de/bonds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const bonds: BondsMessages = {
'bonds.node.add': 'Node hinzufügen',
'bonds.node.enterMessage': 'Node-Adresse eingeben',
'bonds.validations.nodeAlreadyAdded': 'Node wurde bereits hinzugefügt',
'bonds.node.removeMessage': 'Bist du sicher, dass du die Node {node} entfernen möchtest?'
'bonds.node.removeMessage': 'Bist Du sicher, dass Du die Node {node} entfernen möchtest?',
'bonds.validations.bondStatusActive': 'Das Abtrennen von einem aktiven Knoten ist nicht erlaubt'
}

export default bonds
3 changes: 2 additions & 1 deletion src/renderer/i18n/en/bonds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const bonds: BondsMessages = {
'bonds.node.add': 'Add node',
'bonds.node.enterMessage': 'Enter node to monitor',
'bonds.validations.nodeAlreadyAdded': 'Node is already added',
'bonds.node.removeMessage': 'Are you sure you want to remove node with address {node} ?'
'bonds.node.removeMessage': 'Are you sure you want to remove node with address {node} ?',
'bonds.validations.bondStatusActive': 'Unbonding from an active node is not allowed'
}

export default bonds
3 changes: 2 additions & 1 deletion src/renderer/i18n/es/bonds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const bonds: BondsMessages = {
'bonds.node.add': 'Añadir nodo',
'bonds.node.enterMessage': 'Introduzca el nodo a supervisar',
'bonds.validations.nodeAlreadyAdded': 'Nodo ya añadido',
'bonds.node.removeMessage': '¿Estás seguro de que quieres eliminar el nodo con dirección {node} ?'
'bonds.node.removeMessage': '¿Estás seguro de que quieres eliminar el nodo con dirección {node} ?',
'bonds.validations.bondStatusActive': 'Desvincularse de un nodo activo no está permitido'
}

export default bonds
3 changes: 2 additions & 1 deletion src/renderer/i18n/fr/bonds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const bonds: BondsMessages = {
'bonds.node.add': 'Ajouter un nœud',
'bonds.node.enterMessage': 'Entrez le nœud à surveiller',
'bonds.validations.nodeAlreadyAdded': 'Le nœud est déjà ajouté',
'bonds.node.removeMessage': 'Êtes-vous sûr de vouloir supprimer le nœud {node} ?'
'bonds.node.removeMessage': 'Êtes-vous sûr de vouloir supprimer le nœud {node} ?',
'bonds.validations.bondStatusActive': "La déliaison d'un nœud actif n'est pas autorisée"
}

export default bonds
3 changes: 2 additions & 1 deletion src/renderer/i18n/hi/bonds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const bonds: BondsMessages = {
'bonds.node.add': 'नोड जोड़ें',
'bonds.node.enterMessage': 'ट्रैकिंग के लिए नोड दर्ज करें',
'bonds.validations.nodeAlreadyAdded': 'नोड पहले से जोड़ा जा चुका है',
'bonds.node.removeMessage': 'क्या आप वाकई में नोड {node} को हटाना चाहते हैं?'
'bonds.node.removeMessage': 'क्या आप वाकई में नोड {node} को हटाना चाहते हैं?',
'bonds.validations.bondStatusActive': 'सक्रिय नोड से अनबॉन्डिंग की अनुमति नहीं है'
}

export default bonds
3 changes: 2 additions & 1 deletion src/renderer/i18n/ru/bonds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const bonds: BondsMessages = {
'bonds.node.add': 'Добавить узел',
'bonds.node.enterMessage': 'Введите узел для отслеживания',
'bonds.validations.nodeAlreadyAdded': 'Узел уже добавлен',
'bonds.node.removeMessage': 'Вы уверены, что хотите удалить узел {node} ?'
'bonds.node.removeMessage': 'Вы уверены, что хотите удалить узел {node} ?',
'bonds.validations.bondStatusActive': 'Развязывание с активным узлом не допускается'
}

export default bonds
1 change: 1 addition & 0 deletions src/renderer/i18n/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ type BondsMessageKey =
| 'bonds.node.removeMessage'
| 'bonds.bondProvider'
| 'bonds.validations.nodeAlreadyAdded'
| 'bonds.validations.bondStatusActive'

export type BondsMessages = { [key in BondsMessageKey]: string }

Expand Down
Loading

0 comments on commit 98621a4

Please sign in to comment.