Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}