Skip to content

Commit

Permalink
Add cross-chain support to governance and authz actions (#1380)
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahSaso authored Oct 1, 2023
1 parent d34ba43 commit d7347af
Show file tree
Hide file tree
Showing 36 changed files with 773 additions and 558 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 @@ -844,6 +844,7 @@
"noProposalsYet": "No proposals to vote on yet.",
"noSubDaosYet": "No SubDAOs yet.",
"noSubmission": "No submission",
"noTokenSelected": "No token selected",
"noVestingPaymentsFound": "No vesting payments found.",
"noVote": "No",
"noWithVeto": "No with Veto",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
NestedActionsEditorOptions,
NestedActionsRenderer,
NestedActionsRendererProps,
useChain,
} from '@dao-dao/stateless'
import {
AddressInputProps,
Expand All @@ -17,9 +18,8 @@ import {
import { ActionComponent } from '@dao-dao/types/actions'
import { isValidBech32Address, makeValidateAddress } from '@dao-dao/utils'

import { useActionOptions } from '../../../react'

export type AuthzExecData = {
chainId: string
// Set common address to use for all actions.
address: string
// Once created, fill group adjacent messages by sender.
Expand All @@ -43,9 +43,7 @@ export const AuthzExecComponent: ActionComponent<AuthzExecOptions> = (
props
) => {
const { t } = useTranslation()
const {
chain: { bech32_prefix: bech32Prefix },
} = useActionOptions()
const { bech32_prefix: bech32Prefix } = useChain()
const { watch, register } = useFormContext<AuthzExecData>()
const {
fieldNamePrefix,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ guide](https://github.com/DA0-DA0/dao-dao-ui/wiki/Bulk-importing-actions).

```json
{
"chainId": "<CHAIN ID>",
"address": "<TARGET ACCOUNT ADDRESS>",
"_actionData": [
// ACTIONS
Expand Down
140 changes: 91 additions & 49 deletions packages/stateful/actions/core/authorizations/AuthzExec/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ import { useFormContext } from 'react-hook-form'
import { constSelector, useRecoilValueLoadable } from 'recoil'

import { MsgExec } from '@dao-dao/protobuf/codegen/cosmos/authz/v1beta1/tx'
import { LockWithKeyEmoji, useChain } from '@dao-dao/stateless'
import {
ChainPickerInput,
ChainProvider,
LockWithKeyEmoji,
useChain,
} from '@dao-dao/stateless'
import {
ActionComponent,
ActionContextType,
ActionKey,
ActionMaker,
CosmosMsgFor_Empty,
Expand All @@ -15,10 +21,12 @@ import {
} from '@dao-dao/types'
import {
cwMsgToProtobuf,
decodePolytoneExecuteMsg,
isDecodedStargateMsg,
isValidContractAddress,
isValidWalletAddress,
makeStargateMessage,
maybeMakePolytoneExecuteMessage,
objectMatchesStructure,
protobufToCwMsg,
} from '@dao-dao/utils'
Expand All @@ -32,6 +40,7 @@ import {
import { daoInfoSelector } from '../../../../recoil'
import {
WalletActionsProvider,
useActionOptions,
useActionsForMatching,
useLoadedActionsAndCategories,
} from '../../../react'
Expand All @@ -41,11 +50,6 @@ import {
AuthzExecComponent as StatelessAuthzExecComponent,
} from './Component'

const useDefaults: UseDefaults<AuthzExecData> = () => ({
address: '',
msgs: [],
})

type InnerOptions = Pick<AuthzExecOptions, 'msgPerSenderIndex'>

const InnerComponentLoading: ActionComponent<InnerOptions> = (props) => (
Expand Down Expand Up @@ -105,67 +109,99 @@ const InnerComponentWrapper: ActionComponent<
: constSelector(undefined)
)

return isContractAddress ? (
daoInfoLoadable.state === 'hasValue' ? (
<SuspenseLoader fallback={<InnerComponentLoading {...props} />}>
<DaoProviders info={daoInfoLoadable.contents!}>
<InnerComponent {...props} />
</DaoProviders>
</SuspenseLoader>
) : (
<InnerComponentLoading {...props} />
)
) : isWalletAddress ? (
return isContractAddress &&
daoInfoLoadable.state === 'hasValue' &&
daoInfoLoadable.contents ? (
<SuspenseLoader fallback={<InnerComponentLoading {...props} />}>
<DaoProviders info={daoInfoLoadable.contents}>
<InnerComponent {...props} />
</DaoProviders>
</SuspenseLoader>
) : (isContractAddress &&
(daoInfoLoadable.state === 'hasError' || !daoInfoLoadable.contents)) ||
isWalletAddress ? (
<WalletActionsProvider address={address}>
<InnerComponent {...props} />
</WalletActionsProvider>
) : (
<InnerComponent {...props} />
<InnerComponentLoading {...props} />
)
}

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

// Load DAO info for chosen DAO.
const { 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')

// When creating, just show one form for the chosen address. When not
// creating, render a form for each sender message group since the component
// needs to be wrapped in the providers for that sender.
return props.isCreating ? (
<InnerComponentWrapper
{...props}
options={{
address,
}}
/>
) : (
return (
<>
{msgsPerSender.map(({ sender }, index) => (
<InnerComponentWrapper
key={index}
{...props}
options={{
address: sender,
// Set so the component knows which sender message group to render.
msgPerSenderIndex: index,
}}
{context.type === ActionContextType.Dao && (
<ChainPickerInput
className="mb-4"
disabled={!props.isCreating}
fieldName={props.fieldNamePrefix + 'chainId'}
/>
))}
)}

<ChainProvider chainId={chainId}>
{props.isCreating ? (
<InnerComponentWrapper
{...props}
options={{
address,
}}
/>
) : (
<>
{msgsPerSender.map(({ sender }, index) => (
<InnerComponentWrapper
key={index}
{...props}
options={{
address: sender,
// Set so the component knows which sender message group to render.
msgPerSenderIndex: index,
}}
/>
))}
</>
)}
</ChainProvider>
</>
)
}

export const makeAuthzExecAction: ActionMaker<AuthzExecData> = ({
t,
address: grantee,
chain: { chain_id: currentChainId },
}) => {
const useDefaults: UseDefaults<AuthzExecData> = () => ({
chainId: currentChainId,
address: '',
msgs: [],
})

const useDecodedCosmosMsg: UseDecodedCosmosMsg<AuthzExecData> = (
msg: Record<string, any>
) =>
useMemo(() => {
) => {
let chainId = currentChainId
const decodedPolytone = decodePolytoneExecuteMsg(chainId, msg)
if (decodedPolytone.match) {
chainId = decodedPolytone.chainId
msg = decodedPolytone.msg
}

return useMemo(() => {
if (
!isDecodedStargateMsg(msg) ||
msg.stargate.typeUrl !== MsgExec.typeUrl ||
Expand Down Expand Up @@ -204,6 +240,7 @@ export const makeAuthzExecAction: ActionMaker<AuthzExecData> = ({
return {
match: true,
data: {
chainId,
// Technically each message could have a different address. While we
// don't support that on creation, we can still detect and render them
// correctly in the component.
Expand All @@ -212,20 +249,25 @@ export const makeAuthzExecAction: ActionMaker<AuthzExecData> = ({
_msgs: msgsPerSender,
},
}
}, [msg])
}, [chainId, msg])
}

const useTransformToCosmos: UseTransformToCosmos<AuthzExecData> = () =>
useCallback(
({ address, msgs }) =>
makeStargateMessage({
stargate: {
typeUrl: MsgExec.typeUrl,
value: {
grantee,
msgs: msgs.map((msg) => cwMsgToProtobuf(msg, address)),
} as MsgExec,
},
}),
({ chainId, address, msgs }) =>
maybeMakePolytoneExecuteMessage(
currentChainId,
chainId,
makeStargateMessage({
stargate: {
typeUrl: MsgExec.typeUrl,
value: {
grantee,
msgs: msgs.map((msg) => cwMsgToProtobuf(msg, address)),
} as MsgExec,
},
})
),
[]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default {
component: AuthzGrantRevokeComponent,
decorators: [
makeReactHookFormDecorator<AuthzGrantRevokeData>({
chainId: CHAIN_ID,
mode: 'grant',
authorizationTypeUrl: GenericAuthorization.typeUrl,
customTypeUrl: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ import {
SelectInput,
TextInput,
WarningCard,
useChain,
} from '@dao-dao/stateless'
import {
AddressInputProps,
GenericTokenBalance,
LoadingData,
TokenType,
} from '@dao-dao/types'
import { ActionComponent } from '@dao-dao/types/actions'
import {
Expand All @@ -47,7 +47,6 @@ import {
validateRequired,
} from '@dao-dao/utils'

import { useActionOptions } from '../../../react'
import {
ACTION_TYPES,
AUTHORIZATION_TYPES,
Expand All @@ -65,9 +64,7 @@ export const AuthzGrantRevokeComponent: ActionComponent<
AuthzGrantRevokeOptions
> = (props) => {
const { t } = useTranslation()
const {
chain: { chain_id: chainId, bech32_prefix: bech32Prefix },
} = useActionOptions()
const { chain_id: chainId, bech32_prefix: bech32Prefix } = useChain()
const {
fieldNamePrefix,
errors,
Expand Down Expand Up @@ -239,14 +236,7 @@ export const AuthzGrantRevokeComponent: ActionComponent<
{...({
...props,
options: {
nativeBalances: balances.loading
? { loading: true }
: {
loading: false,
data: balances.data.filter(
({ token }) => token.type === TokenType.Native
),
},
nativeBalances: balances,
},
onRemove: isCreating ? () => removeCoin(index) : undefined,
} as NativeCoinSelectorProps)}
Expand Down Expand Up @@ -456,15 +446,7 @@ export const AuthzGrantRevokeComponent: ActionComponent<
{...({
...props,
options: {
nativeBalances: balances.loading
? { loading: true }
: {
loading: false,
data: balances.data.filter(
({ token }) =>
token.type === TokenType.Native
),
},
nativeBalances: balances,
},
onRemove: isCreating
? () => removeCoin(index)
Expand Down
Loading

2 comments on commit d7347af

@vercel
Copy link

@vercel vercel bot commented on d7347af Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on d7347af Oct 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.