From 76bbaa85188ac0f34400c4b189d13ece33248179 Mon Sep 17 00:00:00 2001 From: Noah Saso Date: Tue, 7 Jan 2025 15:54:00 -0500 Subject: [PATCH] fix authz exec action match error when unable to decode sender from unknown stargate message --- packages/i18n/locales/en/translation.json | 1 + packages/state/utils/message.ts | 50 +++++++------ .../core/actions/AuthzExec/Component.tsx | 72 ++++++++----------- .../actions/core/actions/AuthzExec/index.tsx | 54 +++++++++++--- .../stateful/actions/providers/wallet.tsx | 7 +- packages/types/protobuf/utils.ts | 5 ++ 6 files changed, 115 insertions(+), 74 deletions(-) diff --git a/packages/i18n/locales/en/translation.json b/packages/i18n/locales/en/translation.json index 588f62d57f..c360dd8040 100644 --- a/packages/i18n/locales/en/translation.json +++ b/packages/i18n/locales/en/translation.json @@ -1141,6 +1141,7 @@ "executeSmartContractActionDescription": "Execute a message on a smart contract.", "executorAccountTooltip": "This is the account that will be executing the message on the smart contract. The contract will see this as the `sender`.", "extensionsDescription": "Optionally set up extensions to expand your DAO's capabilities. You can always set these up later.", + "failedToDecodeAddressUnrecognizedMessage": "Failed to decode address from an unrecognized message.", "failing": "Failing", "feeShareDescription": "Earn gas fees from a smart contract you deployed.", "feedDescription": "Open proposals in the DAOs you follow will show up here.", diff --git a/packages/state/utils/message.ts b/packages/state/utils/message.ts index 2d46728bab..355400700d 100644 --- a/packages/state/utils/message.ts +++ b/packages/state/utils/message.ts @@ -20,12 +20,14 @@ export const processMessage: MessageProcessor = async ({ queryClient, errorIfNoRemoteAccount = false, }) => { - const accounts = await queryClient.fetchQuery( - accountQueries.list(queryClient, { - chainId, - address: sender, - }) - ) + const accounts = sender + ? await queryClient.fetchQuery( + accountQueries.list(queryClient, { + chainId, + address: sender, + }) + ) + : [] const decodedMessage = decodeMessage(message) @@ -43,15 +45,17 @@ export const processMessage: MessageProcessor = async ({ // If not found for some reason, query for it. if (!account) { // Get proxy on destination chain. - const proxy = await queryClient.fetchQuery( - polytoneNoteQueries.remoteAddress(queryClient, { - chainId, - contractAddress: decodedPolytone.polytoneConnection.note, - args: { - localAddress: sender, - }, - }) - ) + const proxy = sender + ? await queryClient.fetchQuery( + polytoneNoteQueries.remoteAddress(queryClient, { + chainId, + contractAddress: decodedPolytone.polytoneConnection.note, + args: { + localAddress: sender, + }, + }) + ) + : undefined if (errorIfNoRemoteAccount && !proxy) { throw new Error( @@ -97,13 +101,15 @@ export const processMessage: MessageProcessor = async ({ // If not found, query for it. if (!account) { // Get remote ICA on destination chain. - const remoteIcaAddress = await queryClient.fetchQuery( - accountQueries.remoteIcaAddress({ - srcChainId: chainId, - address: sender, - destChainId: decodedIca.chainId, - }) - ) + const remoteIcaAddress = sender + ? await queryClient.fetchQuery( + accountQueries.remoteIcaAddress({ + srcChainId: chainId, + address: sender, + destChainId: decodedIca.chainId, + }) + ) + : undefined if (errorIfNoRemoteAccount && !remoteIcaAddress) { throw new Error( diff --git a/packages/stateful/actions/core/actions/AuthzExec/Component.tsx b/packages/stateful/actions/core/actions/AuthzExec/Component.tsx index ebbd450efb..82594ef8a3 100644 --- a/packages/stateful/actions/core/actions/AuthzExec/Component.tsx +++ b/packages/stateful/actions/core/actions/AuthzExec/Component.tsx @@ -11,13 +11,12 @@ import { useChain, } from '@dao-dao/stateless' import { - AddressInputProps, NestedActionsEditorFormData, StatefulEntityDisplayProps, UnifiedCosmosMsg, } from '@dao-dao/types' import { ActionComponent } from '@dao-dao/types/actions' -import { isValidBech32Address, makeValidateAddress } from '@dao-dao/utils' +import { isValidBech32Address } from '@dao-dao/utils' export type AuthzExecData = { chainId: string @@ -35,7 +34,6 @@ export type AuthzExecOptions = { // to render. msgPerSenderIndex?: number - AddressInput: ComponentType> EntityDisplay: ComponentType } & NestedActionsEditorOptions & Omit @@ -45,59 +43,49 @@ export const AuthzExecComponent: ActionComponent = ( ) => { const { t } = useTranslation() const { bech32Prefix } = useChain() - const { watch, register } = useFormContext() + const { watch } = useFormContext() const { fieldNamePrefix, - errors, isCreating, - options: { msgPerSenderIndex, AddressInput, EntityDisplay, ...options }, + options: { msgPerSenderIndex, EntityDisplay, ...options }, } = props const address = watch((fieldNamePrefix + 'address') as 'address') const msgsPerSender = watch((fieldNamePrefix + '_msgs') as '_msgs') || [] + const sender = !isCreating + ? msgsPerSender[msgPerSenderIndex!]?.sender + : undefined + return ( <> - {/* When creating, show common address field for all messages. When not creating, msgs will be grouped by sender and displayed in order, which if created via this action, will look the same with one address at the top and many actions below it. */} - {isCreating && ( + {isValidBech32Address(address, bech32Prefix) && isCreating ? ( <> - + - + - )} - - {(isValidBech32Address(address, bech32Prefix) || !isCreating) && ( - <> - {isCreating ? ( - <> - - - - - ) : ( -
- - + ) : ( + !isCreating && ( +
+ + {sender ? ( + + ) : ( +

+ {t('info.failedToDecodeAddressUnrecognizedMessage')} +

+ )} - - -
- )} - + + +
+ ) )} ) diff --git a/packages/stateful/actions/core/actions/AuthzExec/index.tsx b/packages/stateful/actions/core/actions/AuthzExec/index.tsx index 42a1cf637d..4d2bd2e5da 100644 --- a/packages/stateful/actions/core/actions/AuthzExec/index.tsx +++ b/packages/stateful/actions/core/actions/AuthzExec/index.tsx @@ -1,5 +1,6 @@ import { useQueryClient } from '@tanstack/react-query' import { useFormContext } from 'react-hook-form' +import { useTranslation } from 'react-i18next' import { contractQueries, processMessage } from '@dao-dao/state' import { @@ -7,6 +8,7 @@ import { ActionsContext, ChainProvider, DaoSupportedChainPickerInput, + InputLabel, LockWithKeyEmoji, useActionOptions, useChain, @@ -29,6 +31,8 @@ import { getChainForChainId, isDecodedStargateMsg, isValidBech32Address, + makeEmptyUnifiedProfile, + makeValidateAddress, maybeMakePolytoneExecuteMessages, } from '@dao-dao/utils' @@ -40,6 +44,7 @@ import { } from '../../../../components' import { useQueryLoadingData } from '../../../../hooks' import { useActionEncodeContext } from '../../../context' +import { BaseActionsProvider } from '../../../providers/base' import { WalletActionsProvider } from '../../../providers/wallet' import { AuthzExecData, @@ -65,7 +70,6 @@ const InnerComponentLoading: ActionComponent = (props) => ( options={{ msgPerSenderIndex: props.options.msgPerSenderIndex, encodeContext: useActionEncodeContext(), - AddressInput, EntityDisplay, SuspenseLoader, }} @@ -79,7 +83,6 @@ const InnerComponent: ActionComponent = (props) => ( options={{ msgPerSenderIndex: props.options.msgPerSenderIndex, encodeContext: useActionEncodeContext(), - AddressInput, EntityDisplay, SuspenseLoader, }} @@ -90,14 +93,17 @@ const InnerComponentWrapper: ActionComponent< InnerOptions & { address: string } > = (props) => { const { + isCreating, options: { address }, } = props const { chainId, bech32Prefix } = useChain() + const queryClient = useQueryClient() + const validAddress = !!address && isValidBech32Address(address, bech32Prefix) const isDao = useQueryLoadingData( contractQueries.isDao( - useQueryClient(), - address && isValidBech32Address(address, bech32Prefix) + queryClient, + validAddress ? { chainId, address, @@ -107,9 +113,9 @@ const InnerComponentWrapper: ActionComponent< false ) - return isDao.loading || isDao.updating ? ( + return (validAddress || isCreating) && (isDao.loading || isDao.updating) ? ( - ) : isDao.data ? ( + ) : !isDao.loading && !isDao.updating && isDao.data ? ( - ) : ( + ) : validAddress || isCreating ? ( + ) : ( + // If no address is defined and we're not creating, that means the component + // failed to detect the sender, likely due to not knowing how to decode the + // message which would contain the sender. Use an empty profile and accounts + // so the component can still render. + + + ) } const Component: ActionComponent = (props) => { + const { t } = useTranslation() const { context } = useActionOptions() // Load DAO info for chosen DAO. - const { watch } = useFormContext() + const { register, watch } = useFormContext() const address = watch((props.fieldNamePrefix + 'address') as 'address') || '' const msgsPerSender = watch((props.fieldNamePrefix + '_msgs') as '_msgs') ?? [] const chainId = watch((props.fieldNamePrefix + 'chainId') as 'chainId') + const { bech32Prefix } = getChainForChainId(chainId) // When creating, just show one form for the chosen address. When not // creating, render a form for each sender message group since the component @@ -155,6 +178,21 @@ const Component: ActionComponent = (props) => { {/* Re-render when chain changes so hooks and state reset. */} + {/* When creating, show common address field for all messages. When not creating, msgs will be grouped by sender and displayed in order, which if created via this action, will look the same with one address at the top and many actions below it. */} + {props.isCreating && ( + <> + + + + + )} + {props.isCreating ? ( { - const { address: connectedAddress, chain } = useWallet() + const { + address: connectedAddress, + chain: { chainId }, + } = useWallet() const address = overrideAddress === undefined ? connectedAddress : overrideAddress @@ -26,7 +29,7 @@ export const WalletActionsProvider = ({ const accounts = useQueryLoadingDataWithError( address ? accountQueries.list(queryClient, { - chainId: chain.chainId, + chainId, address, }) : undefined diff --git a/packages/types/protobuf/utils.ts b/packages/types/protobuf/utils.ts index e5d05f94c5..0bceeba3c5 100644 --- a/packages/types/protobuf/utils.ts +++ b/packages/types/protobuf/utils.ts @@ -617,6 +617,11 @@ export const decodedStargateMsgToCw = ( value, }, }) + // Attempt to decode the sender from the message. + if ('sender' in value && typeof value.sender === 'string') { + sender = value.sender + } + // Cannot decode the sender from any arbitrary message. break }