-
Notifications
You must be signed in to change notification settings - Fork 22
/
useSimulateCosmosMsgs.ts
170 lines (150 loc) · 5.17 KB
/
useSimulateCosmosMsgs.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { useCallback } from 'react'
import { constSelector, useRecoilValue } from 'recoil'
import { DaoDaoCoreSelectors } from '@dao-dao/state/recoil'
import { useChain } from '@dao-dao/stateless'
import {
UnifiedCosmosMsg,
cwMsgToEncodeObject,
getTypesRegistry,
} from '@dao-dao/types'
import { cosmos } from '@dao-dao/types/protobuf'
import { SignMode } from '@dao-dao/types/protobuf/codegen/cosmos/tx/signing/v1beta1/signing'
import { SimulateRequest } from '@dao-dao/types/protobuf/codegen/cosmos/tx/v1beta1/service'
import {
AuthInfo,
Fee,
Tx,
TxBody,
} from '@dao-dao/types/protobuf/codegen/cosmos/tx/v1beta1/tx'
import { Any } from '@dao-dao/types/protobuf/codegen/google/protobuf/any'
import {
cosmosProtoRpcClientRouter,
decodeMessages,
decodePolytoneExecuteMsg,
isValidBech32Address,
} from '@dao-dao/utils'
// TODO(secret): prevent simulating if it contains compute/wasm messages
// Simulate executing Cosmos messages on-chain. We can't just use the simulate
// function on SigningCosmWasmClient or SigningStargateClient because they
// include signer info from the wallet. We may want to simulate these messages
// coming from a DAO, so we don't want wallet signing info included. Those
// simulate functions internally call this function:
// https://github.com/cosmos/cosmjs/blob/2c3b27eeb3622a6108086267ba6faf3984251be3/packages/stargate/src/modules/tx/queries.ts#L47
// We can copy this simulate function and leave out the wallet-specific fields
// (i.e. sequence) and unnecessary fields (i.e. publicKey, memo) to simulate
// these messages from a DAO address.
export const useSimulateCosmosMsgs = (senderAddress: string) => {
const { chainId, bech32Prefix } = useChain()
const polytoneProxies = useRecoilValue(
isValidBech32Address(senderAddress, bech32Prefix)
? DaoDaoCoreSelectors.polytoneProxiesSelector({
chainId,
contractAddress: senderAddress,
})
: constSelector(undefined)
)
const simulate = useCallback(
async (msgs: UnifiedCosmosMsg[]): Promise<void> => {
// If no messages, nothing to simulate.
if (msgs.length === 0) {
return
}
await doSimulation(chainId, msgs, senderAddress)
// Also simulate polytone messages on receiving chains.
const decodedPolytoneMessages = decodeMessages(msgs).flatMap((msg) => {
const decoded = decodePolytoneExecuteMsg(chainId, msg, 'any')
return decoded.match && decoded.cosmosMsg ? decoded : []
})
if (decodedPolytoneMessages.length) {
const polytoneGroupedByChainId = decodedPolytoneMessages.reduce(
(acc, decoded) => ({
...acc,
[decoded.chainId]: [
...(acc[decoded.chainId] ?? []),
...decoded.cosmosMsgs,
],
}),
{} as Record<string, UnifiedCosmosMsg[] | undefined>
)
await Promise.all(
Object.entries(polytoneGroupedByChainId).map(
async ([chainId, msgs]) => {
const polytoneProxy = (polytoneProxies || {})[chainId]
if (!polytoneProxy || !msgs?.length) {
return
}
await doSimulation(chainId, msgs, polytoneProxy)
}
)
)
}
// Unfortunately we can't simulate messages from an ICA for weird
// cosmos-sdk reasons... YOLO
},
[chainId, polytoneProxies, senderAddress]
)
return simulate
}
const doSimulation = async (
chainId: string,
msgs: UnifiedCosmosMsg[],
senderAddress: string
) => {
const cosmosRpcClient = await cosmosProtoRpcClientRouter.connect(chainId)
const typesRegistry = getTypesRegistry()
const encodedMsgs = msgs.map((msg) => {
const encoded = cwMsgToEncodeObject(chainId, msg, senderAddress)
return typesRegistry.encodeAsAny(encoded)
})
// Simulate messages together.
try {
await simulateMessages(cosmosRpcClient, encodedMsgs)
} catch (err) {
// Simulate messages separately and log any errors, but don't throw them.
// This helps debug which message is causing an error if they all fail
// together. But we only care about the result of simulating all messages
// since they may depend on each other.
await encodedMsgs.reduce(async (p, encoded) => {
await p
console.log('Simulating:', encoded)
try {
await simulateMessages(cosmosRpcClient, [encoded])
} catch (err) {
console.error(err)
}
}, Promise.resolve())
// Rethrow original error.
throw err
}
}
const simulateMessages = async (
cosmosRpcClient: Awaited<
ReturnType<typeof cosmos.ClientFactory.createRPCQueryClient>
>['cosmos'],
messages: Any[]
) => {
const tx = Tx.fromPartial({
authInfo: AuthInfo.fromPartial({
fee: Fee.fromPartial({}),
signerInfos: [
// @ts-ignore
{
modeInfo: {
single: {
mode: SignMode.SIGN_MODE_DIRECT,
},
},
},
],
}),
body: TxBody.fromPartial({
messages,
memo: '',
}),
signatures: [new Uint8Array()],
})
const request = SimulateRequest.fromPartial({
txBytes: Tx.encode(tx).finish(),
})
await cosmosRpcClient.tx.v1beta1.simulate(request)
}