@stellar-expert/tx-meta-effects-parser 5.0.4 → 5.1.1
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.
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@ class AssetSupplyProcessor {
|
|
|
8
8
|
* @param {EffectsAnalyzer} effectsAnalyzer
|
|
9
9
|
*/
|
|
10
10
|
constructor(effectsAnalyzer) {
|
|
11
|
-
this.assetTransfers =
|
|
11
|
+
this.assetTransfers = new Map()
|
|
12
12
|
this.processXlmBalances = effectsAnalyzer.isContractCall
|
|
13
13
|
this.effectsAnalyzer = effectsAnalyzer
|
|
14
14
|
}
|
|
@@ -19,7 +19,7 @@ class AssetSupplyProcessor {
|
|
|
19
19
|
*/
|
|
20
20
|
effectsAnalyzer
|
|
21
21
|
/**
|
|
22
|
-
* @type {
|
|
22
|
+
* @type {Map<String,BigInt>}
|
|
23
23
|
* @private
|
|
24
24
|
*/
|
|
25
25
|
assetTransfers
|
|
@@ -28,6 +28,32 @@ class AssetSupplyProcessor {
|
|
|
28
28
|
* @private
|
|
29
29
|
*/
|
|
30
30
|
processXlmBalances = false
|
|
31
|
+
/**
|
|
32
|
+
* @type {Number}
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
supplyChanges = 0
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Calculate differences and generate minted/burned effects if needed
|
|
39
|
+
*/
|
|
40
|
+
analyze() {
|
|
41
|
+
for (const effect of this.effectsAnalyzer.effects) {
|
|
42
|
+
this.processEffect(effect)
|
|
43
|
+
}
|
|
44
|
+
for (const [asset, amount] of this.assetTransfers.entries()) {
|
|
45
|
+
if (amount > 0n) {
|
|
46
|
+
this.effectsAnalyzer.mint(asset, amount.toString(), true)
|
|
47
|
+
this.supplyChanges |= 2
|
|
48
|
+
} else if (amount < 0n) {
|
|
49
|
+
this.effectsAnalyzer.burn(asset, (-amount).toString())
|
|
50
|
+
this.supplyChanges |= 1
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if ((this.supplyChanges & 3) === 3) { //analyze possible collapsible mints only if both mint and burn effects recorded
|
|
54
|
+
new CollapsibleMintsAnalyzer(this.effectsAnalyzer).removeCollapsingMints()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
31
57
|
|
|
32
58
|
/**
|
|
33
59
|
* Process generated operation effect
|
|
@@ -37,15 +63,23 @@ class AssetSupplyProcessor {
|
|
|
37
63
|
switch (effect.type) {
|
|
38
64
|
case effectTypes.accountCredited:
|
|
39
65
|
case effectTypes.claimableBalanceCreated:
|
|
66
|
+
//increase supply
|
|
67
|
+
this.increase(effect.asset, effect.amount)
|
|
68
|
+
break
|
|
40
69
|
case effectTypes.assetBurned:
|
|
41
70
|
//increase supply
|
|
42
71
|
this.increase(effect.asset, effect.amount)
|
|
72
|
+
this.supplyChanges |= 1
|
|
43
73
|
break
|
|
44
74
|
case effectTypes.accountDebited:
|
|
45
75
|
case effectTypes.claimableBalanceRemoved:
|
|
76
|
+
//decrease supply
|
|
77
|
+
this.decrease(effect.asset, effect.amount)
|
|
78
|
+
break
|
|
46
79
|
case effectTypes.assetMinted:
|
|
47
80
|
//decrease supply
|
|
48
81
|
this.decrease(effect.asset, effect.amount)
|
|
82
|
+
this.supplyChanges |= 2
|
|
49
83
|
break
|
|
50
84
|
case effectTypes.liquidityPoolDeposited:
|
|
51
85
|
//increase supply for every deposited asset (if liquidity provider is an issuer)
|
|
@@ -73,22 +107,6 @@ class AssetSupplyProcessor {
|
|
|
73
107
|
}
|
|
74
108
|
}
|
|
75
109
|
|
|
76
|
-
/**
|
|
77
|
-
* Calculate differences and generate minted/burned effects if needed
|
|
78
|
-
*/
|
|
79
|
-
analyze() {
|
|
80
|
-
for (const effect of this.effectsAnalyzer.effects) {
|
|
81
|
-
this.processEffect(effect)
|
|
82
|
-
}
|
|
83
|
-
for (const [asset, amount] of Object.entries(this.assetTransfers)) {
|
|
84
|
-
if (amount > 0n) {
|
|
85
|
-
this.effectsAnalyzer.mint(asset, amount.toString(), true)
|
|
86
|
-
} else if (amount < 0n) {
|
|
87
|
-
this.effectsAnalyzer.burn(asset, (-amount).toString())
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
110
|
/**
|
|
93
111
|
* @param {String} asset
|
|
94
112
|
* @param {String} amount
|
|
@@ -97,7 +115,7 @@ class AssetSupplyProcessor {
|
|
|
97
115
|
increase(asset, amount) {
|
|
98
116
|
if (!this.shouldProcessAsset(asset))
|
|
99
117
|
return
|
|
100
|
-
this.assetTransfers
|
|
118
|
+
this.assetTransfers.set(asset, (this.assetTransfers.get(asset) || 0n) + BigInt(amount))
|
|
101
119
|
}
|
|
102
120
|
|
|
103
121
|
/**
|
|
@@ -108,7 +126,7 @@ class AssetSupplyProcessor {
|
|
|
108
126
|
decrease(asset, amount) {
|
|
109
127
|
if (!this.shouldProcessAsset(asset))
|
|
110
128
|
return
|
|
111
|
-
this.assetTransfers
|
|
129
|
+
this.assetTransfers.set(asset, (this.assetTransfers.get(asset) || 0n) - BigInt(amount))
|
|
112
130
|
}
|
|
113
131
|
|
|
114
132
|
/**
|
|
@@ -122,4 +140,65 @@ class AssetSupplyProcessor {
|
|
|
122
140
|
}
|
|
123
141
|
}
|
|
124
142
|
|
|
143
|
+
class CollapsibleMintsAnalyzer {
|
|
144
|
+
/**
|
|
145
|
+
* @param {EffectsAnalyzer} effectsAnalyzer
|
|
146
|
+
*/
|
|
147
|
+
constructor(effectsAnalyzer) {
|
|
148
|
+
this.effectsAnalyzer = effectsAnalyzer
|
|
149
|
+
this.supply = new Map()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @type {EffectsAnalyzer}
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
effectsAnalyzer
|
|
157
|
+
/**
|
|
158
|
+
* @type {Map<String,[]>}
|
|
159
|
+
* @private
|
|
160
|
+
*/
|
|
161
|
+
supply
|
|
162
|
+
|
|
163
|
+
removeCollapsingMints() {
|
|
164
|
+
const {effects} = this.effectsAnalyzer
|
|
165
|
+
for (const effect of effects) {
|
|
166
|
+
if (effect.type === effectTypes.assetMinted || effect.type === effectTypes.assetBurned) {
|
|
167
|
+
this.addEffectToSupplyCounter(effect)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const [asset, assetEffects] of this.supply.entries()) {
|
|
171
|
+
if (assetEffects.length < 2)
|
|
172
|
+
continue //skip non-collapsible effects
|
|
173
|
+
//aggregate amount
|
|
174
|
+
let sum = 0n
|
|
175
|
+
let position
|
|
176
|
+
for (const effect of assetEffects) {
|
|
177
|
+
sum += effect.type === effectTypes.assetMinted ? BigInt(effect.amount) : -BigInt(effect.amount) //add to the running total
|
|
178
|
+
position = effects.indexOf(effect) //find effect position in the parent effects container
|
|
179
|
+
effects.splice(position, 1) //remove the effect from the parent container
|
|
180
|
+
}
|
|
181
|
+
if (sum > 0n) { //asset minted
|
|
182
|
+
this.effectsAnalyzer.mint(asset, sum.toString(), true) //insert mint effect
|
|
183
|
+
} else if (sum < 0n) { //asset burned
|
|
184
|
+
this.effectsAnalyzer.burn(asset, sum.toString(), position) //insert burn effect at the position of the last removed effect
|
|
185
|
+
}
|
|
186
|
+
//if sum=0 then both effects were annihilated and removed
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {{}} effect
|
|
192
|
+
* @private
|
|
193
|
+
*/
|
|
194
|
+
addEffectToSupplyCounter(effect) {
|
|
195
|
+
let container = this.supply.get(effect.asset)
|
|
196
|
+
if (!container) {
|
|
197
|
+
container = []
|
|
198
|
+
this.supply.set(effect.asset, container)
|
|
199
|
+
}
|
|
200
|
+
container.push(effect)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
125
204
|
module.exports = AssetSupplyProcessor
|
package/src/events-analyzer.js
CHANGED
|
@@ -161,7 +161,7 @@ class EventsAnalyzer {
|
|
|
161
161
|
case 'mint': {
|
|
162
162
|
if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']))
|
|
163
163
|
return //throw new Error('Non-standard event')
|
|
164
|
-
const to = xdrParseScVal(topics[
|
|
164
|
+
const to = xdrParseScVal(topics[2])
|
|
165
165
|
const amount = processEventBodyValue(body.data())
|
|
166
166
|
if (!this.matchInvocationEffect(e => e.function === 'mint' && matchArrays([to, amount], e.args)))
|
|
167
167
|
return
|
|
@@ -191,7 +191,6 @@ class EventsAnalyzer {
|
|
|
191
191
|
case 'clawback': {
|
|
192
192
|
if (!matchEventTopicsShape(topics, ['address', 'address', 'str?']))
|
|
193
193
|
return //throw new Error('Non-standard event')
|
|
194
|
-
const admin = xdrParseScVal(topics[1])
|
|
195
194
|
const from = xdrParseScVal(topics[2])
|
|
196
195
|
const amount = processEventBodyValue(body.data())
|
|
197
196
|
if (!this.matchInvocationEffect(e => e.function === 'clawback' && matchArrays([from, amount], e.args)))
|
|
@@ -298,7 +298,14 @@ function parseContractData(value) {
|
|
|
298
298
|
break
|
|
299
299
|
case 'contractExecutableWasm':
|
|
300
300
|
entry.kind = 'wasm'
|
|
301
|
-
|
|
301
|
+
const instance = valueAttr.instance()._attributes
|
|
302
|
+
entry.hash = instance.executable.wasmHash().toString('hex')
|
|
303
|
+
if (instance.storage?.length) {
|
|
304
|
+
entry.storage = instance.storage.map(entry => ({
|
|
305
|
+
key: entry.key().toXDR('base64'),
|
|
306
|
+
val: entry.val().toXDR('base64')
|
|
307
|
+
}))
|
|
308
|
+
}
|
|
302
309
|
break
|
|
303
310
|
default:
|
|
304
311
|
throw new TxMetaEffectParserError('Unsupported executable type: ' + type)
|
|
@@ -145,12 +145,12 @@ class EffectsAnalyzer {
|
|
|
145
145
|
}, position)
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
burn(asset, amount) {
|
|
148
|
+
burn(asset, amount, position = undefined) {
|
|
149
149
|
this.addEffect({
|
|
150
150
|
type: effectTypes.assetBurned,
|
|
151
151
|
asset,
|
|
152
152
|
amount
|
|
153
|
-
})
|
|
153
|
+
}, position)
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
setOptions() {
|
|
@@ -694,33 +694,6 @@ class EffectsAnalyzer {
|
|
|
694
694
|
this.addEffect(effect)
|
|
695
695
|
}
|
|
696
696
|
|
|
697
|
-
processContractDataChanges({action, before, after}) {
|
|
698
|
-
const {owner, key, value, durability} = after || before
|
|
699
|
-
const effect = {
|
|
700
|
-
type: '',
|
|
701
|
-
owner,
|
|
702
|
-
key,
|
|
703
|
-
value,
|
|
704
|
-
durability
|
|
705
|
-
}
|
|
706
|
-
switch (action) {
|
|
707
|
-
case 'created':
|
|
708
|
-
effect.type = effectTypes.contractDataCreated
|
|
709
|
-
break
|
|
710
|
-
case 'updated':
|
|
711
|
-
if (before.value === after.value)
|
|
712
|
-
return //value has not changed
|
|
713
|
-
effect.type = effectTypes.contractDataUpdated
|
|
714
|
-
break
|
|
715
|
-
case 'removed':
|
|
716
|
-
effect.type = effectTypes.contractDataRemoved
|
|
717
|
-
delete effect.value
|
|
718
|
-
break
|
|
719
|
-
}
|
|
720
|
-
this.addEffect(effect)
|
|
721
|
-
this.processContractBalance(effect)
|
|
722
|
-
}
|
|
723
|
-
|
|
724
697
|
processContractBalance(effect) {
|
|
725
698
|
const parsedKey = xdr.ScVal.fromXDR(effect.key, 'base64')
|
|
726
699
|
if (parsedKey._arm !== 'vec')
|
|
@@ -748,22 +721,108 @@ class EffectsAnalyzer {
|
|
|
748
721
|
balanceEffects[0].balance = parsedValue.amount
|
|
749
722
|
}
|
|
750
723
|
|
|
751
|
-
processContractChanges({action, after}) {
|
|
752
|
-
if (action
|
|
753
|
-
return // TODO: check whether any contract properties changed
|
|
754
|
-
if (action !== 'created')
|
|
724
|
+
processContractChanges({action, before, after}) {
|
|
725
|
+
if (action !== 'created' && action !== 'updated')
|
|
755
726
|
throw new UnexpectedTxMetaChangeError({type: 'contract', action})
|
|
756
727
|
const {kind, contract, hash} = after
|
|
757
|
-
if (this.effects.some(e => e.contract === contract))
|
|
758
|
-
return //skip contract creation effects processed by top-level createContract operation call
|
|
759
728
|
const effect = {
|
|
760
729
|
type: effectTypes.contractCreated,
|
|
761
730
|
contract,
|
|
762
731
|
kind,
|
|
763
732
|
wasmHash: hash
|
|
764
733
|
}
|
|
734
|
+
if (action === 'created') {
|
|
735
|
+
if (this.effects.some(e => e.contract === contract))
|
|
736
|
+
return //skip contract creation effects processed by top-level createContract operation call
|
|
737
|
+
} else if (action === 'updated') {
|
|
738
|
+
effect.type = effectTypes.contractUpdated
|
|
739
|
+
effect.prevWasmHash = before.hash
|
|
740
|
+
if (before.storage?.length || after.storage?.length) {
|
|
741
|
+
this.processInstanceDataChanges(before, after)
|
|
742
|
+
}
|
|
743
|
+
if (before.hash === hash) //skip if hash unchanged
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
this.addEffect(effect)
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
processContractDataChanges({action, before, after}) {
|
|
750
|
+
const {owner, key, durability} = after || before
|
|
751
|
+
const effect = {
|
|
752
|
+
type: '',
|
|
753
|
+
owner,
|
|
754
|
+
durability,
|
|
755
|
+
key
|
|
756
|
+
}
|
|
757
|
+
switch (action) {
|
|
758
|
+
case 'created':
|
|
759
|
+
effect.type = effectTypes.contractDataCreated
|
|
760
|
+
effect.value = after.value
|
|
761
|
+
break
|
|
762
|
+
case 'updated':
|
|
763
|
+
if (before.value === after.value)
|
|
764
|
+
return //value has not changed
|
|
765
|
+
effect.type = effectTypes.contractDataUpdated
|
|
766
|
+
effect.value = after.value
|
|
767
|
+
effect.prevValue = before.value
|
|
768
|
+
break
|
|
769
|
+
case 'removed':
|
|
770
|
+
effect.type = effectTypes.contractDataRemoved
|
|
771
|
+
effect.prevValue = before.value
|
|
772
|
+
break
|
|
773
|
+
}
|
|
765
774
|
this.addEffect(effect)
|
|
775
|
+
this.processContractBalance(effect)
|
|
776
|
+
}
|
|
766
777
|
|
|
778
|
+
processInstanceDataChanges(before, after) {
|
|
779
|
+
const storageBefore = before.storage || []
|
|
780
|
+
const storageAfter = [...(after.storage || [])]
|
|
781
|
+
for (const {key, val} of storageBefore) {
|
|
782
|
+
let newVal
|
|
783
|
+
for (let i = 0; i < storageAfter.length; i++) {
|
|
784
|
+
const afterValue = storageAfter[i]
|
|
785
|
+
if (afterValue.key === key) {
|
|
786
|
+
newVal = afterValue.val //update new value
|
|
787
|
+
storageAfter.splice(i, 1) //remove from array to simplify iteration
|
|
788
|
+
break
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (newVal === undefined) { //removed
|
|
792
|
+
const effect = {
|
|
793
|
+
type: effectTypes.contractDataRemoved,
|
|
794
|
+
owner: after.contract || before.contract,
|
|
795
|
+
key,
|
|
796
|
+
prevValue: val,
|
|
797
|
+
durability: 'instance'
|
|
798
|
+
}
|
|
799
|
+
this.addEffect(effect)
|
|
800
|
+
continue
|
|
801
|
+
}
|
|
802
|
+
if (val === newVal) //value has not changed
|
|
803
|
+
continue
|
|
804
|
+
|
|
805
|
+
const effect = {
|
|
806
|
+
type: effectTypes.contractDataUpdated,
|
|
807
|
+
owner: after.contract || before.contract,
|
|
808
|
+
key,
|
|
809
|
+
value: newVal,
|
|
810
|
+
prevValue: val,
|
|
811
|
+
durability: 'instance'
|
|
812
|
+
}
|
|
813
|
+
this.addEffect(effect)
|
|
814
|
+
}
|
|
815
|
+
//iterate all storage items left
|
|
816
|
+
for (const {key, val} of storageAfter) {
|
|
817
|
+
const effect = {
|
|
818
|
+
type: effectTypes.contractDataCreated,
|
|
819
|
+
owner: after.contract || before.contract,
|
|
820
|
+
key,
|
|
821
|
+
value: val,
|
|
822
|
+
durability: 'instance'
|
|
823
|
+
}
|
|
824
|
+
this.addEffect(effect)
|
|
825
|
+
}
|
|
767
826
|
}
|
|
768
827
|
|
|
769
828
|
processChanges() {
|