Skip to content

Commit

Permalink
fix authz exec action match error when unable to decode sender from u…
Browse files Browse the repository at this point in the history
…nknown stargate message
  • Loading branch information
NoahSaso committed Jan 7, 2025
1 parent 953a730 commit 76bbaa8
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 74 deletions.
1 change: 1 addition & 0 deletions packages/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
50 changes: 28 additions & 22 deletions packages/state/utils/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
72 changes: 30 additions & 42 deletions packages/stateful/actions/core/actions/AuthzExec/Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -35,7 +34,6 @@ export type AuthzExecOptions = {
// to render.
msgPerSenderIndex?: number

AddressInput: ComponentType<AddressInputProps<AuthzExecData>>
EntityDisplay: ComponentType<StatefulEntityDisplayProps>
} & NestedActionsEditorOptions &
Omit<NestedActionsRendererProps, 'msgsFieldName'>
Expand All @@ -45,59 +43,49 @@ export const AuthzExecComponent: ActionComponent<AuthzExecOptions> = (
) => {
const { t } = useTranslation()
const { bech32Prefix } = useChain()
const { watch, register } = useFormContext<AuthzExecData>()
const { watch } = useFormContext<AuthzExecData>()
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 ? (
<>
<InputLabel className="-mb-3" name={t('title.account')} />
<InputLabel className="-mb-2" name={t('title.actions')} />

<AddressInput
autoFocus
error={errors?.address}
fieldName={(fieldNamePrefix + 'address') as 'address'}
register={register}
validation={[makeValidateAddress(bech32Prefix)]}
/>
<NestedActionsEditor {...props} />
</>
)}

{(isValidBech32Address(address, bech32Prefix) || !isCreating) && (
<>
{isCreating ? (
<>
<InputLabel className="-mb-2" name={t('title.actions')} />

<NestedActionsEditor {...props} />
</>
) : (
<div className="flex flex-col gap-4">
<InputLabel className="-mb-2" name={t('title.account')} />
<EntityDisplay
address={msgsPerSender[msgPerSenderIndex!].sender}
/>
) : (
!isCreating && (
<div className="flex flex-col gap-4">
<InputLabel className="-mb-2" name={t('title.account')} />
{sender ? (
<EntityDisplay address={sender} />
) : (
<p className="body-text italic">
{t('info.failedToDecodeAddressUnrecognizedMessage')}
</p>
)}

<InputLabel className="-mb-2" name={t('title.actions')} />
<NestedActionsRenderer
{...options}
msgsFieldName={
fieldNamePrefix + `_msgs.${msgPerSenderIndex!}.msgs`
}
/>
</div>
)}
</>
<InputLabel className="-mb-2" name={t('title.actions')} />
<NestedActionsRenderer
{...options}
msgsFieldName={
fieldNamePrefix + `_msgs.${msgPerSenderIndex!}.msgs`
}
/>
</div>
)
)}
</>
)
Expand Down
54 changes: 46 additions & 8 deletions packages/stateful/actions/core/actions/AuthzExec/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
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 {
ActionBase,
ActionsContext,
ChainProvider,
DaoSupportedChainPickerInput,
InputLabel,
LockWithKeyEmoji,
useActionOptions,
useChain,
Expand All @@ -29,6 +31,8 @@ import {
getChainForChainId,
isDecodedStargateMsg,
isValidBech32Address,
makeEmptyUnifiedProfile,
makeValidateAddress,
maybeMakePolytoneExecuteMessages,
} from '@dao-dao/utils'

Expand All @@ -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,
Expand All @@ -65,7 +70,6 @@ const InnerComponentLoading: ActionComponent<InnerOptions> = (props) => (
options={{
msgPerSenderIndex: props.options.msgPerSenderIndex,
encodeContext: useActionEncodeContext(),
AddressInput,
EntityDisplay,
SuspenseLoader,
}}
Expand All @@ -79,7 +83,6 @@ const InnerComponent: ActionComponent<InnerOptions> = (props) => (
options={{
msgPerSenderIndex: props.options.msgPerSenderIndex,
encodeContext: useActionEncodeContext(),
AddressInput,
EntityDisplay,
SuspenseLoader,
}}
Expand All @@ -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,
Expand All @@ -107,9 +113,9 @@ const InnerComponentWrapper: ActionComponent<
false
)

return isDao.loading || isDao.updating ? (
return (validAddress || isCreating) && (isDao.loading || isDao.updating) ? (
<InnerComponentLoading {...props} />
) : isDao.data ? (
) : !isDao.loading && !isDao.updating && isDao.data ? (
<DaoProviders
key={
// Make sure to re-render (reset state inside the contexts) when the
Expand All @@ -122,23 +128,40 @@ const InnerComponentWrapper: ActionComponent<
>
<InnerComponent {...props} />
</DaoProviders>
) : (
) : validAddress || isCreating ? (
<WalletActionsProvider address={address}>
<InnerComponent {...props} />
</WalletActionsProvider>
) : (
// 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.
<BaseActionsProvider
actionContext={{
type: ActionContextType.Wallet,
profile: makeEmptyUnifiedProfile(chainId, address),
accounts: [],
}}
address={address}
>
<InnerComponent {...props} />
</BaseActionsProvider>
)
}

const Component: ActionComponent = (props) => {
const { t } = useTranslation()
const { context } = useActionOptions()

// Load DAO info for chosen DAO.
const { watch } = useFormContext<AuthzExecData>()
const { register, watch } = useFormContext<AuthzExecData>()
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
Expand All @@ -155,6 +178,21 @@ const Component: ActionComponent = (props) => {

{/* Re-render when chain changes so hooks and state reset. */}
<ChainProvider key={chainId} chainId={chainId}>
{/* 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 && (
<>
<InputLabel className="-mb-3" name={t('title.account')} />

<AddressInput
autoFocus
error={props.errors?.address}
fieldName={(props.fieldNamePrefix + 'address') as 'address'}
register={register}
validation={[makeValidateAddress(bech32Prefix)]}
/>
</>
)}

{props.isCreating ? (
<InnerComponentWrapper
{...props}
Expand Down
7 changes: 5 additions & 2 deletions packages/stateful/actions/providers/wallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ export const WalletActionsProvider = ({
address: overrideAddress,
children,
}: WalletActionsProviderProps) => {
const { address: connectedAddress, chain } = useWallet()
const {
address: connectedAddress,
chain: { chainId },
} = useWallet()

const address =
overrideAddress === undefined ? connectedAddress : overrideAddress
Expand All @@ -26,7 +29,7 @@ export const WalletActionsProvider = ({
const accounts = useQueryLoadingDataWithError(
address
? accountQueries.list(queryClient, {
chainId: chain.chainId,
chainId,
address,
})
: undefined
Expand Down
5 changes: 5 additions & 0 deletions packages/types/protobuf/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 76bbaa8

Please sign in to comment.