@subsquid/solana-normalization 0.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.
- package/lib/data.d.ts +124 -0
- package/lib/data.d.ts.map +1 -0
- package/lib/data.js +3 -0
- package/lib/data.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +19 -0
- package/lib/index.js.map +1 -0
- package/lib/log-parser.d.ts +55 -0
- package/lib/log-parser.d.ts.map +1 -0
- package/lib/log-parser.js +125 -0
- package/lib/log-parser.js.map +1 -0
- package/lib/mapping.d.ts +4 -0
- package/lib/mapping.d.ts.map +1 -0
- package/lib/mapping.js +424 -0
- package/lib/mapping.js.map +1 -0
- package/package.json +27 -0
- package/src/data.ts +154 -0
- package/src/index.ts +2 -0
- package/src/log-parser.ts +146 -0
- package/src/mapping.ts +535 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type {Base58Bytes} from '@subsquid/solana-rpc-data'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export class InvokeMessage {
|
|
5
|
+
public kind = 'invoke' as const
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
public programId: Base58Bytes,
|
|
9
|
+
public stackHeight: number
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
static parse(msg: string): InvokeMessage | undefined {
|
|
13
|
+
let m = /^Program ([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+) invoke \[(\d+)]$/.exec(msg)
|
|
14
|
+
if (m) return new InvokeMessage(m[1], parseInt(m[2]))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
toString(): string {
|
|
18
|
+
return `Program ${this.programId} invoke [${this.stackHeight}]`
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export class InvokeResultMessage {
|
|
24
|
+
public kind = 'invoke-result' as const
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
public programId: Base58Bytes,
|
|
28
|
+
public success: boolean,
|
|
29
|
+
public error?: string
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
static parse(msg: string): InvokeResultMessage | undefined {
|
|
33
|
+
let m = /^Program ([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+) success$/.exec(msg)
|
|
34
|
+
if (m) return new InvokeResultMessage(m[1], true)
|
|
35
|
+
|
|
36
|
+
m = /^Program ([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+) failed: (.*)$/.exec(msg)
|
|
37
|
+
if (m) return new InvokeResultMessage(m[1], false, m[2])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
toString(): string {
|
|
41
|
+
if (this.success) {
|
|
42
|
+
return `Program ${this.programId} success`
|
|
43
|
+
} else {
|
|
44
|
+
return `Program ${this.programId} failed: ${this.error}`
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
export class ProgramLogMessage {
|
|
51
|
+
public kind = 'log' as const
|
|
52
|
+
|
|
53
|
+
constructor(public message: string) {}
|
|
54
|
+
|
|
55
|
+
static parse(msg: string): ProgramLogMessage | undefined {
|
|
56
|
+
let m = /^Program log: (.*)$/.exec(msg)
|
|
57
|
+
if (m) return new ProgramLogMessage(m[1])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
toString(): string {
|
|
61
|
+
return `Program log: ${this.message}`
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
export class ComputeUnitsMessage {
|
|
67
|
+
public kind = 'cu' as const
|
|
68
|
+
|
|
69
|
+
constructor(
|
|
70
|
+
public programId: Base58Bytes,
|
|
71
|
+
public consumed: bigint,
|
|
72
|
+
public available: bigint
|
|
73
|
+
) {}
|
|
74
|
+
|
|
75
|
+
static parse(msg: string): ComputeUnitsMessage | undefined {
|
|
76
|
+
let m = /^Program ([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+) consumed (\d+) of (\d+) compute units$/.exec(msg)
|
|
77
|
+
if (m) return new ComputeUnitsMessage(m[1], BigInt(m[2]), BigInt(m[3]))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
toString(): string {
|
|
81
|
+
return `Program ${this.programId} consumed ${this.consumed} of ${this.available} compute units`
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
export class ProgramDataMessage {
|
|
87
|
+
public kind = 'data' as const
|
|
88
|
+
|
|
89
|
+
constructor(
|
|
90
|
+
public message: string
|
|
91
|
+
) {}
|
|
92
|
+
|
|
93
|
+
static parse(msg: string): ProgramDataMessage | undefined {
|
|
94
|
+
let m = /^Program data: (.*)$/.exec(msg)
|
|
95
|
+
if (m) return new ProgramDataMessage(m[1])
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
toString(): string {
|
|
99
|
+
return `Program data: ${this.message}`
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
export class LogTruncatedMessage {
|
|
105
|
+
public kind = 'truncate' as const
|
|
106
|
+
|
|
107
|
+
static parse(msg: string): LogTruncatedMessage | undefined {
|
|
108
|
+
if (msg == 'Log truncated') return new LogTruncatedMessage()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
toString(): string {
|
|
112
|
+
return 'Log truncated'
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
export class OtherMessage {
|
|
118
|
+
public kind = 'other' as const
|
|
119
|
+
|
|
120
|
+
constructor(public message: string) {}
|
|
121
|
+
|
|
122
|
+
toString(): string {
|
|
123
|
+
return this.message
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
export type Message =
|
|
129
|
+
InvokeMessage |
|
|
130
|
+
InvokeResultMessage |
|
|
131
|
+
ProgramLogMessage |
|
|
132
|
+
ProgramDataMessage |
|
|
133
|
+
ComputeUnitsMessage |
|
|
134
|
+
LogTruncatedMessage |
|
|
135
|
+
OtherMessage
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
export function parseLogMessage(msg: string): Message {
|
|
139
|
+
return InvokeMessage.parse(msg)
|
|
140
|
+
|| InvokeResultMessage.parse(msg)
|
|
141
|
+
|| ProgramLogMessage.parse(msg)
|
|
142
|
+
|| ProgramDataMessage.parse(msg)
|
|
143
|
+
|| ComputeUnitsMessage.parse(msg)
|
|
144
|
+
|| LogTruncatedMessage.parse(msg)
|
|
145
|
+
|| new OtherMessage(msg)
|
|
146
|
+
}
|
package/src/mapping.ts
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import type * as rpc from '@subsquid/solana-rpc-data'
|
|
2
|
+
import type {Base58Bytes} from '@subsquid/solana-rpc-data'
|
|
3
|
+
import {addErrorContext, unexpectedCase} from '@subsquid/util-internal'
|
|
4
|
+
import assert from 'assert'
|
|
5
|
+
import {Balance, Block, BlockHeader, Instruction, LogMessage, Reward, TokenBalance, Transaction} from './data'
|
|
6
|
+
import {InvokeMessage, InvokeResultMessage, LogTruncatedMessage, Message, parseLogMessage} from './log-parser'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export function mapRpcBlock(src: rpc.Block): Block {
|
|
10
|
+
let header: BlockHeader = {
|
|
11
|
+
hash: src.hash,
|
|
12
|
+
height: src.height,
|
|
13
|
+
slot: src.slot,
|
|
14
|
+
parentSlot: src.block.parentSlot,
|
|
15
|
+
parentHash: src.block.previousBlockhash,
|
|
16
|
+
timestamp: src.block.blockTime ?? 0
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let instructions: Instruction[] = []
|
|
20
|
+
let logs: LogMessage[] = []
|
|
21
|
+
let balances: Balance[] = []
|
|
22
|
+
let tokenBalances: TokenBalance[] = []
|
|
23
|
+
|
|
24
|
+
let transactions = src.block.transactions
|
|
25
|
+
?.map((tx, i) => {
|
|
26
|
+
try {
|
|
27
|
+
return mapRpcTransaction(i, tx, instructions, logs, balances, tokenBalances)
|
|
28
|
+
} catch(err: any) {
|
|
29
|
+
throw addErrorContext(err, {
|
|
30
|
+
blockTransaction: tx.transaction.signatures[0]
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
}) ?? []
|
|
34
|
+
|
|
35
|
+
let rewards = src.block.rewards?.map(s => {
|
|
36
|
+
let reward: Reward = {
|
|
37
|
+
pubkey: s.pubkey,
|
|
38
|
+
lamports: BigInt(s.lamports),
|
|
39
|
+
postBalance: BigInt(s.postBalance)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (s.rewardType) {
|
|
43
|
+
reward.rewardType = s.rewardType
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (s.commission != null) {
|
|
47
|
+
reward.commission = s.commission
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return reward
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
header,
|
|
55
|
+
transactions,
|
|
56
|
+
instructions,
|
|
57
|
+
logs,
|
|
58
|
+
balances,
|
|
59
|
+
tokenBalances,
|
|
60
|
+
rewards: rewards || []
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
function mapRpcTransaction(
|
|
66
|
+
transactionIndex: number,
|
|
67
|
+
src: rpc.Transaction,
|
|
68
|
+
instructions: Instruction[],
|
|
69
|
+
logs: LogMessage[],
|
|
70
|
+
balances: Balance[],
|
|
71
|
+
tokenBalances: TokenBalance[]
|
|
72
|
+
): Transaction {
|
|
73
|
+
let tx: Transaction = {
|
|
74
|
+
transactionIndex,
|
|
75
|
+
version: src.version,
|
|
76
|
+
accountKeys: src.transaction.message.accountKeys,
|
|
77
|
+
addressTableLookups: src.transaction.message.addressTableLookups ?? [],
|
|
78
|
+
numReadonlySignedAccounts: src.transaction.message.header.numReadonlySignedAccounts,
|
|
79
|
+
numReadonlyUnsignedAccounts: src.transaction.message.header.numReadonlyUnsignedAccounts,
|
|
80
|
+
numRequiredSignatures: src.transaction.message.header.numRequiredSignatures,
|
|
81
|
+
recentBlockhash: src.transaction.message.recentBlockhash,
|
|
82
|
+
signatures: src.transaction.signatures,
|
|
83
|
+
err: src.meta.err,
|
|
84
|
+
computeUnitsConsumed: BigInt(src.meta.computeUnitsConsumed ?? 0),
|
|
85
|
+
fee: BigInt(src.meta.fee),
|
|
86
|
+
loadedAddresses: src.meta.loadedAddresses ?? {readonly: [], writable: []},
|
|
87
|
+
hasDroppedLogMessages: false
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let accounts: Base58Bytes[]
|
|
91
|
+
if (tx.version === 'legacy') {
|
|
92
|
+
accounts = tx.accountKeys
|
|
93
|
+
} else {
|
|
94
|
+
assert(src.meta?.loadedAddresses)
|
|
95
|
+
accounts = tx.accountKeys.concat(
|
|
96
|
+
src.meta.loadedAddresses.writable,
|
|
97
|
+
src.meta.loadedAddresses.readonly
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let getAccount = (index: number): Base58Bytes => {
|
|
102
|
+
assert(index < accounts.length)
|
|
103
|
+
return accounts[index]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
new InstructionParser(
|
|
107
|
+
getAccount,
|
|
108
|
+
tx,
|
|
109
|
+
src,
|
|
110
|
+
src.meta.logMessages?.map(parseLogMessage),
|
|
111
|
+
instructions,
|
|
112
|
+
logs
|
|
113
|
+
).parse()
|
|
114
|
+
|
|
115
|
+
balances.push(
|
|
116
|
+
...mapBalances(getAccount, transactionIndex, src)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
tokenBalances.push(
|
|
120
|
+
...mapTokenBalances(getAccount, transactionIndex, src)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return tx
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
const PROGRAMS_MISSING_INVOKE_LOG = new Set([
|
|
128
|
+
'AddressLookupTab1e1111111111111111111111111',
|
|
129
|
+
'BPFLoader1111111111111111111111111111111111',
|
|
130
|
+
'BPFLoader2111111111111111111111111111111111',
|
|
131
|
+
'BPFLoaderUpgradeab1e11111111111111111111111',
|
|
132
|
+
'Ed25519SigVerify111111111111111111111111111',
|
|
133
|
+
'KeccakSecp256k11111111111111111111111111111',
|
|
134
|
+
'NativeLoader1111111111111111111111111111111',
|
|
135
|
+
'ZkTokenProof1111111111111111111111111111111',
|
|
136
|
+
])
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class InstructionParser {
|
|
140
|
+
private pos = 0
|
|
141
|
+
private messages: Message[]
|
|
142
|
+
private messagePos = 0
|
|
143
|
+
private messagesTruncated = false
|
|
144
|
+
private errorPos?: number
|
|
145
|
+
private lastAddress: number[] = []
|
|
146
|
+
|
|
147
|
+
constructor(
|
|
148
|
+
private getAccount: (index: number) => Base58Bytes,
|
|
149
|
+
private tx: Transaction,
|
|
150
|
+
private src: rpc.Transaction,
|
|
151
|
+
messages: Message[] | undefined,
|
|
152
|
+
private instructions: Instruction[],
|
|
153
|
+
private logs: LogMessage[]
|
|
154
|
+
) {
|
|
155
|
+
if (messages == null) {
|
|
156
|
+
this.messages = []
|
|
157
|
+
this.messagesTruncated = true
|
|
158
|
+
} else {
|
|
159
|
+
this.messages = messages
|
|
160
|
+
}
|
|
161
|
+
let err: any = this.src.meta.err
|
|
162
|
+
if (err) {
|
|
163
|
+
if ('InstructionError' in err) {
|
|
164
|
+
let pos = err['InstructionError'][0]
|
|
165
|
+
assert(typeof pos == 'number')
|
|
166
|
+
assert(0 <= pos && pos < this.src.transaction.message.instructions.length)
|
|
167
|
+
this.errorPos = pos
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
parse(): void {
|
|
173
|
+
while (this.pos < this.src.transaction.message.instructions.length) {
|
|
174
|
+
let instruction = this.src.transaction.message.instructions[this.pos]
|
|
175
|
+
|
|
176
|
+
let inner = this.src.meta.innerInstructions
|
|
177
|
+
?.filter(i => i.index === this.pos)
|
|
178
|
+
.flatMap(i => i.instructions) ?? []
|
|
179
|
+
|
|
180
|
+
if (this.errorPos == null || this.errorPos >= this.pos) {
|
|
181
|
+
let instructions = [instruction].concat(inner)
|
|
182
|
+
let end = this.traverse(instructions, 0, 1)
|
|
183
|
+
assert(end == instructions.length)
|
|
184
|
+
} else {
|
|
185
|
+
this.assert(inner.length == 0, false, 0, 'seemingly non-executed instruction has inner instructions')
|
|
186
|
+
this.push(1, instruction)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.pos += 1
|
|
190
|
+
}
|
|
191
|
+
this.tx.hasDroppedLogMessages = this.tx.hasDroppedLogMessages || this.messagesTruncated
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private traverse(
|
|
195
|
+
instructions: rpc.Instruction[],
|
|
196
|
+
pos: number,
|
|
197
|
+
stackHeight: number
|
|
198
|
+
): number {
|
|
199
|
+
this.assert(pos < instructions.length, true, 0, 'unexpected and of inner instructions')
|
|
200
|
+
|
|
201
|
+
let instruction = instructions[pos]
|
|
202
|
+
|
|
203
|
+
this.assert(
|
|
204
|
+
instruction.stackHeight == null || instruction.stackHeight == stackHeight,
|
|
205
|
+
false, pos, 'instruction has unexpected stack height'
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
let msg: Message | undefined
|
|
209
|
+
if (this.messagePos < this.messages.length) {
|
|
210
|
+
msg = this.messages[this.messagePos]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (msg?.kind == 'truncate') {
|
|
214
|
+
this.messagePos += 1
|
|
215
|
+
this.messagesTruncated = true
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (this.messagesTruncated) {
|
|
219
|
+
this.push(stackHeight, instruction)
|
|
220
|
+
return this.logLessTraversal(stackHeight, instructions, pos + 1)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let programId = this.getAccount(instruction.programIdIndex)
|
|
224
|
+
|
|
225
|
+
if (msg?.kind === 'invoke' && msg.programId === programId) {
|
|
226
|
+
this.assert(msg.stackHeight == stackHeight, true, pos, 'invoke message has unexpected stack height')
|
|
227
|
+
this.messagePos += 1
|
|
228
|
+
return this.invokeInstruction(stackHeight, instructions, pos)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (PROGRAMS_MISSING_INVOKE_LOG.has(programId)) {
|
|
232
|
+
let dropped = this.dropInvokeLessInstructionMessages(pos)
|
|
233
|
+
let ins = this.push(stackHeight, instruction)
|
|
234
|
+
ins.hasDroppedLogMessages = ins.hasDroppedLogMessages || dropped
|
|
235
|
+
this.tx.hasDroppedLogMessages = this.tx.hasDroppedLogMessages || dropped
|
|
236
|
+
return this.invokeLessTraversal(dropped, stackHeight, instructions, pos + 1)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// FIXME: add an option to ignore this
|
|
240
|
+
throw this.error(true, pos, 'missing invoke message')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private dropInvokeLessInstructionMessages(pos: number): boolean {
|
|
244
|
+
let initialPos = this.messagePos
|
|
245
|
+
while (this.messagePos < this.messages.length && !this.messagesTruncated) {
|
|
246
|
+
let msg = this.messages[this.messagePos]
|
|
247
|
+
switch(msg.kind) {
|
|
248
|
+
case 'log':
|
|
249
|
+
case 'data':
|
|
250
|
+
case 'cu':
|
|
251
|
+
case 'other':
|
|
252
|
+
this.messagePos += 1
|
|
253
|
+
break
|
|
254
|
+
case 'truncate':
|
|
255
|
+
this.messagePos += 1
|
|
256
|
+
this.messagesTruncated = true
|
|
257
|
+
return true
|
|
258
|
+
case 'invoke':
|
|
259
|
+
return this.messagePos - initialPos > 0
|
|
260
|
+
case 'invoke-result':
|
|
261
|
+
throw this.error(true, pos, `invoke result message does not match any invoke`)
|
|
262
|
+
default:
|
|
263
|
+
throw unexpectedCase()
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return false
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private invokeInstruction(
|
|
270
|
+
stackHeight: number,
|
|
271
|
+
instructions: rpc.Instruction[],
|
|
272
|
+
instructionPos: number
|
|
273
|
+
): number {
|
|
274
|
+
let ins = this.push(stackHeight, instructions[instructionPos])
|
|
275
|
+
let pos = instructionPos + 1
|
|
276
|
+
while (true) {
|
|
277
|
+
let token = this.takeInstructionMessages(ins, instructionPos)
|
|
278
|
+
switch(token.kind) {
|
|
279
|
+
case 'invoke':
|
|
280
|
+
pos = this.traverse(instructions, pos, stackHeight + 1)
|
|
281
|
+
break
|
|
282
|
+
case 'invoke-result':
|
|
283
|
+
if (token.programId != ins.programId) {
|
|
284
|
+
throw this.error(true, instructionPos,
|
|
285
|
+
`invoke result message and instruction program ids don't match`
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
if (token.error) {
|
|
289
|
+
ins.error = token.error
|
|
290
|
+
}
|
|
291
|
+
pos = this.invokeLessTraversal(true, stackHeight, instructions, pos)
|
|
292
|
+
this.messagePos += 1
|
|
293
|
+
return pos
|
|
294
|
+
case 'truncate':
|
|
295
|
+
ins.hasDroppedLogMessages = true
|
|
296
|
+
return this.logLessTraversal(stackHeight, instructions, pos)
|
|
297
|
+
default:
|
|
298
|
+
throw unexpectedCase()
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private takeInstructionMessages(
|
|
304
|
+
ins: Instruction,
|
|
305
|
+
pos: number
|
|
306
|
+
): InvokeMessage | InvokeResultMessage | LogTruncatedMessage {
|
|
307
|
+
if (this.messagesTruncated) return new LogTruncatedMessage()
|
|
308
|
+
while (this.messagePos < this.messages.length) {
|
|
309
|
+
let msg = this.messages[this.messagePos]
|
|
310
|
+
switch(msg.kind) {
|
|
311
|
+
case 'log':
|
|
312
|
+
case 'data':
|
|
313
|
+
case 'other':
|
|
314
|
+
this.logs.push({
|
|
315
|
+
transactionIndex: ins.transactionIndex,
|
|
316
|
+
logIndex: this.messagePos,
|
|
317
|
+
instructionAddress: ins.instructionAddress,
|
|
318
|
+
programId: ins.programId,
|
|
319
|
+
kind: msg.kind,
|
|
320
|
+
message: msg.message
|
|
321
|
+
})
|
|
322
|
+
break
|
|
323
|
+
case 'cu':
|
|
324
|
+
if (ins.programId != msg.programId) {
|
|
325
|
+
throw this.error(true, pos, 'unexpected programId in compute unit message')
|
|
326
|
+
}
|
|
327
|
+
ins.computeUnitsConsumed = msg.consumed
|
|
328
|
+
break
|
|
329
|
+
case 'invoke':
|
|
330
|
+
case 'invoke-result':
|
|
331
|
+
return msg
|
|
332
|
+
case 'truncate':
|
|
333
|
+
this.messagesTruncated = true
|
|
334
|
+
this.messagePos += 1
|
|
335
|
+
return msg
|
|
336
|
+
default:
|
|
337
|
+
throw unexpectedCase()
|
|
338
|
+
}
|
|
339
|
+
this.messagePos += 1
|
|
340
|
+
}
|
|
341
|
+
throw this.error(false, pos, 'unexpected end of log messages')
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private invokeLessTraversal(
|
|
345
|
+
messagesDropped: boolean,
|
|
346
|
+
parentStackHeight: number,
|
|
347
|
+
instructions: rpc.Instruction[],
|
|
348
|
+
pos: number
|
|
349
|
+
): number {
|
|
350
|
+
return this.logLessTraversal(parentStackHeight, instructions, pos, (ins, pos) => {
|
|
351
|
+
ins.hasDroppedLogMessages = ins.hasDroppedLogMessages || messagesDropped
|
|
352
|
+
if (PROGRAMS_MISSING_INVOKE_LOG.has(ins.programId)) {
|
|
353
|
+
} else {
|
|
354
|
+
throw this.error(false, pos, 'invoke message is missing')
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private logLessTraversal(
|
|
360
|
+
parentStackHeight: number,
|
|
361
|
+
instructions: rpc.Instruction[],
|
|
362
|
+
pos: number,
|
|
363
|
+
cb?: (ins: Instruction, pos: number) => void
|
|
364
|
+
): number {
|
|
365
|
+
while (pos < instructions.length) {
|
|
366
|
+
let instruction = instructions[pos]
|
|
367
|
+
let stackHeight = instruction.stackHeight ?? 2
|
|
368
|
+
if (stackHeight > parentStackHeight) {
|
|
369
|
+
let ins = this.push(stackHeight, instruction)
|
|
370
|
+
cb?.(ins, pos)
|
|
371
|
+
pos += 1
|
|
372
|
+
} else {
|
|
373
|
+
return pos
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return pos
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private push(stackHeight: number, src: rpc.Instruction): Instruction {
|
|
380
|
+
assert(stackHeight > 0)
|
|
381
|
+
|
|
382
|
+
if (src.stackHeight != null) {
|
|
383
|
+
assert(stackHeight === src.stackHeight)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let address = this.lastAddress.slice()
|
|
387
|
+
|
|
388
|
+
while (address.length > stackHeight) {
|
|
389
|
+
address.pop()
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (address.length === stackHeight) {
|
|
393
|
+
address[stackHeight - 1] += 1
|
|
394
|
+
} else {
|
|
395
|
+
assert(address.length + 1 == stackHeight)
|
|
396
|
+
address[stackHeight - 1] = 0
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
let i: Instruction = {
|
|
400
|
+
transactionIndex: this.tx.transactionIndex,
|
|
401
|
+
instructionAddress: address,
|
|
402
|
+
programId: this.getAccount(src.programIdIndex),
|
|
403
|
+
accounts: src.accounts.map(a => this.getAccount(a)),
|
|
404
|
+
data: src.data,
|
|
405
|
+
isCommitted: !this.tx.err,
|
|
406
|
+
hasDroppedLogMessages: this.messagesTruncated
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
this.instructions.push(i)
|
|
410
|
+
this.lastAddress = address
|
|
411
|
+
return i
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private assert(ok: unknown, withMessagePos: boolean, innerPos: number, msg: string): asserts ok {
|
|
415
|
+
if (!ok) throw this.error(withMessagePos, innerPos, msg)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private error(withMessagePos: boolean, innerPos: number, msg: string): Error {
|
|
419
|
+
let loc = `stopped at instruction ${this.pos}`
|
|
420
|
+
if (innerPos > 0) {
|
|
421
|
+
loc += `, inner instruction ${innerPos - 1})`
|
|
422
|
+
}
|
|
423
|
+
if (withMessagePos && this.messagePos < this.messages.length) {
|
|
424
|
+
loc += ` and log message ${this.messagePos}`
|
|
425
|
+
}
|
|
426
|
+
return new Error(
|
|
427
|
+
`Failed to process transaction ${this.tx.signatures[0]}: ${loc}: ${msg}`
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
function mapBalances(
|
|
434
|
+
getAccount: (idx: number) => Base58Bytes,
|
|
435
|
+
transactionIndex: number,
|
|
436
|
+
tx: rpc.Transaction,
|
|
437
|
+
): Balance[] {
|
|
438
|
+
let balances: Balance[] = []
|
|
439
|
+
|
|
440
|
+
let pre = tx.meta.preBalances
|
|
441
|
+
let post = tx.meta.postBalances
|
|
442
|
+
|
|
443
|
+
assert(pre.length == post.length)
|
|
444
|
+
|
|
445
|
+
for (let i = 0; i < pre.length; i++) {
|
|
446
|
+
if (pre[i] === post[i]) {
|
|
447
|
+
// nothing changed, don't create an entry
|
|
448
|
+
} else {
|
|
449
|
+
balances.push({
|
|
450
|
+
transactionIndex,
|
|
451
|
+
account: getAccount(i),
|
|
452
|
+
pre: BigInt(pre[i]),
|
|
453
|
+
post: BigInt(post[i])
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
balances.sort((a, b) => {
|
|
459
|
+
if (a.account < b.account) return -1
|
|
460
|
+
if (a.account > b.account) return 1
|
|
461
|
+
return 0
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
return balances
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
function mapTokenBalances(
|
|
469
|
+
getAccount: (idx: number) => Base58Bytes,
|
|
470
|
+
transactionIndex: number,
|
|
471
|
+
tx: rpc.Transaction
|
|
472
|
+
): TokenBalance[] {
|
|
473
|
+
let balances: TokenBalance[] = []
|
|
474
|
+
|
|
475
|
+
let preBalances = new Map(
|
|
476
|
+
tx.meta.preTokenBalances?.map(b => [getAccount(b.accountIndex), b])
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
let postBalances = new Map(
|
|
480
|
+
tx.meta.postTokenBalances?.map(b => [getAccount(b.accountIndex), b])
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
for (let [account, post] of postBalances.entries()) {
|
|
484
|
+
let pre = preBalances.get(account)
|
|
485
|
+
if (pre) {
|
|
486
|
+
balances.push({
|
|
487
|
+
transactionIndex,
|
|
488
|
+
account,
|
|
489
|
+
|
|
490
|
+
preProgramId: pre.programId ?? undefined,
|
|
491
|
+
preMint: pre.mint,
|
|
492
|
+
preDecimals: pre.uiTokenAmount.decimals,
|
|
493
|
+
preOwner: pre.owner ?? undefined,
|
|
494
|
+
preAmount: BigInt(pre.uiTokenAmount.amount),
|
|
495
|
+
|
|
496
|
+
postProgramId: post.programId ?? undefined,
|
|
497
|
+
postMint: post.mint,
|
|
498
|
+
postDecimals: post.uiTokenAmount.decimals,
|
|
499
|
+
postOwner: post.owner ?? undefined,
|
|
500
|
+
postAmount: BigInt(post.uiTokenAmount.amount)
|
|
501
|
+
})
|
|
502
|
+
} else {
|
|
503
|
+
balances.push({
|
|
504
|
+
transactionIndex,
|
|
505
|
+
account,
|
|
506
|
+
postProgramId: post.programId ?? undefined,
|
|
507
|
+
postMint: post.mint,
|
|
508
|
+
postDecimals: post.uiTokenAmount.decimals,
|
|
509
|
+
postOwner: post.owner ?? undefined,
|
|
510
|
+
postAmount: BigInt(post.uiTokenAmount.amount)
|
|
511
|
+
})
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
for (let [account, pre] of preBalances.entries()) {
|
|
516
|
+
if (postBalances.has(account)) continue
|
|
517
|
+
balances.push({
|
|
518
|
+
transactionIndex,
|
|
519
|
+
account,
|
|
520
|
+
preProgramId: pre.programId ?? undefined,
|
|
521
|
+
preMint: pre.mint,
|
|
522
|
+
preDecimals: pre.uiTokenAmount.decimals,
|
|
523
|
+
preOwner: pre.owner ?? undefined,
|
|
524
|
+
preAmount: BigInt(pre.uiTokenAmount.amount)
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
balances.sort((a, b) => {
|
|
529
|
+
if (a.account < b.account) return -1
|
|
530
|
+
if (a.account > b.account) return 1
|
|
531
|
+
return 0
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
return balances
|
|
535
|
+
}
|