call.gno
6.47 Kb · 200 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//
122// Only works in test builds: revive() requires MachineOptions.ReviveEnabled,
123// which gnovm sets only in gnovm/pkg/test, never on-chain. The recover()
124// fallback can't substitute for it either — a panic crossing the cross(rlm)
125// boundary skips ordinary ancestor defers on-chain. So a panicking receiver
126// reverts the whole recv transaction in production, matching Union's own
127// CosmWasm dispatch (not its EVM one, which isolates receiver reverts via a
128// low-level self call Gno has no on-chain equivalent for). The security/
129// intent/batch scenario filetests exercise the test-only path; on a real
130// chain they'd still revert atomically, just via the raw panic.
131func safeCallOnZkgm(_ int, rlm realm, receiver z.Zkgmable, env z.CallEnv) (err error, panicked bool) {
132 if canRevive() {
133 p := revive(func() {
134 err = receiver.OnZkgm(cross(rlm), env)
135 })
136
137 return err, p != nil
138 }
139
140 defer func() {
141 if recover() != nil {
142 err = nil
143 panicked = true
144 }
145 }()
146
147 err = receiver.OnZkgm(cross(rlm), env)
148
149 return err, false
150}
151
152// safeCallOnIntentZkgm runs the receiver's intent callback and reports a failure
153// as panicked=true, mirroring safeCallOnZkgm for the OnIntentZkgm path.
154func safeCallOnIntentZkgm(_ int, rlm realm, receiver z.Zkgmable, env z.IntentCallEnv) (err error, panicked bool) {
155 if canRevive() {
156 p := revive(func() {
157 err = receiver.OnIntentZkgm(cross(rlm), env)
158 })
159
160 return err, p != nil
161 }
162
163 defer func() {
164 if recover() != nil {
165 err = nil
166 panicked = true
167 }
168 }()
169
170 err = receiver.OnIntentZkgm(cross(rlm), env)
171
172 return err, false
173}
174
175// canRevive reports whether revive works in the current runtime. revive panics
176// rather than returning when the interpreter lacks support for it.
177func canRevive() (ok bool) {
178 ok = true
179
180 defer func() {
181 if recover() != nil {
182 ok = false
183 }
184 }()
185 revive(func() {})
186
187 return ok
188}
189
190// callErrAck encodes err as a failure acknowledgement for the call.
191func callErrAck(_ int, rlm realm, err error) types.RecvPacketResult {
192 ack, encErr := z.EncodeAck(z.Ack{Tag: tagAckFailure(), InnerAck: []byte(err.Error())})
193 if encErr != nil {
194 // EncodeAck on a 2-field schema is essentially infallible. A failure
195 // would emit a non-Tag-prefixed ack the counterparty can't decode.
196 panic(encErr)
197 }
198
199 return failureResult(ack)
200}