batch.gno
4.50 Kb · 131 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)
9
10// verifyBatch verifies each allowed child instruction against the shared funds budget.
11// reference: https://github.com/unionlabs/union/blob/d91c5e94354e15801bd5f82dc658eae3b79f2dad/cosmwasm/app/ucs03-zkgm/src/contract.rs#L2941-L2967
12func (v *ucs03ZkgmV1) verifyBatch(_ int, rlm realm, funds *funds, channelId types.ChannelId, path *u256.Uint, batch z.Batch) error {
13 for _, instruction := range batch.Instructions {
14 if !isAllowedBatchInstruction(instruction.Opcode) {
15 return makeError(errInvalidBatchInstruction)
16 }
17
18 if err := v.verifyInternal(0, rlm, funds, channelId, path, instruction); err != nil {
19 return err
20 }
21 }
22
23 return nil
24}
25
26// executeBatch runs each child in order, aborting the whole batch on an only-maker child.
27// reference: https://github.com/unionlabs/union/blob/d91c5e94354e15801bd5f82dc658eae3b79f2dad/cosmwasm/app/ucs03-zkgm/src/contract.rs#L1686-L1726
28func (v *ucs03ZkgmV1) executeBatch(_ int, rlm realm, packet types.Packet, relayer address, relayerMsg []byte, salt [32]byte, path *u256.Uint, batch z.Batch, intent bool) ([]byte, error) {
29 acks := make([][]byte, len(batch.Instructions))
30
31 // NOTE: batch.Instructions remains uncapped until the Union reference defines
32 // a maximum. Each child runs dispatchExecutePacket, including ABI decode and
33 // keccak work, so execution cost scales with child count.
34 for i, instruction := range batch.Instructions {
35 if !isAllowedBatchInstruction(instruction.Opcode) {
36 panic(execFatal{makeError(errInvalidBatchInstruction).Error()})
37 }
38
39 childSalt := z.DeriveBatchSalt(u256.NewUint(uint64(i)), salt)
40
41 childRes, err := v.dispatchExecutePacket(0, rlm, packet, relayer, relayerMsg, childSalt, path, instruction, intent)
42 if err != nil {
43 panic(execFatal{err.Error()})
44 }
45
46 childAck := childRes.Acknowledgement
47 if len(childAck) == 0 {
48 // empty ack is unrecoverable once earlier children have committed.
49 panic(execFatal{"batch child returned empty ack"})
50 }
51 // A single only-maker child aborts the entire batch: return the sentinel up
52 // to the recv boundary (OnRecvPacket / OnIntentRecvPacket), which panics on
53 // it and reverts the whole recv tx — so already-processed children's
54 // mutations do NOT stand. Mirrors union's reply handler returning
55 // Err(OnlyMaker) on an only-maker batch ack (contract.rs L2528-2530).
56 if isOnlyMakerAck(childAck) {
57 return types.CloneBytes(z.ACK_ERR_ONLY_MAKER), nil
58 }
59
60 acks[i] = childAck
61 }
62
63 batchAckBytes, err := z.EncodeBatchAck(z.BatchAck{Acknowledgements: acks})
64 if err != nil {
65 panic(execFatal{err.Error()})
66 }
67
68 ack, err := z.EncodeAck(z.Ack{Tag: tagAckSuccess(), InnerAck: batchAckBytes})
69 if err != nil {
70 panic(execFatal{err.Error()})
71 }
72
73 return ack, nil
74}
75
76// acknowledgeBatch fans the outer ack out to each child instruction.
77func (v *ucs03ZkgmV1) acknowledgeBatch(_ int, rlm realm, packet types.Packet, relayer address, salt [32]byte, path *u256.Uint, batch z.Batch, successful bool, ack []byte) error {
78 var batchAck z.BatchAck
79
80 if successful {
81 decoded, err := z.DecodeBatchAck(ack)
82 if err != nil {
83 return err
84 }
85
86 batchAck = decoded
87 if len(batchAck.Acknowledgements) != len(batch.Instructions) {
88 return makeError(errBatchAckCountMismatch)
89 }
90 }
91
92 for i, instruction := range batch.Instructions {
93 var childAck []byte
94
95 childSuccessful := successful
96
97 if successful {
98 decodedChildAck, err := z.DecodeAck(batchAck.Acknowledgements[i])
99 if err != nil {
100 return err
101 }
102
103 childSuccessful = decodedChildAck.Tag != nil && decodedChildAck.Tag.Eq(tagAckSuccess())
104 childAck = decodedChildAck.InnerAck
105 }
106
107 childSalt := z.DeriveBatchSalt(u256.NewUint(uint64(i)), salt)
108 if err := v.acknowledgeInternal(0, rlm, packet, relayer, childSalt, path, instruction, childSuccessful, childAck); err != nil {
109 return err
110 }
111 }
112
113 return nil
114}
115
116// timeoutBatch fans the timeout out to each child instruction.
117func (v *ucs03ZkgmV1) timeoutBatch(_ int, rlm realm, packet types.Packet, salt [32]byte, path *u256.Uint, batch z.Batch) error {
118 for i, instruction := range batch.Instructions {
119 childSalt := z.DeriveBatchSalt(u256.NewUint(uint64(i)), salt)
120 if err := v.timeoutInternal(0, rlm, packet, childSalt, path, instruction); err != nil {
121 return err
122 }
123 }
124
125 return nil
126}
127
128// isAllowedBatchInstruction reports whether opcode may appear in a batch.
129func isAllowedBatchInstruction(opcode uint8) bool {
130 return opcode == z.OP_CALL || opcode == z.OP_TOKEN_ORDER
131}