@stellar-expert/tx-meta-effects-parser 7.0.0-rc.9 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,308 +1,324 @@
1
- const {StrKey, encodeMuxedAccount, encodeMuxedAccountToAddress} = require('@stellar/stellar-base')
2
- const effectTypes = require('../effect-types')
3
- const {xdrParseScVal, xdrParseAsset, isContractAddress} = require('../parser/tx-xdr-parser-utils')
4
- const {mapSacContract} = require('./sac-contract-mapper')
5
-
6
- const EVENT_TYPES = {
7
- SYSTEM: 0,
8
- CONTRACT: 1,
9
- DIAGNOSTIC: 2
10
- }
11
-
12
- class EventsAnalyzer {
13
- /**
14
- * @param {EffectsAnalyzer} effectsAnalyzer
15
- */
16
- constructor(effectsAnalyzer) {
17
- this.effectsAnalyzer = effectsAnalyzer
18
- this.callStack = []
19
- }
20
-
21
- /**
22
- * @type {[]}
23
- * @private
24
- */
25
- callStack
26
-
27
- analyze() {
28
- this.analyzeDiagnosticEvents()
29
- this.analyzeEvents()
30
- }
31
-
32
- /**
33
- * @private
34
- */
35
- analyzeEvents() {
36
- const {events} = this.effectsAnalyzer
37
- if (!events)
38
- return
39
- //contract-generated events
40
- for (const evt of events) {
41
- const body = evt.body().value()
42
- const rawTopics = body.topics()
43
- const topics = rawTopics.map(xdrParseScVal)
44
- if (topics[0] === 'DATA' && topics[1] === 'set')
45
- continue //skip data entries modifications
46
- const rawData = body.data()
47
- //add event to the pipeline
48
- this.effectsAnalyzer.addEffect({
49
- type: effectTypes.contractEvent,
50
- contract: StrKey.encodeContract(evt.contractId()),
51
- topics,
52
- rawTopics: rawTopics.map(v => v.toXDR('base64')),
53
- data: processEventBodyValue(rawData),
54
- rawData: rawData.toXDR('base64')
55
- })
56
- }
57
- }
58
-
59
-
60
- /**
61
- * @private
62
- */
63
- analyzeDiagnosticEvents() {
64
- const {diagnosticEvents, processSystemEvents, processMetrics, processFailedOpEffects} = this.effectsAnalyzer
65
- if (!diagnosticEvents)
66
- return
67
- const opContractId = this.effectsAnalyzer.retrieveOpContractId()
68
- //diagnostic events
69
- for (const evt of diagnosticEvents) {
70
- if (!processSystemEvents && !(processFailedOpEffects || evt.inSuccessfulContractCall()))
71
- continue //throw new UnexpectedTxMetaChangeError({type: 'diagnostic_event', action: 'failed'})
72
- //parse event
73
- const event = evt.event()
74
- let contractId = event.contractId() || opContractId //contract id may be attached to the event itself, otherwise use contract from operation
75
- if (contractId && typeof contractId !== 'string') {
76
- contractId = StrKey.encodeContract(contractId)
77
- }
78
- this.processDiagnosticEvent(event._attributes.body._value, event._attributes.type.value, contractId, processMetrics)
79
- }
80
- }
81
-
82
- /**
83
- * @param {xdr.ContractEventV0} body
84
- * @param {Number} type
85
- * @param {String} contract
86
- * @param {Boolean} processMetrics
87
- * @private
88
- */
89
- processDiagnosticEvent(body, type, contract, processMetrics) {
90
- const topics = body.topics()
91
- if (!topics?.length)
92
- return
93
- switch (xdrParseScVal(topics[0])) {
94
- case 'fn_call': // contract call
95
- if (type !== EVENT_TYPES.DIAGNOSTIC)
96
- return // skip non-diagnostic events
97
- const rawArgs = body.data()
98
- const funcCall = {
99
- type: effectTypes.contractInvoked,
100
- contract: xdrParseScVal(topics[1], true),
101
- function: xdrParseScVal(topics[2]),
102
- args: processEventBodyValue(rawArgs),
103
- rawArgs: rawArgs.toXDR('base64')
104
- }
105
- //add the invocation to the call stack
106
- if (this.callStack.length) {
107
- funcCall.depth = this.callStack.length
108
- }
109
- this.callStack.push(funcCall)
110
- this.effectsAnalyzer.addEffect(funcCall)
111
- break
112
- case 'fn_return':
113
- if (type !== EVENT_TYPES.DIAGNOSTIC)
114
- return // skip non-diagnostic events
115
- //attach execution result to the contract invocation event
116
- const lastFuncCall = this.callStack.pop()
117
- const result = body.data()
118
- if (result.switch().name !== 'scvVoid') {
119
- lastFuncCall.result = result.toXDR('base64')
120
- }
121
- break
122
- case 'error':
123
- if (type !== EVENT_TYPES.DIAGNOSTIC)
124
- return // skip non-diagnostic events
125
- let code = topics[1].value().value()
126
- if (code.name) {
127
- code = code.name
128
- }
129
- this.effectsAnalyzer.addEffect({
130
- type: effectTypes.contractError,
131
- contract,
132
- code,
133
- details: processEventBodyValue(body.data())
134
- })
135
- break
136
- case 'core_metrics':
137
- if (type !== EVENT_TYPES.DIAGNOSTIC)
138
- return // skip non-diagnostic events
139
- if (!processMetrics)
140
- return
141
- this.effectsAnalyzer.addMetric(contract, xdrParseScVal(topics[1]), parseInt(processEventBodyValue(body.data())))
142
- break
143
- //handle standard token contract events
144
- //see https://github.com/stellar/rs-soroban-sdk/blob/main/soroban-sdk/src/token.rs
145
- case 'transfer': {
146
- if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']))
147
- return
148
- const from = xdrParseScVal(topics[1])
149
- const receiver = xdrParseScVal(topics[2])
150
- let to = receiver
151
- let amount = processEventBodyValue(body.data())
152
- if (amount?.amount !== undefined) {
153
- if (amount.to_muxed_id && !to.startsWith('M')) {
154
- to = encodeMuxedAccountToAddress(encodeMuxedAccount(to, amount.to_muxed_id))
155
- amount = amount.amount
156
- }
157
- }
158
- if (typeof amount!=='string')
159
- return null
160
- if (to === from) //self transfer - nothing happens
161
- return // TODO: need additional checks
162
- let classicAsset
163
- if (topics.length > 3) {
164
- classicAsset = xdrParseAsset(xdrParseScVal(topics[3]))
165
- if (!mapSacContract(this.effectsAnalyzer, contract, classicAsset)) {
166
- classicAsset = null //not an SAC event
167
- }
168
- }
169
- if (classicAsset) {
170
- if (classicAsset.includes(from)) { //SAC transfer by asset issuer
171
- this.effectsAnalyzer.mint(classicAsset, amount)
172
- }
173
- if (isContractAddress(from)) {
174
- this.effectsAnalyzer.debit(amount, classicAsset, from)
175
- }
176
- if (isContractAddress(to)) {
177
- this.effectsAnalyzer.credit(amount, classicAsset, to)
178
- }
179
- if (classicAsset.includes(receiver)) { //SAC transfer by asset issuer
180
- this.effectsAnalyzer.burn(classicAsset, amount)
181
- }
182
- } else { //other cases
183
- this.effectsAnalyzer.debit(amount, this.effectsAnalyzer.resolveAsset(contract), from)
184
- this.effectsAnalyzer.credit(amount, this.effectsAnalyzer.resolveAsset(contract), to)
185
- }
186
- }
187
- break
188
- case 'mint': {
189
- if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']) && !matchEventTopicsShape(topics, ['address', 'str?']))
190
- return //throw new Error('Non-standard event')
191
- let to = xdrParseScVal(topics[topics[2]?._arm === 'address' ? 2 : 1])
192
- let amount = processEventBodyValue(body.data())
193
- if (amount?.amount !== undefined) {
194
- if (amount.to_muxed_id && !to.startsWith('M')) {
195
- to = encodeMuxedAccountToAddress(encodeMuxedAccount(to, amount.to_muxed_id))
196
- amount = amount.amount
197
- }
198
- }
199
- if (typeof amount !== 'string')
200
- return null
201
- const last = topics[topics.length - 1]
202
- if (last._arm === 'str') {
203
- mapSacContract(this.effectsAnalyzer, contract, xdrParseAsset(xdrParseScVal(last)))
204
- }
205
- const asset = this.effectsAnalyzer.resolveAsset(contract)
206
- this.effectsAnalyzer.mint(asset, amount)
207
- if (isContractAddress(asset) || isContractAddress(to)) {
208
- this.effectsAnalyzer.credit(amount, asset, to)
209
- }
210
- }
211
- break
212
- case 'burn': {
213
- if (!matchEventTopicsShape(topics, ['address', 'str?']))
214
- return //throw new Error('Non-standard event')
215
- const from = xdrParseScVal(topics[1])
216
- const amount = processEventBodyValue(body.data())
217
- if (typeof amount !== 'string')
218
- return null
219
- if (topics.length > 2) {
220
- mapSacContract(this.effectsAnalyzer, contract, xdrParseAsset(xdrParseScVal(topics[2])))
221
- }
222
- const asset = this.effectsAnalyzer.resolveAsset(contract)
223
- if (isContractAddress(asset) || isContractAddress(from)) {
224
- this.effectsAnalyzer.debit(amount, asset, from)
225
- }
226
- this.effectsAnalyzer.burn(asset, amount)
227
- }
228
- break
229
- case 'clawback': {
230
- if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']) && !matchEventTopicsShape(topics, ['address', 'str?']))
231
- return //throw new Error('Non-standard event')
232
- const from = xdrParseScVal(topics[topics[2]?._arm === 'address' ? 2 : 1])
233
- const amount = processEventBodyValue(body.data())
234
- if (typeof amount !== 'string')
235
- return null
236
- if (topics.length > 3) {
237
- mapSacContract(this.effectsAnalyzer, contract, xdrParseAsset(xdrParseScVal(topics[3])))
238
- }
239
- const asset = this.effectsAnalyzer.resolveAsset(contract)
240
- this.effectsAnalyzer.debit(amount, asset, from)
241
- this.effectsAnalyzer.burn(asset, amount)
242
- }
243
- break
244
- case 'set_admin': {
245
- if (!matchEventTopicsShape(topics, ['address', 'str?']))
246
- return //throw new Error('Non-standard event')
247
- const currentAdmin = xdrParseScVal(topics[1])
248
- const newAdmin = processEventBodyValue(body.data())
249
- if (topics.length > 2) {
250
- mapSacContract(this.effectsAnalyzer, contract, xdrParseAsset(xdrParseScVal(topics[2])))
251
- }
252
- this.effectsAnalyzer.setAdmin(contract, newAdmin)
253
- }
254
- break
255
- /*case 'set_authorized':*/ //TODO: think about processing this effects
256
- /*case 'approve': {
257
- if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']))
258
- throw new Error('Non-standard event')
259
- const from = xdrParseScVal(topics[1])
260
- const spender = xdrParseScVal(topics[2])
261
- if (topics.length > 3) {
262
- mapSacContract(this.effectsAnalyzer, contractId, xdrParseAsset(xdrParseScVal(topics[3])))
263
- }
264
- }
265
- break*/
266
- }
267
- }
268
- }
269
-
270
- /**
271
- * Compare types in the topics array with expected values
272
- * @param {ScVal[]} topics
273
- * @param {string[]} shape
274
- * @return {boolean}
275
- */
276
- function matchEventTopicsShape(topics, shape) {
277
- if (topics.length > shape.length + 1)
278
- return false
279
- //we ignore the first topic because it's an event name
280
- for (let i = 0; i < shape.length; i++) {
281
- let match = shape[i]
282
- let optional = false
283
- if (match.endsWith('?')) {
284
- match = match.substring(0, match.length - 1)
285
- optional = true
286
- }
287
- const topic = topics[i + 1]
288
- if (topic) {
289
- if (topic._arm !== match)
290
- return false
291
- } else if (!optional)
292
- return false
293
- }
294
- return true
295
- }
296
-
297
- /**
298
- * Retrieve event body value
299
- * @param value
300
- */
301
- function processEventBodyValue(value) {
302
- const innerValue = value.value()
303
- if (!innerValue) //scVoid
304
- return null
305
- return xdrParseScVal(value) //other scValue
306
- }
307
-
1
+ const {StrKey, encodeMuxedAccount, encodeMuxedAccountToAddress} = require('@stellar/stellar-base')
2
+ const effectTypes = require('../effect-types')
3
+ const {xdrParseScVal, xdrParseAsset} = require('../parser/tx-xdr-parser-utils')
4
+ const {isContractAddress, validateAmount} = require('../parser/normalization')
5
+ const {mapSacContract} = require('./sac-contract-mapper')
6
+
7
+ const EVENT_TYPES = {
8
+ SYSTEM: 0,
9
+ CONTRACT: 1,
10
+ DIAGNOSTIC: 2
11
+ }
12
+
13
+ class EventsAnalyzer {
14
+ /**
15
+ * @param {EffectsAnalyzer} effectsAnalyzer
16
+ */
17
+ constructor(effectsAnalyzer) {
18
+ this.effectsAnalyzer = effectsAnalyzer
19
+ this.callStack = []
20
+ }
21
+
22
+ /**
23
+ * @type {[]}
24
+ * @private
25
+ */
26
+ callStack
27
+
28
+ analyze() {
29
+ this.analyzeDiagnosticEvents()
30
+ this.analyzeEvents()
31
+ }
32
+
33
+ /**
34
+ * @private
35
+ */
36
+ analyzeEvents() {
37
+ const {events} = this.effectsAnalyzer
38
+ if (!events)
39
+ return
40
+ //contract-generated events
41
+ for (const evt of events) {
42
+ const body = evt.body().value()
43
+ const rawTopics = body.topics()
44
+ const topics = rawTopics.map(xdrParseScVal)
45
+ if (topics[0] === 'DATA' && topics[1] === 'set')
46
+ continue //skip data entries modifications
47
+ const rawData = body.data()
48
+ //add event to the pipeline
49
+ this.effectsAnalyzer.addEffect({
50
+ type: effectTypes.contractEvent,
51
+ contract: StrKey.encodeContract(evt.contractId()),
52
+ topics,
53
+ rawTopics: rawTopics.map(v => v.toXDR('base64')),
54
+ data: processEventBodyValue(rawData),
55
+ rawData: rawData.toXDR('base64')
56
+ })
57
+ }
58
+ }
59
+
60
+
61
+ /**
62
+ * @private
63
+ */
64
+ analyzeDiagnosticEvents() {
65
+ const {diagnosticEvents, processSystemEvents, processMetrics, processFailedOpEffects} = this.effectsAnalyzer
66
+ if (!diagnosticEvents)
67
+ return
68
+ const opContractId = this.effectsAnalyzer.retrieveOpContractId()
69
+ //diagnostic events
70
+ for (const evt of diagnosticEvents) {
71
+ if (!processSystemEvents && !(processFailedOpEffects || evt.inSuccessfulContractCall()))
72
+ continue //throw new UnexpectedTxMetaChangeError({type: 'diagnostic_event', action: 'failed'})
73
+ //parse event
74
+ const event = evt.event()
75
+ let contractId = event.contractId() || opContractId //contract id may be attached to the event itself, otherwise use contract from operation
76
+ if (contractId && typeof contractId !== 'string') {
77
+ contractId = StrKey.encodeContract(contractId)
78
+ }
79
+ this.processDiagnosticEvent(event._attributes.body._value, event._attributes.type.value, contractId, processMetrics)
80
+ }
81
+ }
82
+
83
+ /**
84
+ * @param {xdr.ContractEventV0} body
85
+ * @param {Number} type
86
+ * @param {String} contract
87
+ * @param {Boolean} processMetrics
88
+ * @private
89
+ */
90
+ processDiagnosticEvent(body, type, contract, processMetrics) {
91
+ const topics = body.topics()
92
+ if (!topics?.length)
93
+ return
94
+ switch (xdrParseScVal(topics[0])) {
95
+ case 'fn_call': // contract call
96
+ if (type !== EVENT_TYPES.DIAGNOSTIC)
97
+ return // skip non-diagnostic events
98
+ const rawArgs = body.data()
99
+ const funcCall = {
100
+ type: effectTypes.contractInvoked,
101
+ contract: xdrParseScVal(topics[1], true),
102
+ function: xdrParseScVal(topics[2]),
103
+ args: processEventBodyValue(rawArgs),
104
+ rawArgs: rawArgs.toXDR('base64')
105
+ }
106
+ //add the invocation to the call stack
107
+ if (this.callStack.length) {
108
+ funcCall.depth = this.callStack.length
109
+ }
110
+ this.callStack.push(funcCall)
111
+ this.effectsAnalyzer.addEffect(funcCall)
112
+ break
113
+ case 'fn_return':
114
+ if (type !== EVENT_TYPES.DIAGNOSTIC)
115
+ return // skip non-diagnostic events
116
+ //attach execution result to the contract invocation event
117
+ const lastFuncCall = this.callStack.pop()
118
+ const result = body.data()
119
+ if (result.switch().name !== 'scvVoid') {
120
+ lastFuncCall.result = result.toXDR('base64')
121
+ }
122
+ break
123
+ case 'error':
124
+ if (type !== EVENT_TYPES.DIAGNOSTIC)
125
+ return // skip non-diagnostic events
126
+ let code = topics[1].value().value()
127
+ if (code.name) {
128
+ code = code.name
129
+ }
130
+ this.effectsAnalyzer.addEffect({
131
+ type: effectTypes.contractError,
132
+ contract,
133
+ code,
134
+ details: processEventBodyValue(body.data())
135
+ })
136
+ break
137
+ case 'core_metrics':
138
+ if (type !== EVENT_TYPES.DIAGNOSTIC)
139
+ return // skip non-diagnostic events
140
+ if (!processMetrics)
141
+ return
142
+ this.effectsAnalyzer.addMetric(contract, xdrParseScVal(topics[1]), parseInt(processEventBodyValue(body.data())))
143
+ break
144
+ //handle standard token contract events
145
+ //see https://github.com/stellar/rs-soroban-sdk/blob/main/soroban-sdk/src/token.rs
146
+ case 'transfer': {
147
+ if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']))
148
+ return
149
+ const from = xdrParseScVal(topics[1])
150
+ const receiver = xdrParseScVal(topics[2])
151
+ let to = receiver
152
+ let amount = processEventBodyValue(body.data())
153
+ if (amount?.amount !== undefined) {
154
+ if (amount.to_muxed_id && !to.startsWith('M')) {
155
+ to = encodeMuxedAccountToAddress(encodeMuxedAccount(to, amount.to_muxed_id))
156
+ amount = amount.amount
157
+ }
158
+ }
159
+ if (validateAmount(amount, false) === null)
160
+ return null
161
+ if (to === from) //self transfer - nothing happens
162
+ return // TODO: need additional checks
163
+ const asset = this.getAssetFromEventTopics(topics, contract)
164
+ if (!StrKey.isValidContract(asset)) {
165
+ if (asset.includes(from)) { //SAC transfer by asset issuer
166
+ this.effectsAnalyzer.mint(asset, amount)
167
+ }
168
+ if (isContractAddress(from)) {
169
+ this.effectsAnalyzer.debit(amount, asset, from)
170
+ }
171
+ if (isContractAddress(to)) {
172
+ this.effectsAnalyzer.credit(amount, asset, to)
173
+ }
174
+ if (asset.includes(receiver)) { //SAC transfer by asset issuer
175
+ this.effectsAnalyzer.burn(asset, amount)
176
+ }
177
+ } else { //other cases
178
+ this.effectsAnalyzer.debit(amount, asset, from)
179
+ this.effectsAnalyzer.credit(amount, asset, to)
180
+ }
181
+ }
182
+ break
183
+ case 'mint': {
184
+ if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']) && !matchEventTopicsShape(topics, ['address', 'str?']))
185
+ return //throw new Error('Non-standard event')
186
+ let to = xdrParseScVal(topics[topics[2]?._arm === 'address' ? 2 : 1])
187
+ let amount = processEventBodyValue(body.data())
188
+ if (amount?.amount !== undefined) {
189
+ if (amount.to_muxed_id && !to.startsWith('M')) {
190
+ to = encodeMuxedAccountToAddress(encodeMuxedAccount(to, amount.to_muxed_id))
191
+ amount = amount.amount
192
+ }
193
+ }
194
+ if (validateAmount(amount, false) === null)
195
+ return null
196
+ validateAmount(amount)
197
+ const asset = this.getAssetFromEventTopics(topics, contract)
198
+ this.effectsAnalyzer.mint(asset, amount)
199
+ if (isContractAddress(asset) || isContractAddress(to)) {
200
+ this.effectsAnalyzer.credit(amount, asset, to)
201
+ }
202
+ }
203
+ break
204
+ case 'burn': {
205
+ if (!matchEventTopicsShape(topics, ['address', 'str?']))
206
+ return //throw new Error('Non-standard event')
207
+ const from = xdrParseScVal(topics[1])
208
+ const amount = processEventBodyValue(body.data())
209
+ if (validateAmount(amount, false) === null)
210
+ return null
211
+ const asset = this.getAssetFromEventTopics(topics, contract)
212
+ if (isContractAddress(asset) || isContractAddress(from)) {
213
+ this.effectsAnalyzer.debit(amount, asset, from)
214
+ }
215
+ this.effectsAnalyzer.burn(asset, amount)
216
+ }
217
+ break
218
+ case 'clawback': {
219
+ if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']) && !matchEventTopicsShape(topics, ['address', 'str?']))
220
+ return //throw new Error('Non-standard event')
221
+ const from = xdrParseScVal(topics[topics[2]?._arm === 'address' ? 2 : 1])
222
+ const amount = processEventBodyValue(body.data())
223
+ if (validateAmount(amount, false) === null)
224
+ return null
225
+ const asset = this.getAssetFromEventTopics(topics, contract)
226
+ if (StrKey.isValidContract(from)) { //transfer tokens from account only in case of contract assets to avoid double debits
227
+ this.effectsAnalyzer.debit(amount, asset, from)
228
+ this.effectsAnalyzer.burn(asset, amount)
229
+ }
230
+ }
231
+ break
232
+ case 'set_admin': {
233
+ if (!matchEventTopicsShape(topics, ['address', 'str?']))
234
+ return //throw new Error('Non-standard event')
235
+ const currentAdmin = xdrParseScVal(topics[1])
236
+ const newAdmin = processEventBodyValue(body.data())
237
+ this.getAssetFromEventTopics(topics, contract)
238
+ this.effectsAnalyzer.setAdmin(contract, newAdmin)
239
+ }
240
+ break
241
+ case 'set_authorized': {
242
+ if (!matchEventTopicsShape(topics, ['address', 'str?']))
243
+ return //throw new Error('Non-standard event')
244
+ const trustor = xdrParseScVal(topics[1])
245
+ const asset = this.getAssetFromEventTopics(topics, contract)
246
+ const isAuthorized = processEventBodyValue(body.data())
247
+ this.effectsAnalyzer.addEffect({
248
+ type: effectTypes.trustlineAuthorizationUpdated,
249
+ trustor,
250
+ asset,
251
+ flags: isAuthorized ? 1 : 0,
252
+ prevFlags: isAuthorized ? 0 : 1
253
+ })
254
+ }
255
+ break
256
+ //TODO: think about processing these effects
257
+ /*case 'approve': {
258
+ if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']))
259
+ throw new Error('Non-standard event')
260
+ const from = xdrParseScVal(topics[1])
261
+ const spender = xdrParseScVal(topics[2])
262
+ if (topics.length > 3) {
263
+ mapSacContract(this.effectsAnalyzer, contractId, xdrParseAsset(xdrParseScVal(topics[3])))
264
+ }
265
+ }
266
+ break*/
267
+ }
268
+ }
269
+
270
+ /**
271
+ * @param {ScVal[]} topics
272
+ * @param {string} contract
273
+ * @return {string|null}
274
+ * @private
275
+ */
276
+ getAssetFromEventTopics(topics, contract) {
277
+ const last = topics[topics.length - 1]
278
+ if (last._arm === 'str') {
279
+ const classicAsset = xdrParseAsset(xdrParseScVal(last))
280
+ mapSacContract(this.effectsAnalyzer, contract, classicAsset)
281
+ }
282
+ return this.effectsAnalyzer.resolveAsset(contract)
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Compare types in the topics array with expected values
288
+ * @param {ScVal[]} topics
289
+ * @param {string[]} shape
290
+ * @return {boolean}
291
+ */
292
+ function matchEventTopicsShape(topics, shape) {
293
+ if (topics.length > shape.length + 1)
294
+ return false
295
+ //we ignore the first topic because it's an event name
296
+ for (let i = 0; i < shape.length; i++) {
297
+ let match = shape[i]
298
+ let optional = false
299
+ if (match.endsWith('?')) {
300
+ match = match.substring(0, match.length - 1)
301
+ optional = true
302
+ }
303
+ const topic = topics[i + 1]
304
+ if (topic) {
305
+ if (topic._arm !== match)
306
+ return false
307
+ } else if (!optional)
308
+ return false
309
+ }
310
+ return true
311
+ }
312
+
313
+ /**
314
+ * Retrieve event body value
315
+ * @param value
316
+ */
317
+ function processEventBodyValue(value) {
318
+ const innerValue = value.value()
319
+ if (innerValue === undefined) //scVoid
320
+ return null
321
+ return xdrParseScVal(value) //other scValue
322
+ }
323
+
308
324
  module.exports = EventsAnalyzer