Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement edit multi signature profile #5358

Merged
merged 26 commits into from
Oct 10, 2023
Merged
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
bf5572d
feature: implemented edit multisignature feature
eniolam1000752 Oct 2, 2023
1760006
wip: resolved signing issue
eniolam1000752 Oct 4, 2023
93c9fe7
wip: fixing signatures view
eniolam1000752 Oct 5, 2023
1655549
wip: debugging issue with core/service on jenkins
eniolam1000752 Oct 5, 2023
6959b51
fix: Refactored register multi sig flow for an edit multi-sig registr…
eniolam1000752 Oct 5, 2023
d895883
fix: resolved deepscan issues
eniolam1000752 Oct 5, 2023
e84d0de
fix: resolved merge conflict
eniolam1000752 Oct 5, 2023
fb13dec
fix: resolved issue with register multi sig form
eniolam1000752 Oct 5, 2023
cb9cd63
fix: resolved issue with register multi sig status
eniolam1000752 Oct 5, 2023
242ad55
fix: resolved issue on statusConfig
eniolam1000752 Oct 5, 2023
ca69f37
fixed issue with summary test
eniolam1000752 Oct 5, 2023
60c2acb
fix: resolved coverage on register multi sig summary
eniolam1000752 Oct 5, 2023
e7eb8e0
fix: resolved all unit tests
eniolam1000752 Oct 5, 2023
357f191
fix: resolved issue from manu
eniolam1000752 Oct 6, 2023
8b1d832
fix: updated text
eniolam1000752 Oct 6, 2023
8936211
fix: resolved registration signing from a non multi sig account
eniolam1000752 Oct 6, 2023
065d283
fix: resolved test coverage issues
eniolam1000752 Oct 6, 2023
32a770b
fix: resolved deep scan issue
eniolam1000752 Oct 6, 2023
6f0f62f
fix: resolved issue with signing if not member
eniolam1000752 Oct 8, 2023
c9f7ad9
fix: resolved issue with previous state retention on reigster multi-sig
eniolam1000752 Oct 8, 2023
924abc9
fix: resolved failing unit test
eniolam1000752 Oct 8, 2023
3cb6ab9
fix: refactored ui on params and root signature display
eniolam1000752 Oct 9, 2023
1e1a2e6
fix: resolved issue with account details not showing
eniolam1000752 Oct 9, 2023
2c9d2d0
fix: resolved issue with memeber not in params memeber
eniolam1000752 Oct 9, 2023
4853f13
fix: resolved summary unit test
eniolam1000752 Oct 10, 2023
b68bdd0
fix: resolved failing unit test
eniolam1000752 Oct 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/locales/en/common.json
Original file line number Diff line number Diff line change
@@ -232,6 +232,7 @@
"Estimated reward": "Estimated reward",
"Events": "Events",
"Execution status": "Execution status",
"Existing members": "Existing members",
"Expand": "Expand",
"Expand sidebar": "Expand sidebar",
"Expiry": "Expiry",
@@ -505,12 +506,12 @@
"Redo": "Redo",
"Register keys": "Register keys",
"Register multisignature": "Register multisignature",
"Register multisignature account": "Register multisignature account",
"Register sidechain": "Register sidechain",
"Register validator": "Register validator",
"Register validator summary": "Register validator summary",
"Registered": "Registered",
"Registered validators": "Registered validators",
"Registering members": "Registering members",
"Registrant": "Registrant",
"Reject": "Reject",
"Rejected the pairing request from {{sanitizedName}}": "Rejected the pairing request from {{sanitizedName}}",
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import { truncateAddress, truncateAccountName } from '@wallet/utils/account';
import { mockHWAccounts } from '@hardwareWallet/__fixtures__';
import { mockAppsTokens } from '@token/fungible/__fixtures__';
import { useTokenBalances } from '@token/fungible/hooks/queries';
import { useAuth } from '@auth/hooks/queries';
import AccountManagementDropdown from './AccountManagementDropdown';

const mockCurrentAccount = mockSavedAccounts[0];
@@ -14,8 +15,12 @@ jest.mock('../../../account/hooks/useCurrentAccount.js', () => ({
}));

jest.mock('@token/fungible/hooks/queries/useTokenBalances');
jest.mock('@auth/hooks/queries/useAuth');

describe('AccountManagementDropdown', () => {
useAuth.mockReturnValue({
data: {},
});
useTokenBalances.mockReturnValue({
data: {
data: [{ name: 'Lisk', symbol: 'LSK', availableBalance: 0, ...mockAppsTokens.data[0] }],
@@ -76,4 +81,19 @@ describe('AccountManagementDropdown', () => {
fireEvent.click(screen.getByText('Backup account'));
expect(screen.getByText('Register multisignature account')).toBeVisible();
});

it('Should have edit register multisignature enabled', () => {
useAuth.mockReturnValue({
data: { data: { numberOfSignatures: 3 } },
});

const props = {
currentAccount: mockHWAccounts[0],
onMenuClick: mockOnMenuClick,
};
renderWithRouterAndQueryClient(AccountManagementDropdown, props);
fireEvent.click(screen.getByAltText('dropdownArrowIcon'));
fireEvent.click(screen.getByText('Backup account'));
expect(screen.getByText('Edit multisignature account')).toBeVisible();
});
});
4 changes: 2 additions & 2 deletions src/modules/account/const/accountMenu.js
Original file line number Diff line number Diff line change
@@ -37,8 +37,8 @@ export const accountMenu = ({
}
: {},
icon: 'multiSignatureOutline',
label: 'Register multisignature account',
isHidden: authData?.data?.numberOfSignatures > 0 || hasNetworkError || isLoadingNetwork,
isHidden: hasNetworkError || isLoadingNetwork,
label: `${authData?.data?.numberOfSignatures > 0 ? 'Edit' : 'Register'} multisignature account`,
},
{
component: 'removeSelectedAccount',
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ const Members = ({ members = [], t }) => {
const rightColumn = members.slice(sliceIndex, members.length);
return (
<div className={styles.membersContainer}>
<p>{t('Members')}</p>
<p>{t('Registering members')}</p>
<div>
{leftColumn.map((member, i) => (
<Member member={member} i={i} key={`registerMultiSignature-members-list-${i}`} t={t} />
@@ -58,17 +58,27 @@ const InfoColumn = ({ title, children, className }) => (
</div>
);

const MultiSignatureReview = ({ t, members, fee, numberOfSignatures, token }) => (
const MultiSignatureReview = ({
t,
members,
fee,
numberOfSignatures,
token,
isMultisignature,
isRegisterMultisigature,
}) => (
<>
<Members members={members} t={t} />
<div className={styles.infoContainer}>
<InfoColumn title={t('Required signatures')} className="info-numberOfSignatures">
{numberOfSignatures}
</InfoColumn>
<InfoColumn title={t('Fees')} className="info-fee">
<TokenAmount val={fee} token={token} />
</InfoColumn>
</div>
{!isMultisignature && isRegisterMultisigature && (
<div className={styles.infoContainer}>
<InfoColumn title={t('Required signatures')} className="info-numberOfSignatures">
{numberOfSignatures}
</InfoColumn>
<InfoColumn title={t('Fees')} className="info-fee">
<TokenAmount val={fee} token={token} />
</InfoColumn>
</div>
)}
</>
);

Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@ describe('Multisignature Review component', () => {
fee: 2000000,
numberOfSignatures: 2,
token: { ...mockAppsTokens.data[0], availableBalance: '1000000000' },
isMultisignature: false,
isRegisterMultisigature: true,
};

beforeEach(() => {
23 changes: 11 additions & 12 deletions src/modules/transaction/components/Multisignature/Multisignature.js
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@ const ErrorActions = ({ t, status, message, network, application }) => (
</a>
);

// eslint-disable-next-line max-statements
// eslint-disable-next-line max-statements, complexity
const Multisignature = ({
transactions,
title,
@@ -107,7 +107,6 @@ const Multisignature = ({
};

useEffect(() => resetTransactionResult, []);

useEffect(() => () => clearTimeout(ref.current), []);

return (
@@ -135,17 +134,17 @@ const Multisignature = ({
/>
) : null}
{status.code !== txStatusTypes.broadcastSuccess &&
status.code !== txStatusTypes.broadcastError ? (
<SecondaryButton className={`${styles.copy} copy-button`} onClick={onCopy}>
<span className={styles.buttonContent}>
<Icon name={copied ? 'transactionStatusSuccessful' : 'copy'} />
{t(copied ? 'Copied' : 'Copy')}
</span>
</SecondaryButton>
) : null}
{status.code === txStatusTypes.multisigSignatureSuccess ? (
status.code !== txStatusTypes.broadcastError && (
<SecondaryButton className={`${styles.copy} copy-button`} onClick={onCopy}>
<span className={styles.buttonContent}>
<Icon name={copied ? 'transactionStatusSuccessful' : 'copy'} />
{t(copied ? 'Copied' : 'Copy')}
</span>
</SecondaryButton>
)}
{(status.code === txStatusTypes.multisigSignatureSuccess) && (
<FullySignedActions onDownload={onDownload} t={t} onSend={onSend} />
) : null}
)}
{status.code === txStatusTypes.multisigSignaturePartialSuccess ? (
<PartiallySignedActions onDownload={onDownload} t={t} />
) : null}
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import React from 'react';
import { extractAddressFromPublicKey } from '@wallet/utils/account';
import MultiSignatureReview from '../MultiSignatureReview';

const RegisterMultisignatureGroup = ({ t, transactionJSON, formProps }) => {
const RegisterMultisignatureGroup = ({ t, transactionJSON, formProps, account }) => {
const mandatory = transactionJSON.params.mandatoryKeys.map((item) => ({
address: extractAddressFromPublicKey(item),
publicKey: item,
@@ -21,6 +21,8 @@ const RegisterMultisignatureGroup = ({ t, transactionJSON, formProps }) => {
token={formProps.fields.token}
members={[...mandatory, ...optional]}
numberOfSignatures={transactionJSON.params.numberOfSignatures}
isMultisignature={account.summary.isMultisignature}
isRegisterMultisigature
/>
);
};
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ export { default as Recipient } from './recipient';
export { default as NumberOfSignatures } from './numberOfSignatures';
export { default as Sender } from './sender';
export { default as SignedAndRemainingMembersList } from './signedAndRemainingMembersList';
export { default as SignedAndRemainingSignatureList } from './signedAndRemainingSignatureList';
export { default as TransactionId } from './transactionId';
export { default as TxDate } from './txDate';
export { default as Stakes } from './stakes';
Original file line number Diff line number Diff line change
@@ -186,7 +186,8 @@
"sender sender"
"numberOfSignatures fee"
"members members"
"signedAndRemainingMembersList signedAndRemainingMembersList";
"signedAndRemainingMembersList signedAndRemainingMembersList"
"signedAndRemainingSignatureList signedAndRemainingSignatureList";
}

.reportValidatorMisbehavior {
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ import {
Module,
Nonce,
GenericParams,
SignedAndRemainingSignatureList,
} from 'src/modules/transaction/components/TransactionDetails';
import styles from './layoutSchema.css';

@@ -94,7 +95,14 @@ export const LayoutSchema = {
className: styles.multiSigLayout,
},
[`${registerMultisignature}-preview`]: {
components: [Sender, Members, NumberOfSignatures, Fee, SignedAndRemainingMembersList],
components: [
Sender,
Members,
NumberOfSignatures,
Fee,
SignedAndRemainingMembersList,
SignedAndRemainingSignatureList,
],
className: styles.multiSigRegisterPreview,
},
[unlock]: {
Original file line number Diff line number Diff line change
@@ -39,6 +39,7 @@ const SignedAndRemainingMembersList = ({ t }) => {
needed={needed}
required={required}
className={styles.signedAndRemainingMembersList}
title={isMultisignatureRegistration ? 'Multisignature params signature' : 'Members'}
t={t}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { useMemo, useContext } from 'react';
import { SignedAndRemainingMembers } from '@wallet/components/multisignatureMembers';
import { calculateRemainingAndSignedMembers } from '@wallet/utils/account';
import TransactionDetailsContext from '../../context/transactionDetailsContext';
import styles from './styles.css';

const SignedAndRemainingSignatureList = ({ t }) => {
const { transaction, wallet } = useContext(TransactionDetailsContext);
const keys = wallet.keys;

const { signed, remaining } = useMemo(
() => calculateRemainingAndSignedMembers(keys, transaction, false),
[wallet]
);

const required = keys.numberOfSignatures;
const needed = required - signed.length;

return (
<SignedAndRemainingMembers
signed={signed}
remaining={remaining}
needed={needed}
required={required}
className={styles.signedAndRemainingSignatureList}
title="Signatures"
t={t}
/>
);
};

export default SignedAndRemainingSignatureList;
Original file line number Diff line number Diff line change
@@ -162,6 +162,10 @@
grid-area: signedAndRemainingMembersList;
}

.signedAndRemainingSignatureList {
grid-area: signedAndRemainingSignatureList;
}

.genericParams {
grid-area: genericParams;
padding-top: var(--vertical-padding-m);
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import CustomTransactionInfo from './CustomTransactionInfo';

const Members = ({ members, t }) => (
<section>
<label>{t('Members')}</label>
<label>{t('Existing members')}</label>
<div className={styles.membersContainer}>
{members.map((member, i) => (
<div className={styles.memberInfo} key={i + 1}>
10 changes: 6 additions & 4 deletions src/modules/transaction/configuration/statusConfig.js
Original file line number Diff line number Diff line change
@@ -131,24 +131,26 @@ export const getTransactionStatus = (account, transactions, options = {}) => {
// signature success
if (!isEmpty(transactions.signedTransaction)) {
const numberOfSignatures = getNumberOfSignatures(account, transactions.signedTransaction);
const isRegisterMultisignature =
moduleCommand === MODULE_COMMANDS_NAME_MAP.registerMultisignature;
const isMultisignature = account?.summary?.isMultisignature || options.isMultisignature;

let nonEmptySignatures = transactions.signedTransaction.signatures.filter(
(sig) => sig.length > 0
).length;

if (moduleCommand === MODULE_COMMANDS_NAME_MAP.registerMultisignature) {
if (isRegisterMultisignature && !isMultisignature) {
nonEmptySignatures = transactions.signedTransaction.params.signatures.filter(
(sig) => sig.compare(Buffer.alloc(64)) > 0
).length;
}

const isMultisignature = account?.summary?.isMultisignature || options.isMultisignature;

if (
nonEmptySignatures < numberOfSignatures ||
(isMultisignature &&
nonEmptySignatures === numberOfSignatures &&
!options.canSenderSignTx &&
moduleCommand === MODULE_COMMANDS_NAME_MAP.registerMultisignature)
isRegisterMultisignature)
) {
return { code: txStatusTypes.multisigSignaturePartialSuccess };
}
8 changes: 6 additions & 2 deletions src/modules/transaction/configuration/statusConfig.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { mockCommandParametersSchemas } from 'src/modules/common/__fixtures__';
import wallets from '@tests/constants/wallets';
import { getTransactionStatus } from './statusConfig';
import { txStatusTypes } from './txStatus';

@@ -74,10 +75,13 @@ describe('Transaction signature status', () => {
});

it('should return transaction status for fully signed transaction', () => {
const account = {};
const account = { summary: { isMultisignature: true } };
const transactions = {
txSignatureError: null,
signedTransaction,
signedTransaction: {
...signedTransaction,
signatures: [...signedTransaction.signatures, wallets.genesis.summary.publicKey],
},
};
const isMultisignature = true;
const status = getTransactionStatus(account, transactions, {
20 changes: 19 additions & 1 deletion src/modules/transaction/utils/transaction.js
Original file line number Diff line number Diff line change
@@ -281,6 +281,7 @@ const signTransactionUsingHW = async (
return { ...signedTransaction, id };
};

// eslint-disable-next-line max-statements
export const sign = async (
wallet,
schema,
@@ -290,11 +291,28 @@ export const sign = async (
senderAccount,
options
) => {
const moduleCommand = joinModuleAndCommand(transaction);
if (wallet.metadata?.isHW) {
return signTransactionUsingHW(wallet, schema, chainID, transaction, senderAccount, options);
}

if (options?.txInitiatorAccount?.numberOfSignatures > 0) {
const isMultiSignatureAccount = options?.txInitiatorAccount?.numberOfSignatures > 0;
const isRegisterMultisignature =
moduleCommand === MODULE_COMMANDS_NAME_MAP.registerMultisignature;
const paramSignatures = transaction.params.signatures;
const areParamsSignaturesFullySigned =
isMultiSignatureAccount &&
paramSignatures?.filter((sig) => sig.length !== 64 || Buffer.from(sig).equals(Buffer.alloc(64)))
.length === 0 &&
paramSignatures?.length ===
transaction.params.mandatoryKeys?.length + transaction.params.optionalKeys?.length;

const isEditRegisterMultiSignature = isRegisterMultisignature && isMultiSignatureAccount;

if (
(isMultiSignatureAccount && !isRegisterMultisignature) ||
(isEditRegisterMultiSignature && areParamsSignaturesFullySigned)
) {
return signMultisigUsingPrivateKey(
schema,
chainID,
Loading