call.gno
5.96 Kb · 192 lines
1package ucs03_zkgm
2
3import (
4 types "gno.land/p/onbloc/ibc/union/types"
5
6 z "gno.land/p/onbloc/ibc/union/zkgm"
7 u256 "gno.land/p/onbloc/math/uint256"
8 core "gno.land/r/onbloc/ibc/union/core"
9)
10
11// verifyCall rejects eureka mode and requires the call sender to match the dispatch sender.
12// reference: https://github.com/unionlabs/union/blob/d91c5e94354e15801bd5f82dc658eae3b79f2dad/cosmwasm/app/ucs03-zkgm/src/contract.rs#L2997-L3019
13func (v *ucs03ZkgmV1) verifyCall(_ int, rlm realm, call z.Call) error {
14 if call.Eureka {
15 return makeError(errEurekaUnsupported)
16 }
17
18 if string(call.Sender) != rlm.Previous().Address().String() {
19 return makeError(errInvalidCallSender)
20 }
21
22 return nil
23}
24
25// executeCall dispatches an OP_CALL to the registered receiver's OnZkgm callback; failures and panics fail closed.
26// reference: https://github.com/unionlabs/union/blob/d91c5e94354e15801bd5f82dc658eae3b79f2dad/cosmwasm/app/ucs03-zkgm/src/contract.rs#L1500-L1683
27func (v *ucs03ZkgmV1) executeCall(_ int, rlm realm, packet types.Packet, relayer address, relayerMsg []byte, path *u256.Uint, call z.Call, intent bool) (types.RecvPacketResult, error) {
28 // TODO: when eureka is supported, fail it closed on intent (return
29 // ACK_ERR_ONLY_MAKER) like every other opcode instead of a failure ack.
30 if call.Eureka {
31 return callErrAck(0, rlm, makeError(errEurekaUnsupported)), nil
32 }
33
34 sender := types.CloneBytes(call.Sender)
35 calldata := types.CloneBytes(call.ContractCalldata)
36 receiverPath := string(call.ContractAddress)
37 receiver, ok := v.store.GetReceiver(receiverPath)
38
39 if !ok || receiver == nil {
40 return callErrAck(0, rlm, makeError(errReceiverNotRegistered, receiverPath)), nil
41 }
42
43 relayerStr := string(relayer)
44 proxyAccount := z.PredictCallProxyAccount(path, packet.DestinationChannelId, sender)
45
46 // Intent settlement dispatches OnIntentZkgm to the receiver (union execute_call
47 // intent arm), and is maker-only: any receiver failure reverts to ACK_ERR_ONLY_MAKER
48 // so the proven recv can settle instead.
49 if intent {
50 env := z.IntentCallEnv{
51 Caller: relayerStr,
52 ProxyAccount: proxyAccount,
53 Path: path,
54 SourceChannel: packet.SourceChannelId.String(),
55 DestinationChannel: packet.DestinationChannelId.String(),
56 Sender: sender,
57 Calldata: calldata,
58 MarketMaker: []byte(relayerStr),
59 MarketMakerMsg: types.CloneBytes(relayerMsg),
60 }
61 if err, panicked := safeCallOnIntentZkgm(0, rlm, receiver, env); panicked || err != nil {
62 return core.NewRecvPacketResult(types.PacketStatusSuccess, types.CloneBytes(z.ACK_ERR_ONLY_MAKER)), nil
63 }
64
65 return callSuccessAck()
66 }
67
68 env := z.CallEnv{
69 Caller: relayerStr,
70 ProxyAccount: proxyAccount,
71 Path: path,
72 SourceChannel: packet.SourceChannelId.String(),
73 DestinationChannel: packet.DestinationChannelId.String(),
74 Sender: sender,
75 Calldata: calldata,
76 Relayer: []byte(relayerStr),
77 RelayerMsg: types.CloneBytes(relayerMsg),
78 }
79
80 err, panicked := safeCallOnZkgm(0, rlm, receiver, env)
81 if panicked {
82 return core.NewRecvPacketResult(types.PacketStatusSuccess, types.CloneBytes(z.ACK_ERR_ONLY_MAKER)), nil
83 }
84
85 if err != nil {
86 return callErrAck(0, rlm, err), nil
87 }
88
89 return callSuccessAck()
90}
91
92// callSuccessAck builds the empty success acknowledgement a completed call writes.
93func callSuccessAck() (types.RecvPacketResult, error) {
94 ack, err := z.EncodeAck(z.Ack{Tag: tagAckSuccess(), InnerAck: []byte{}})
95 if err != nil {
96 return core.NewRecvPacketResult(types.PacketStatusUnknown, nil), err
97 }
98
99 return core.NewRecvPacketResult(types.PacketStatusSuccess, ack), nil
100}
101
102// acknowledgeCall is a no-op for standard calls; eureka mode is unsupported.
103func (v *ucs03ZkgmV1) acknowledgeCall(_ int, rlm realm, packet types.Packet, path *u256.Uint, call z.Call) error {
104 if call.Eureka {
105 return makeError(errEurekaUnsupported)
106 }
107
108 return nil
109}
110
111// timeoutCall is a no-op for standard calls; eureka mode is unsupported.
112func (v *ucs03ZkgmV1) timeoutCall(_ int, rlm realm, packet types.Packet, path *u256.Uint, call z.Call) error {
113 if call.Eureka {
114 return makeError(errEurekaUnsupported)
115 }
116
117 return nil
118}
119
120// safeCallOnZkgm runs the receiver and reports a failure as panicked=true.
121// revive is preferred since it also catches cross-realm aborts. The recover
122// fallback, used only when revive is unsupported, catches ordinary panics.
123func safeCallOnZkgm(_ int, rlm realm, receiver z.Zkgmable, env z.CallEnv) (err error, panicked bool) {
124 if canRevive() {
125 p := revive(func() {
126 err = receiver.OnZkgm(cross(rlm), env)
127 })
128
129 return err, p != nil
130 }
131
132 defer func() {
133 if recover() != nil {
134 err = nil
135 panicked = true
136 }
137 }()
138
139 err = receiver.OnZkgm(cross(rlm), env)
140
141 return err, false
142}
143
144// safeCallOnIntentZkgm runs the receiver's intent callback and reports a failure
145// as panicked=true, mirroring safeCallOnZkgm for the OnIntentZkgm path.
146func safeCallOnIntentZkgm(_ int, rlm realm, receiver z.Zkgmable, env z.IntentCallEnv) (err error, panicked bool) {
147 if canRevive() {
148 p := revive(func() {
149 err = receiver.OnIntentZkgm(cross(rlm), env)
150 })
151
152 return err, p != nil
153 }
154
155 defer func() {
156 if recover() != nil {
157 err = nil
158 panicked = true
159 }
160 }()
161
162 err = receiver.OnIntentZkgm(cross(rlm), env)
163
164 return err, false
165}
166
167// canRevive reports whether revive works in the current runtime. revive panics
168// rather than returning when the interpreter lacks support for it.
169func canRevive() (ok bool) {
170 ok = true
171
172 defer func() {
173 if recover() != nil {
174 ok = false
175 }
176 }()
177 revive(func() {})
178
179 return ok
180}
181
182// callErrAck encodes err as a failure acknowledgement for the call.
183func callErrAck(_ int, rlm realm, err error) types.RecvPacketResult {
184 ack, encErr := z.EncodeAck(z.Ack{Tag: tagAckFailure(), InnerAck: []byte(err.Error())})
185 if encErr != nil {
186 // EncodeAck on a 2-field schema is essentially infallible. A failure
187 // would emit a non-Tag-prefixed ack the counterparty can't decode.
188 panic(encErr)
189 }
190
191 return failureResult(ack)
192}