dispatch.gno
8.29 Kb · 283 lines
1package ucs03_zkgm
2
3import (
4 "bytes"
5
6 types "gno.land/p/onbloc/ibc/union/types"
7
8 "gno.land/p/nt/ufmt/v0"
9 z "gno.land/p/onbloc/ibc/union/zkgm"
10 u256 "gno.land/p/onbloc/math/uint256"
11 core "gno.land/r/onbloc/ibc/union/core"
12)
13
14// verifyInternal routes an instruction to its opcode-specific verifier (the send-side validation entry point).
15// reference: https://github.com/unionlabs/union/blob/d91c5e94354e15801bd5f82dc658eae3b79f2dad/cosmwasm/app/ucs03-zkgm/src/contract.rs#L2742-L2796
16func (v *ucs03ZkgmV1) verifyInternal(_ int, rlm realm, funds *funds, channelId types.ChannelId, path *u256.Uint, instruction z.Instruction) error {
17 switch instruction.Opcode {
18 case z.OP_TOKEN_ORDER:
19 order, err := decodeOrderInstruction(instruction)
20 if err != nil {
21 return err
22 }
23
24 return v.verifyTokenOrderV2(0, rlm, funds, channelId, path, order)
25 case z.OP_BATCH:
26 batch, err := decodeBatchInstruction(instruction)
27 if err != nil {
28 return err
29 }
30
31 return v.verifyBatch(0, rlm, funds, channelId, path, batch)
32 case z.OP_FORWARD:
33 forward, err := decodeForwardInstruction(instruction)
34 if err != nil {
35 return err
36 }
37
38 return v.verifyForward(0, rlm, funds, channelId, forward)
39 case z.OP_CALL:
40 call, err := decodeCallInstruction(instruction)
41 if err != nil {
42 return err
43 }
44
45 return v.verifyCall(0, rlm, call)
46 default:
47 return makeError(errUnsupportedOpcode)
48 }
49}
50
51// execFatal is a sentinel panic value.
52//
53// Unlike ordinary panics, values of this type are re-panicked by the recover
54// handler so the VM can observe the failure after transaction rollback.
55type execFatal struct{ msg string }
56
57func (e execFatal) Error() string { return e.msg }
58
59// dispatchExecutePacket routes a received instruction to its opcode handler, turning non-fatal errors into a failure ack.
60func (v *ucs03ZkgmV1) dispatchExecutePacket(
61 _ int, rlm realm,
62 packet types.Packet,
63 relayer address,
64 relayerMsg []byte,
65 salt [32]byte,
66 path *u256.Uint,
67 instruction z.Instruction,
68 intent bool,
69) (result types.RecvPacketResult, err error) {
70 defer func() {
71 if r := recover(); r != nil {
72 if _, ok := r.(execFatal); ok {
73 panic(r)
74 }
75
76 result = failureResult(universalErrorAck())
77 err = nil
78 }
79 }()
80
81 switch instruction.Opcode {
82 case z.OP_TOKEN_ORDER:
83 order, err := decodeOrderInstruction(instruction)
84 if err != nil {
85 return core.NewRecvPacketResult(types.PacketStatusUnknown, nil), err
86 }
87
88 return v.executeTokenOrderV2(0, rlm, packet, relayer, relayerMsg, salt, path, order, intent)
89 case z.OP_BATCH:
90 batch, err := decodeBatchInstruction(instruction)
91 if err != nil {
92 return core.NewRecvPacketResult(types.PacketStatusUnknown, nil), err
93 }
94
95 ack, err := v.executeBatch(0, rlm, packet, relayer, relayerMsg, salt, path, batch, intent)
96 if err != nil {
97 return core.NewRecvPacketResult(types.PacketStatusUnknown, nil), err
98 }
99
100 return core.NewRecvPacketResult(types.PacketStatusSuccess, ack), nil
101 case z.OP_CALL:
102 call, err := decodeCallInstruction(instruction)
103 if err != nil {
104 return core.NewRecvPacketResult(types.PacketStatusUnknown, nil), err
105 }
106
107 return v.executeCall(0, rlm, packet, relayer, relayerMsg, path, call, intent)
108 case z.OP_FORWARD:
109 forward, err := decodeForwardInstruction(instruction)
110 if err != nil {
111 return core.NewRecvPacketResult(types.PacketStatusUnknown, nil), err
112 }
113
114 return v.executeForward(0, rlm, packet, salt, path, forward, intent)
115 default:
116 return core.NewRecvPacketResult(types.PacketStatusUnknown, nil), makeError(errUnsupportedOpcode)
117 }
118}
119
120// acknowledgeInternal routes an acknowledgement to its opcode handler.
121func (v *ucs03ZkgmV1) acknowledgeInternal(_ int, rlm realm, packet types.Packet, relayer address, salt [32]byte, path *u256.Uint, instruction z.Instruction, successful bool, ack []byte) error {
122 switch instruction.Opcode {
123 case z.OP_TOKEN_ORDER:
124 order, err := decodeOrderInstruction(instruction)
125 if err != nil {
126 return err
127 }
128
129 var orderAck z.TokenOrderAck
130
131 hasOrderAck := false
132
133 if successful {
134 decoded, err := z.DecodeTokenOrderAck(ack)
135 if err != nil {
136 return err
137 }
138
139 orderAck = decoded
140 hasOrderAck = true
141 }
142
143 return v.acknowledgeTokenOrderV2(0, rlm, packet, relayer, salt, path, order, orderAck, hasOrderAck)
144 case z.OP_BATCH:
145 batch, err := decodeBatchInstruction(instruction)
146 if err != nil {
147 return err
148 }
149
150 return v.acknowledgeBatch(0, rlm, packet, relayer, salt, path, batch, successful, ack)
151 case z.OP_CALL:
152 call, err := decodeCallInstruction(instruction)
153 if err != nil {
154 return err
155 }
156
157 return v.acknowledgeCall(0, rlm, packet, path, call)
158 default:
159 return makeError(errUnsupportedOpcode)
160 }
161}
162
163// timeoutInternal routes a timeout to its opcode handler.
164func (v *ucs03ZkgmV1) timeoutInternal(_ int, rlm realm, packet types.Packet, salt [32]byte, path *u256.Uint, instruction z.Instruction) error {
165 switch instruction.Opcode {
166 case z.OP_TOKEN_ORDER:
167 order, err := decodeOrderInstruction(instruction)
168 if err != nil {
169 return err
170 }
171
172 return v.refundV2(0, rlm, path, packet.SourceChannelId, order)
173 case z.OP_BATCH:
174 batch, err := decodeBatchInstruction(instruction)
175 if err != nil {
176 return err
177 }
178
179 return v.timeoutBatch(0, rlm, packet, salt, path, batch)
180 case z.OP_CALL:
181 call, err := decodeCallInstruction(instruction)
182 if err != nil {
183 return err
184 }
185
186 return v.timeoutCall(0, rlm, packet, path, call)
187 default:
188 return makeError(errUnsupportedOpcode)
189 }
190}
191
192// decodeOrderInstruction decodes a v2 token order, rejecting unsupported instruction versions.
193func decodeOrderInstruction(instruction z.Instruction) (z.TokenOrderV2, error) {
194 if instruction.Version != z.INSTR_VERSION_2 {
195 return z.TokenOrderV2{}, makeError(errUnsupportedTokenOrderVersion)
196 }
197
198 return z.DecodeTokenOrderV2(instruction.Operand)
199}
200
201// decodeCallInstruction decodes a call, rejecting unsupported instruction versions.
202func decodeCallInstruction(instruction z.Instruction) (z.Call, error) {
203 if instruction.Version != z.INSTR_VERSION_0 {
204 return z.Call{}, makeError(errUnsupportedCallVersion)
205 }
206
207 return z.DecodeCall(instruction.Operand)
208}
209
210// decodeBatchInstruction decodes a batch, rejecting unsupported instruction versions.
211func decodeBatchInstruction(instruction z.Instruction) (z.Batch, error) {
212 if instruction.Version != z.INSTR_VERSION_0 {
213 return z.Batch{}, makeError(errUnsupportedBatchVersion)
214 }
215
216 return z.DecodeBatch(instruction.Operand)
217}
218
219// decodeForwardInstruction decodes a forward, rejecting unsupported instruction versions.
220func decodeForwardInstruction(instruction z.Instruction) (z.Forward, error) {
221 if instruction.Version != z.INSTR_VERSION_0 {
222 return z.Forward{}, makeError(errUnsupportedForwardVersion)
223 }
224
225 return z.DecodeForward(instruction.Operand)
226}
227
228// mustEncodeUniversalErrorAck returns the ABI-encoded failure acknowledgement
229// `z.Ack{Tag: TAG_ACK_FAILURE, InnerAck: empty}` that zkgm-internal recv failures
230// emit. The empty inner ack matches union's `Ack{tag: TAG_ACK_FAILURE, inner_ack:
231// Default::default()}`; the encoded Ack itself is non-empty, satisfying core's
232// non-empty acknowledgement requirement.
233func mustEncodeUniversalErrorAck() []byte {
234 bz, err := z.EncodeAck(z.Ack{
235 Tag: u256.NewUint(0),
236 InnerAck: []byte{},
237 })
238 if err != nil {
239 panic(err)
240 }
241
242 return bz
243}
244
245// failureResult wraps an ack as a failed recv result.
246func failureResult(ack []byte) types.RecvPacketResult {
247 return core.NewRecvPacketResult(types.PacketStatusFailure, ack)
248}
249
250// universalErrorAck returns the cached sentinel error acknowledgement.
251func universalErrorAck() []byte {
252 return mustEncodeUniversalErrorAck()
253}
254
255// tokenOrderDestinationChannel rejects the zero-channel sentinel so the
256// derived destChan is safe to feed into UpdateChannelPath / PopChannelFromPath
257// without colliding with the "no channel" slot. This mirrors the TokenOrder
258// path helper's channel-zero rejection.
259func tokenOrderDestinationChannel(channelId types.ChannelId) (uint32, error) {
260 raw := uint32(channelId)
261 if raw == 0 {
262 return 0, makeError(errInvalidTokenOrderDestChannel)
263 }
264
265 return raw, nil
266}
267
268// isOnlyMakerAck reports whether ack is the only-maker sentinel.
269func isOnlyMakerAck(ack []byte) bool {
270 return bytes.Equal(ack, z.ACK_ERR_ONLY_MAKER)
271}
272
273// extractPanicMessage renders a recovered panic value as a string.
274func extractPanicMessage(r any) string {
275 switch v := r.(type) {
276 case string:
277 return v
278 case error:
279 return v.Error()
280 default:
281 return ufmt.Sprintf("%v", v)
282 }
283}