@subsquid/solana-normalization 0.0.3 → 1.0.0-portal-api.d887ad

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.
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TransactionContext = void 0;
7
+ const assert_1 = __importDefault(require("assert"));
8
+ class TransactionContext {
9
+ constructor(transactionIndex, tx, journal) {
10
+ this.transactionIndex = transactionIndex;
11
+ this.tx = tx;
12
+ this.journal = journal;
13
+ if (tx.version == 'legacy') {
14
+ this.accounts = tx.transaction.message.accountKeys;
15
+ }
16
+ else {
17
+ this.accounts = tx.transaction.message.accountKeys.concat(tx.meta.loadedAddresses?.writable ?? [], tx.meta.loadedAddresses?.readonly ?? []);
18
+ }
19
+ let err = this.tx.meta.err;
20
+ if (err && 'InstructionError' in err) {
21
+ let pos = err.InstructionError?.[0];
22
+ let type = err.InstructionError?.[1];
23
+ if (Number.isSafeInteger(pos)) {
24
+ this.erroredInstruction = pos;
25
+ }
26
+ else {
27
+ this.erroredInstruction = tx.transaction.message.instructions.length;
28
+ this.warn({ transactionError: err }, 'got InstructionError of unrecognized shape');
29
+ }
30
+ this.exceededCallDepth = type === 'CallDepth';
31
+ }
32
+ else {
33
+ this.erroredInstruction = tx.transaction.message.instructions.length;
34
+ this.exceededCallDepth = false;
35
+ }
36
+ }
37
+ get isCommitted() {
38
+ return this.tx.meta.err == null;
39
+ }
40
+ get transactionHash() {
41
+ return this.tx.transaction.signatures[0];
42
+ }
43
+ getAccount(index) {
44
+ (0, assert_1.default)(index < this.accounts.length);
45
+ return this.accounts[index];
46
+ }
47
+ warn(props, msg) {
48
+ this.journal.warn({
49
+ transactionHash: this.transactionHash,
50
+ transactionIndex: this.transactionIndex,
51
+ ...props
52
+ }, msg);
53
+ }
54
+ error(props, msg) {
55
+ this.journal.error({
56
+ transactionHash: this.transactionHash,
57
+ transactionIndex: this.transactionIndex,
58
+ ...props
59
+ }, msg);
60
+ }
61
+ }
62
+ exports.TransactionContext = TransactionContext;
63
+ //# sourceMappingURL=transaction-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction-context.js","sourceRoot":"","sources":["../src/transaction-context.ts"],"names":[],"mappings":";;;;;;AAEA,oDAA2B;AAS3B,MAAa,kBAAkB;IAM3B,YACoB,gBAAwB,EACxB,EAAmB,EAC3B,OAAgB;QAFR,qBAAgB,GAAhB,gBAAgB,CAAQ;QACxB,OAAE,GAAF,EAAE,CAAiB;QAC3B,YAAO,GAAP,OAAO,CAAS;QAExB,IAAI,EAAE,CAAC,OAAO,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,WAAW,CAAA;QACtD,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,WAAW,CAAC,MAAM,CACrD,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,EAAE,EACvC,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,IAAI,EAAE,CAC1C,CAAA;QACL,CAAC;QAED,IAAI,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAA;QAC1B,IAAI,GAAG,IAAI,kBAAkB,IAAI,GAAG,EAAE,CAAC;YACnC,IAAI,GAAG,GAAG,GAAG,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAA;YACnC,IAAI,IAAI,GAAG,GAAG,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,CAAA;YACpC,IAAI,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,kBAAkB,GAAG,GAAG,CAAA;YACjC,CAAC;iBAAM,CAAC;gBACJ,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAA;gBACpE,IAAI,CAAC,IAAI,CAAC,EAAC,gBAAgB,EAAE,GAAG,EAAC,EAAE,4CAA4C,CAAC,CAAA;YACpF,CAAC;YACD,IAAI,CAAC,iBAAiB,GAAG,IAAI,KAAK,WAAW,CAAA;QACjD,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAA;YACpE,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAA;QAClC,CAAC;IACL,CAAC;IAED,IAAI,WAAW;QACX,OAAO,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,CAAA;IACnC,CAAC;IAED,IAAI,eAAe;QACf,OAAO,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;IAC5C,CAAC;IAED,UAAU,CAAC,KAAa;QACpB,IAAA,gBAAM,EAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC/B,CAAC;IAED,IAAI,CAAC,KAAU,EAAE,GAAW;QACxB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YACd,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,GAAG,KAAK;SACX,EAAE,GAAG,CAAC,CAAA;IACX,CAAC;IAED,KAAK,CAAC,KAAU,EAAE,GAAW;QACzB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;YACf,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;YACvC,GAAG,KAAK;SACX,EAAE,GAAG,CAAC,CAAA;IACX,CAAC;CACJ;AAjED,gDAiEC"}
package/lib/votes.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { Block } from './data';
2
+ export declare function removeVotes(block: Block): void;
3
+ //# sourceMappingURL=votes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"votes.d.ts","sourceRoot":"","sources":["../src/votes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,QAAQ,CAAA;AAG5B,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAkB9C"}
package/lib/votes.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.removeVotes = removeVotes;
4
+ function removeVotes(block) {
5
+ let removed = new Set();
6
+ for (let i of block.instructions) {
7
+ if (i.programId == 'Vote111111111111111111111111111111111111111') {
8
+ removed.add(i.transactionIndex);
9
+ }
10
+ }
11
+ function kept(item) {
12
+ return !removed.has(item.transactionIndex);
13
+ }
14
+ block.transactions = block.transactions.filter(kept);
15
+ block.instructions = block.instructions.filter(kept);
16
+ block.logs = block.logs.filter(kept);
17
+ block.balances = block.balances.filter(kept);
18
+ block.tokenBalances = block.tokenBalances.filter(kept);
19
+ }
20
+ //# sourceMappingURL=votes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"votes.js","sourceRoot":"","sources":["../src/votes.ts"],"names":[],"mappings":";;AAGA,kCAkBC;AAlBD,SAAgB,WAAW,CAAC,KAAY;IACpC,IAAI,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;IAE/B,KAAK,IAAI,CAAC,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QAC/B,IAAI,CAAC,CAAC,SAAS,IAAI,6CAA6C,EAAE,CAAC;YAC/D,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAA;QACnC,CAAC;IACL,CAAC;IAED,SAAS,IAAI,CAAC,IAAgC;QAC1C,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;IAC9C,CAAC;IAED,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACpC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC5C,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;AAC1D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subsquid/solana-normalization",
3
- "version": "0.0.3",
3
+ "version": "1.0.0-portal-api.d887ad",
4
4
  "description": "Solana data model",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": "git@github.com:subsquid/squid.git",
@@ -13,13 +13,15 @@
13
13
  ],
14
14
  "main": "lib/index.js",
15
15
  "dependencies": {
16
- "@subsquid/solana-rpc-data": "^0.0.3",
17
- "@subsquid/util-internal": "^3.2.0",
16
+ "@subsquid/solana-rpc-data": "1.0.0-portal-api.d887ad",
17
+ "@subsquid/util-internal": "3.3.0-portal-api.d887ad",
18
18
  "@subsquid/util-internal-validation": "^0.7.0"
19
19
  },
20
20
  "devDependencies": {
21
+ "@subsquid/util-internal-json": "^1.2.3",
22
+ "@subsquid/util-internal-json-fix-unsafe-integers": "^0.0.0",
21
23
  "@types/node": "^18.18.14",
22
- "typescript": "~5.3.2"
24
+ "typescript": "5.5.4"
23
25
  },
24
26
  "scripts": {
25
27
  "build": "rm -rf lib && tsc"
package/src/archive.ts ADDED
@@ -0,0 +1,186 @@
1
+ import {Base58Bytes} from '@subsquid/solana-rpc-data'
2
+ import * as norm from './data'
3
+
4
+
5
+ /**
6
+ * Block account index
7
+ */
8
+ export type AccountIndex = number
9
+
10
+
11
+ export interface Block {
12
+ header: BlockHeader
13
+ accounts: Base58Bytes[]
14
+ transactions: Transaction[]
15
+ instructions: Instruction[]
16
+ logs: LogMessage[]
17
+ balances: Balance[]
18
+ tokenBalances: TokenBalance[]
19
+ rewards: Reward[]
20
+ }
21
+
22
+
23
+ export interface BlockHeader {
24
+ number: number
25
+ hash: string
26
+ parentNumber: number
27
+ parentHash: string
28
+ height?: number
29
+ timestamp?: number
30
+ }
31
+
32
+
33
+ export type Transaction = Omit<norm.Transaction, 'accountKeys' | 'addressTableLookups' | 'loadedAddresses'> & {
34
+ accountKeys: AccountIndex[]
35
+ addressTableLookups: AddressTableLookup[]
36
+ loadedAddresses: {
37
+ readonly: AccountIndex[]
38
+ writable: AccountIndex[]
39
+ }
40
+ }
41
+
42
+
43
+ export interface AddressTableLookup {
44
+ accountKey: AccountIndex
45
+ readonlyIndexes: number[]
46
+ writableIndexes: number[]
47
+ }
48
+
49
+
50
+ export type Instruction = Omit<norm.Instruction, 'programId' | 'accounts'> & {
51
+ programId: AccountIndex
52
+ accounts: AccountIndex[]
53
+ }
54
+
55
+
56
+ export type LogMessage = Omit<norm.LogMessage, 'programId'> & {
57
+ programId: AccountIndex
58
+ }
59
+
60
+
61
+ export type Balance = Omit<norm.Balance, 'account'> & {
62
+ account: AccountIndex
63
+ }
64
+
65
+
66
+ export type TokenBalance = PatchAccounts<norm.TokenBalance>
67
+
68
+
69
+ type PatchAccounts<T> = {
70
+ [K in keyof T]: T[K] extends string ? AccountIndex : T[K]
71
+ }
72
+
73
+
74
+ export type Reward = Omit<norm.Reward, 'pubkey'> & {
75
+ pubkey: AccountIndex
76
+ }
77
+
78
+
79
+ export function toArchiveBlock(block: norm.Block): Block {
80
+ let dict = new AccountDict()
81
+
82
+ let {
83
+ header: {number: slot, parentNumber: parentSlot, ...hdr}
84
+ } = block
85
+
86
+ let header = {
87
+ number: slot,
88
+ parentNumber: parentSlot,
89
+ ...hdr
90
+ }
91
+
92
+ let transactions = block.transactions.map(tx => {
93
+ let {accountKeys, addressTableLookups, loadedAddresses, ...props} = tx
94
+ return {
95
+ ...props,
96
+ accountKeys: accountKeys.map(a => dict.get(a)),
97
+ addressTableLookups: addressTableLookups.map(lookup => {
98
+ return {
99
+ accountKey: dict.get(lookup.accountKey),
100
+ readonlyIndexes: lookup.readonlyIndexes,
101
+ writableIndexes: lookup.writableIndexes
102
+ }
103
+ }),
104
+ loadedAddresses: {
105
+ readonly: loadedAddresses.readonly.map(a => dict.get(a)),
106
+ writable: loadedAddresses.writable.map(a => dict.get(a))
107
+ }
108
+ }
109
+ })
110
+
111
+ let instructions = block.instructions.map(ins => {
112
+ let {programId, accounts, ...props} = ins
113
+ return {
114
+ ...props,
115
+ programId: dict.get(programId),
116
+ accounts: accounts.map(a => dict.get(a))
117
+ }
118
+ })
119
+
120
+ let logs = block.logs.map(rec => {
121
+ let {programId, ...props} = rec
122
+ return {
123
+ ...props,
124
+ programId: dict.get(programId)
125
+ }
126
+ })
127
+
128
+ let balances = block.balances.map(b => {
129
+ let {account, ...props} = b
130
+ return {
131
+ ...props,
132
+ account: dict.get(account)
133
+ }
134
+ })
135
+
136
+ let tokenBalances: TokenBalance[] = block.tokenBalances.map(b => {
137
+ let res: any = {}
138
+ let key: keyof norm.TokenBalance
139
+ for (key in b) {
140
+ let val = b[key]
141
+ if (typeof val == 'string') {
142
+ res[key] = dict.get(val)
143
+ } else {
144
+ res[key] = val
145
+ }
146
+ }
147
+ return res
148
+ })
149
+
150
+ let rewards = block.rewards.map(reward => {
151
+ let {pubkey, ...props} = reward
152
+ return {
153
+ pubkey: dict.get(pubkey),
154
+ ...props
155
+ }
156
+ })
157
+
158
+ return {
159
+ header,
160
+ accounts: dict.list(),
161
+ transactions,
162
+ instructions,
163
+ logs,
164
+ balances,
165
+ tokenBalances,
166
+ rewards
167
+ }
168
+ }
169
+
170
+
171
+ class AccountDict {
172
+ private map = new Map<Base58Bytes, AccountIndex>
173
+
174
+ get(account: Base58Bytes): AccountIndex {
175
+ let idx = this.map.get(account)
176
+ if (idx == null) {
177
+ idx = this.map.size
178
+ this.map.set(account, idx)
179
+ }
180
+ return idx
181
+ }
182
+
183
+ list(): Base58Bytes[] {
184
+ return Array.from(this.map.keys())
185
+ }
186
+ }
package/src/data.ts CHANGED
@@ -2,10 +2,10 @@ import type {Base58Bytes} from '@subsquid/solana-rpc-data'
2
2
 
3
3
 
4
4
  export interface BlockHeader {
5
+ number: number
5
6
  hash: Base58Bytes
6
7
  height: number
7
- slot: number
8
- parentSlot: number
8
+ parentNumber: number
9
9
  parentHash: Base58Bytes
10
10
  timestamp: number
11
11
  }
package/src/index.ts CHANGED
@@ -1,2 +1,4 @@
1
+ export * as archive from './archive'
1
2
  export * from './data'
2
3
  export * from './mapping'
4
+ export * from './votes'
@@ -0,0 +1,344 @@
1
+ import * as rpc from '@subsquid/solana-rpc-data'
2
+ import {unexpectedCase} from '@subsquid/util-internal'
3
+ import assert from 'assert'
4
+ import {Instruction, LogMessage} from './data'
5
+ import {Message, parseLogMessage} from './log-parser'
6
+ import {TransactionContext} from './transaction-context'
7
+
8
+
9
+ const PROGRAMS_MISSING_INVOKE_LOG = new Set([
10
+ 'AddressLookupTab1e1111111111111111111111111',
11
+ 'BPFLoader1111111111111111111111111111111111',
12
+ 'BPFLoader2111111111111111111111111111111111',
13
+ 'BPFLoaderUpgradeab1e11111111111111111111111',
14
+ 'Ed25519SigVerify111111111111111111111111111',
15
+ 'KeccakSecp256k11111111111111111111111111111',
16
+ 'NativeLoader1111111111111111111111111111111',
17
+ 'ZkTokenProof1111111111111111111111111111111'
18
+ ])
19
+
20
+
21
+ export class ParsingError {
22
+ instructionIndex?: number
23
+ innerInstructionIndex?: number
24
+ logMessageIndex?: number
25
+
26
+ constructor(
27
+ public msg: string
28
+ ) {
29
+ }
30
+ }
31
+
32
+
33
+ export class MessageStream {
34
+ private messages: Message[]
35
+ private pos = 0
36
+
37
+ constructor(messages: string[]) {
38
+ this.messages = messages.map(parseLogMessage)
39
+ }
40
+
41
+ get unfinished(): boolean {
42
+ return !this.ended
43
+ }
44
+
45
+ get ended(): boolean {
46
+ return this.pos >= this.messages.length || this.messages[this.pos].kind == 'truncate'
47
+ }
48
+
49
+ get truncated(): boolean {
50
+ return this.pos < this.messages.length && this.messages[this.pos].kind == 'truncate'
51
+ }
52
+
53
+ get current(): Message {
54
+ assert(this.pos < this.messages.length, 'eof reached')
55
+ return this.messages[this.pos]
56
+ }
57
+
58
+ get position(): number {
59
+ return this.pos
60
+ }
61
+
62
+ advance(): boolean {
63
+ if (this.truncated) return false
64
+ this.pos = Math.min(this.pos + 1, this.messages.length)
65
+ return this.pos < this.messages.length
66
+ }
67
+ }
68
+
69
+
70
+ export class InstructionTreeTraversal {
71
+ private lastAddress: number[]
72
+ private instructions: rpc.Instruction[]
73
+ private pos = 0
74
+
75
+ constructor(
76
+ private tx: TransactionContext,
77
+ private messages: MessageStream,
78
+ private instructionIndex: number,
79
+ instruction: rpc.Instruction,
80
+ inner: rpc.Instruction[],
81
+ private output: Instruction[],
82
+ private log: LogMessage[]
83
+ ) {
84
+ this.lastAddress = [instructionIndex - 1]
85
+ this.instructions = [instruction, ...inner]
86
+ if (this.tx.erroredInstruction >= this.instructionIndex) {
87
+ this.call(1)
88
+ this.finishLogLess()
89
+ } else {
90
+ this.assert(
91
+ this.instructions.length === 1,
92
+ 'failed instructions should not have inner calls'
93
+ )
94
+ this.push(1)
95
+ }
96
+ }
97
+
98
+ private call(stackHeight: number): void {
99
+ let ins = this.current
100
+
101
+ this.assert(
102
+ ins.stackHeight == null || ins.stackHeight === stackHeight,
103
+ 'stack height mismatch',
104
+ this.pos
105
+ )
106
+
107
+ let programId = this.tx.getAccount(ins.programIdIndex)
108
+
109
+ if (
110
+ this.messages.unfinished &&
111
+ this.messages.current.kind === 'invoke' &&
112
+ this.messages.current.programId === programId
113
+ ) {
114
+ this.assert(
115
+ this.messages.current.stackHeight === stackHeight,
116
+ 'invoke message has unexpected stack height',
117
+ this.pos,
118
+ this.messages.position
119
+ )
120
+ this.messages.advance()
121
+ this.invoke(stackHeight)
122
+ return
123
+ }
124
+
125
+ if (this.messages.truncated) {
126
+ return
127
+ }
128
+
129
+ if (PROGRAMS_MISSING_INVOKE_LOG.has(programId)) {
130
+ this.push(stackHeight).hasDroppedLogMessages = true
131
+ this.dropNonInvokeMessages()
132
+ return
133
+ }
134
+
135
+ if (this.messages.ended) {
136
+ throw this.error('unexpected end of message log', this.pos)
137
+ }
138
+
139
+ throw this.error('missing invoke message', this.pos, this.messages.position)
140
+ }
141
+
142
+ private invoke(stackHeight: number): void {
143
+ let pos = this.pos
144
+ let ins = this.push(stackHeight)
145
+
146
+ this.takeInstructionMessages(ins, pos)
147
+
148
+ if (this.messages.truncated) {
149
+ ins.hasDroppedLogMessages = true
150
+ return
151
+ }
152
+
153
+ if (this.messages.ended) {
154
+ throw this.error('unexpected end of log', pos)
155
+ }
156
+
157
+ let result = this.messages.current
158
+ assert(result.kind === 'invoke-result')
159
+ this.assert(
160
+ result.programId === ins.programId,
161
+ 'invoke result message and instruction program ids don\'t match'
162
+ )
163
+ ins.error = result.error
164
+ this.messages.advance()
165
+
166
+ // consume invoke-less subcalls,
167
+ // that might have left unvisited due to missing 'invoke' messages
168
+ this.eatInvokeLessSubCalls(ins)
169
+ }
170
+
171
+ private eatInvokeLessSubCalls(parent: Instruction) {
172
+ while (this.unfinished) {
173
+ let stackHeight: number
174
+ if (this.current.stackHeight == null) {
175
+ if (parent.error) {
176
+ // all remaining calls must belong to the given parent
177
+ stackHeight = parent.instructionAddress.length + 1
178
+ } else {
179
+ stackHeight = 2
180
+ }
181
+ } else {
182
+ stackHeight = this.current.stackHeight
183
+ }
184
+
185
+ if (stackHeight <= parent.instructionAddress.length) return
186
+
187
+ let pos = this.pos
188
+ let ins = this.push(stackHeight)
189
+
190
+ // even if we have some messages emitted,
191
+ // we already assigned them to parent call
192
+ ins.hasDroppedLogMessages = true
193
+
194
+ if (PROGRAMS_MISSING_INVOKE_LOG.has(ins.programId)) {
195
+ // all good, it is expected to not have 'invoke' message
196
+ } else if (
197
+ this.tx.exceededCallDepth &&
198
+ this.tx.erroredInstruction == this.instructionIndex &&
199
+ this.ended
200
+ ) {
201
+ // we've reached the max stack depth,
202
+ // there will be no invoke message either
203
+ } else {
204
+ this.tx.error({
205
+ instruction: this.instructionIndex,
206
+ innerInstruction: pos - 1
207
+ }, 'missing invoke message for inner instruction')
208
+ }
209
+ }
210
+ }
211
+
212
+ private takeInstructionMessages(ins: Instruction, pos: number): void {
213
+ while (this.messages.unfinished) {
214
+ let msg = this.messages.current
215
+ switch(msg.kind) {
216
+ case 'log':
217
+ case 'data':
218
+ case 'other':
219
+ this.log.push({
220
+ transactionIndex: this.tx.transactionIndex,
221
+ logIndex: this.messages.position,
222
+ instructionAddress: ins.instructionAddress,
223
+ programId: ins.programId,
224
+ kind: msg.kind,
225
+ message: msg.message
226
+ })
227
+ this.messages.advance()
228
+ break
229
+ case 'cu':
230
+ if (ins.programId == msg.programId) {
231
+ ins.computeUnitsConsumed = msg.consumed
232
+ this.messages.advance()
233
+ } else {
234
+ throw this.error(
235
+ 'unexpected programId in compute unit message',
236
+ pos,
237
+ this.messages.position
238
+ )
239
+ }
240
+ break
241
+ case 'invoke':
242
+ this.call(ins.instructionAddress.length + 1)
243
+ break
244
+ case 'invoke-result':
245
+ case 'truncate':
246
+ return
247
+ default:
248
+ throw unexpectedCase()
249
+ }
250
+ }
251
+ }
252
+
253
+ private dropNonInvokeMessages(): void {
254
+ while (this.messages.unfinished) {
255
+ let msg = this.messages.current
256
+ switch(msg.kind) {
257
+ case 'log':
258
+ case 'data':
259
+ case 'cu':
260
+ case 'other':
261
+ this.messages.advance()
262
+ break
263
+ case 'invoke':
264
+ case 'invoke-result':
265
+ case 'truncate':
266
+ return
267
+ default:
268
+ throw unexpectedCase()
269
+ }
270
+ }
271
+ }
272
+
273
+ private finishLogLess(): void {
274
+ while (this.unfinished) {
275
+ this.push(this.current.stackHeight ?? 2).hasDroppedLogMessages = true
276
+ }
277
+ }
278
+
279
+ private get current(): rpc.Instruction {
280
+ assert(this.pos < this.instructions.length)
281
+ return this.instructions[this.pos]
282
+ }
283
+
284
+ private get unfinished(): boolean {
285
+ return this.pos < this.instructions.length
286
+ }
287
+
288
+ private get ended(): boolean {
289
+ return !this.unfinished
290
+ }
291
+
292
+ private assert(ok: any, msg: string, pos?: number, messagePos?: number): void {
293
+ if (!ok) throw this.error(msg, pos, messagePos)
294
+ }
295
+
296
+ private error(msg: string, pos?: number, messagePos?: number): ParsingError {
297
+ let err = new ParsingError(msg)
298
+ err.instructionIndex = this.instructionIndex
299
+ if (pos) {
300
+ err.innerInstructionIndex = pos - 1
301
+ }
302
+ if (messagePos != null) {
303
+ err.logMessageIndex = messagePos
304
+ }
305
+ return err
306
+ }
307
+
308
+ private push(stackHeight: number): Instruction {
309
+ assert(stackHeight > 0)
310
+
311
+ let ins = this.current
312
+ if (ins.stackHeight != null) {
313
+ assert(stackHeight === ins.stackHeight)
314
+ }
315
+
316
+ let address = this.lastAddress.slice()
317
+
318
+ while (address.length > stackHeight) {
319
+ address.pop()
320
+ }
321
+
322
+ if (address.length === stackHeight) {
323
+ address[stackHeight - 1] += 1
324
+ } else {
325
+ assert(address.length + 1 == stackHeight)
326
+ address[stackHeight - 1] = 0
327
+ }
328
+
329
+ let mapped: Instruction = {
330
+ transactionIndex: this.tx.transactionIndex,
331
+ instructionAddress: address,
332
+ programId: this.tx.getAccount(ins.programIdIndex),
333
+ accounts: ins.accounts.map(a => this.tx.getAccount(a)),
334
+ data: ins.data,
335
+ isCommitted: this.tx.isCommitted,
336
+ hasDroppedLogMessages: false
337
+ }
338
+
339
+ this.output.push(mapped)
340
+ this.lastAddress = address
341
+ this.pos = Math.min(this.pos + 1, this.instructions.length)
342
+ return mapped
343
+ }
344
+ }