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

Helper function to reject any pending tx #382

Merged
merged 5 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"prettier": "^3.0.3"
},
"resolutions": {
"graphql": "^16.0.0"
"graphql": "^16.0.0",
"@polkadot/util-crypto": "12.5.1",
"@polkadot/util": "12.5.1"
}
}
9 changes: 9 additions & 0 deletions packages/ui/cypress/fixtures/knownMultisigs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { injectedAccounts } from './injectedAccounts'

export const knownMultisigs = {
'test-multisig-1': {
address: '5CmwqwwLEkEtsmB9gFaTJdCfurz33xyggHnvwHaGKtvmQNxq',
threshold: 2,
signatories: [injectedAccounts[0].address, injectedAccounts[1].address]
}
}
21 changes: 21 additions & 0 deletions packages/ui/cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference types="cypress" />

import { AuthRequests, Extension, TxRequests } from './Extension'
import { MultisigInfo, rejectCurrentMultisigTxs } from '../utils/rejectCurrentMultisigTxs'
import { InjectedAccountWitMnemonic } from '../fixtures/injectedAccounts'

// ***********************************************
Expand Down Expand Up @@ -77,6 +78,8 @@ Cypress.Commands.add('rejectTx', (id: number, reason: string) => {
return extension.rejectTx(id, reason)
})

Cypress.Commands.add('rejectCurrentMultisigTx', rejectCurrentMultisigTxs)

declare global {
namespace Cypress {
interface Chainable {
Expand All @@ -86,43 +89,61 @@ declare global {
* @example cy.initExtension([{ address: '7NPoMQbiA6trJKkjB35uk96MeJD4PGWkLQLH7k7hXEkZpiba', name: 'Alice', type: 'sr25519'}])
*/
initExtension: (accounts: InjectedAccountWitMnemonic[]) => Chainable<AUTWindow>

/**
* Read the authentication request queue.
* @example cy.getAuthRequests().then((authQueue) => { cy.wrap(Object.values(authQueue).length).should("eq", 1) })
*/
getAuthRequests: () => Chainable<AuthRequests>

/**
* Authorize a specific request
* @param {number} id - the id of the request to authorize. This id is part of the getAuthRequests object response.
* @param {string[]} accountAddresses - the account addresses to share with the applications. These addresses must be part of the ones shared in the `initExtension`
* @example cy.enableAuth(1694443839903, ["7NPoMQbiA6trJKkjB35uk96MeJD4PGWkLQLH7k7hXEkZpiba"])
*/
enableAuth: (id: number, accountAddresses: string[]) => void

/**
* Reject a specific request
* @param {number} id - the id of the request to reject. This id is part of the getAuthRequests object response.
* @param {reason} reason - the reason for the rejection
* @example cy.rejectAuth(1694443839903, "Cancelled")
*/
rejectAuth: (id: number, reason: string) => void

/**
* Read the tx request queue.
* @example cy.getTxRequests().then((txQueue) => { cy.wrap(Object.values(txQueue).length).should("eq", 1) })
*/
getTxRequests: () => Chainable<TxRequests>

/**
* Authorize a specific request
* @param {number} id - the id of the request to approve. This id is part of the getTxRequests object response.
* @example cy.approveTx(1694443839903)
*/
approveTx: (id: number) => void

/**
* Reject a specific request
* @param {number} id - the id of the tx request to reject. This id is part of the getTxRequests object response.
* @param {reason} reason - the reason for the rejection
* @example cy.rejectAuth(1694443839903, "Cancelled")
*/
rejectTx: (id: number, reason: string) => void

/**
* Reject all pending multisig requests with a specific account
* @param {InjectedAccountWitMnemonic} opt.account - The account to reject pending transactions with. It should be the proposer
* @param {multisigInfo} opt.multisigInfo - The information about the multisig to remove pending transactions from
* @param {WSendpoint} opt.WSendpoint - The RPC endpoint to connect to to submit the rejection batch transaction
*/
rejectCurrentMultisigTx: (opt: {
account: InjectedAccountWitMnemonic
multisigInfo: MultisigInfo
WSendpoint: string
}) => void
}
}
}
14 changes: 13 additions & 1 deletion packages/ui/cypress/tests/transactions.cy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { injectedAccounts } from '../fixtures/injectedAccounts'
import { knownMultisigs } from '../fixtures/knownMultisigs'
import { landingPageUrl } from '../fixtures/landingData'
import { landingPage } from '../support/page-objects/landingPage'
import { multisigPage } from '../support/page-objects/multisigPage'
Expand Down Expand Up @@ -51,7 +52,18 @@ describe('Perform transactions', () => {
})
})

it.skip('Makes a balance transfer with Alice', () => {
it('Makes a balance transfer with Alice', () => {
cy.rejectCurrentMultisigTx({
account: injectedAccounts[0],
multisigInfo: {
address: knownMultisigs['test-multisig-1'].address,
threshold: knownMultisigs['test-multisig-1'].threshold,
otherSignatories: knownMultisigs['test-multisig-1'].signatories.filter(
(address) => address !== injectedAccounts[0].address
)
},
WSendpoint: 'wss://rococo-rpc.polkadot.io'
})
multisigPage.newTransactionButton().click()
sendTxModal.sendTxTitle().should('be.visible')
fillAndSubmitTransactionForm()
Expand Down
135 changes: 135 additions & 0 deletions packages/ui/cypress/utils/rejectCurrentMultisigTxs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { cryptoWaitReady } from '@polkadot/util-crypto'
import { InjectedAccountWitMnemonic } from '../fixtures/injectedAccounts'
import { Keyring, WsProvider, ApiPromise } from '@polkadot/api'
import { MultisigStorageInfo } from '../../src/types'
import { PendingTx } from '../../src/hooks/usePendingTx'
import { SubmittableExtrinsic } from '@polkadot/api/types'
import { ISubmittableResult, AnyTuple, Codec } from '@polkadot/types/types'
import { StorageKey } from '@polkadot/types'

export interface MultisigInfo {
address: string
otherSignatories: string[]
threshold: number
}
const callBack =
(resolve: (thenableOrResult?: unknown) => void) =>
({ status, txHash }: ISubmittableResult) => {
console.log('Transaction status:', status.type)
if (status.isBroadcast) {
console.log('Broadcasted', txHash.toHex())
}

if (status.isInBlock) {
console.log('In block')
}

if (status.isFinalized) {
console.log('Finalized block hash', status.asFinalized.toHex())
resolve()
}
}

const getPendingMultisixTx = (
multisigTxs: [StorageKey<AnyTuple>, Codec][],
multisigInfo: MultisigInfo
) => {
const curratedMultisigTxs: PendingTx[] = []

multisigTxs.forEach((storage) => {
// this is supposed to be the multisig address that we asked the storage for
const multisigFromChain = (storage[0].toHuman() as Array<string>)[0]
const hash = (storage[0].toHuman() as Array<string>)[1]
const info = storage[1].toJSON() as unknown as MultisigStorageInfo

// Fix for ghost proposals for https://github.com/polkadot-js/apps/issues/9103
// These 2 should be the same
if (multisigFromChain.toLowerCase() !== multisigInfo.address.toLowerCase()) {
console.error(
'The onchain call and the one found in the block donot correspond',
multisigFromChain,
multisigInfo.address
)
return
}

curratedMultisigTxs.push({
hash,
info,
from: multisigInfo.address
})
})

return curratedMultisigTxs
}

const getRejectionsTxs = (
pendingMultisigTxs: PendingTx[],
account: InjectedAccountWitMnemonic,
multisigInfo: MultisigInfo,
api: ApiPromise
) => {
const allTxs: SubmittableExtrinsic<'promise', ISubmittableResult>[] = []
pendingMultisigTxs.forEach((pendingMultisigTx) => {
const depositor = pendingMultisigTx.info.depositor
if (depositor !== account.address) {
console.log('multisig tx not proposed by the same account', depositor, account.address)
return
}

const rejectCurrent = api.tx.multisig.cancelAsMulti(
multisigInfo.threshold,
multisigInfo.otherSignatories,
pendingMultisigTx.info.when,
pendingMultisigTx.hash
)
allTxs.push(rejectCurrent)
})

return allTxs
}

export const rejectCurrentMultisigTxs = ({
account,
multisigInfo,
WSendpoint
}: {
account: InjectedAccountWitMnemonic
multisigInfo: MultisigInfo
WSendpoint: string
}) => {
// this function takes some time waiting for a finalized block
// with the removal of all pending tx. We set a max timout of 30s
return cy.then(
{ timeout: 30000 },
() =>
new Cypress.Promise(async (resolve) => {
await cryptoWaitReady()

const keyring = new Keyring({ type: 'sr25519' })
keyring.addFromMnemonic(account.mnemonic)

const wsProvider = new WsProvider(WSendpoint)
const api = await ApiPromise.create({ provider: wsProvider })

const multisigTxs = await api.query.multisig.multisigs.entries(multisigInfo.address)
const pendingMultisigTxs = getPendingMultisixTx(multisigTxs, multisigInfo)

if (!pendingMultisigTxs.length) {
console.log('no pending multisig tx for', multisigInfo.address)
resolve()
return
}

console.log('pendingMultisigTxs', pendingMultisigTxs)

const allTxs = getRejectionsTxs(pendingMultisigTxs, account, multisigInfo, api)

console.log(`The multisig has ${allTxs.length} pending txs. Rejecting them now`)

api.tx.utility
.batchAll(allTxs)
.signAndSend(keyring.getPair(account.address), callBack(resolve))
})
)
}
2 changes: 1 addition & 1 deletion packages/ui/src/hooks/useGetEncodedAddress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const useGetEncodedAddress = () => {

const getEncodedAddress = useCallback(
(address: string | Uint8Array | undefined) => {
if (!chainInfo || !address) {
if (!chainInfo || !address || address === 'undefined') {
return
}

Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/hooks/usePendingTx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const usePendingTx = (multiProxy?: MultiProxy) => {
// These 2 should be the same
if (multisigFromChain.toLowerCase() !== multisigs[index].toLowerCase()) {
console.error(
'The onchain call and the one found in the block donot correstpond',
'The onchain call and the one found in the block donot correspond',
multisigFromChain,
multisigs[index]
)
Expand Down
Loading