Skip to content

Commit 4e1f214

Browse files
authored
sdk: add parseLogsForCuUsage (#1953)
* sdk: add parseLogsForCuUsage * linter
1 parent 2c02710 commit 4e1f214

File tree

4 files changed

+278
-1
lines changed

4 files changed

+278
-1
lines changed

sdk/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"test:bignum": "mocha -r ts-node/register tests/bn/**/*.ts",
2121
"test:ci": "mocha -r ts-node/register tests/ci/**/*.ts",
2222
"test:dlob": "mocha -r ts-node/register tests/dlob/**/*.ts",
23+
"test:events": "mocha -r ts-node/register tests/events/**/*.ts",
2324
"patch-and-pub": "npm version patch --force && npm publish",
2425
"prettify": "prettier --check './src/***/*.ts'",
2526
"prettify:fix": "prettier --write './{src,tests}/***/*.ts'",

sdk/src/events/parse.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { Program, Event } from '@coral-xyz/anchor';
2+
import { CuUsageEvent } from './types';
23

34
const driftProgramId = 'dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH';
45
const PROGRAM_LOG = 'Program log: ';
6+
const PROGRAM_INSTRUCTION = 'Program log: Instruction: ';
57
const PROGRAM_DATA = 'Program data: ';
68
const PROGRAM_LOG_START_INDEX = PROGRAM_LOG.length;
79
const PROGRAM_DATA_START_INDEX = PROGRAM_DATA.length;
10+
const PROGRAM_INSTRUCTION_START_INDEX = PROGRAM_INSTRUCTION.length;
811

912
export function parseLogs(
1013
program: Program,
@@ -112,6 +115,7 @@ function handleSystemLog(
112115
// executing for a given log.
113116
class ExecutionContext {
114117
stack: string[] = [];
118+
ixStack: string[] = [];
115119

116120
program(): string {
117121
if (!this.stack.length) {
@@ -130,4 +134,115 @@ class ExecutionContext {
130134
}
131135
this.stack.pop();
132136
}
137+
138+
ix(): string {
139+
if (!this.ixStack.length) {
140+
throw new Error('Expected the ix stack to have elements');
141+
}
142+
return this.ixStack[this.ixStack.length - 1];
143+
}
144+
145+
pushIx(newIx: string) {
146+
this.ixStack.push(newIx);
147+
}
148+
149+
popIx() {
150+
this.ixStack.pop();
151+
}
152+
}
153+
154+
export function parseLogsForCuUsage(
155+
logs: string[],
156+
programId = driftProgramId
157+
): Event<CuUsageEvent>[] {
158+
const cuUsageEvents: Event<CuUsageEvent>[] = [];
159+
160+
const execution = new ExecutionContext();
161+
for (const log of logs) {
162+
if (log.startsWith('Log truncated')) {
163+
break;
164+
}
165+
166+
const [newProgram, newIx, didPopProgram, didPopIx] = handleLogForCuUsage(
167+
execution,
168+
log,
169+
programId
170+
);
171+
if (newProgram) {
172+
execution.push(newProgram);
173+
}
174+
if (newIx) {
175+
execution.pushIx(newIx);
176+
}
177+
if (didPopProgram) {
178+
execution.pop();
179+
}
180+
if (didPopIx !== null) {
181+
cuUsageEvents.push({
182+
name: 'CuUsage',
183+
data: {
184+
instruction: execution.ix(),
185+
cuUsage: didPopIx!,
186+
},
187+
} as any);
188+
execution.popIx();
189+
}
190+
}
191+
return cuUsageEvents;
192+
}
193+
194+
function handleLogForCuUsage(
195+
execution: ExecutionContext,
196+
log: string,
197+
programId = driftProgramId
198+
): [string | null, string | null, boolean, number | null] {
199+
if (execution.stack.length > 0 && execution.program() === programId) {
200+
return handleProgramLogForCuUsage(log, programId);
201+
} else {
202+
return handleSystemLogForCuUsage(log, programId);
203+
}
204+
}
205+
206+
function handleProgramLogForCuUsage(
207+
log: string,
208+
programId = driftProgramId
209+
): [string | null, string | null, boolean, number | null] {
210+
if (log.startsWith(PROGRAM_INSTRUCTION)) {
211+
const ixStr = log.slice(PROGRAM_INSTRUCTION_START_INDEX);
212+
return [null, ixStr, false, null];
213+
} else {
214+
return handleSystemLogForCuUsage(log, programId);
215+
}
216+
}
217+
218+
function handleSystemLogForCuUsage(
219+
log: string,
220+
programId = driftProgramId
221+
): [string | null, string | null, boolean, number | null] {
222+
// System component.
223+
const logStart = log.split(':')[0];
224+
const programStart = `Program ${programId} invoke`;
225+
226+
// Did the program finish executing?
227+
if (logStart.match(/^Program (.*) success/g) !== null) {
228+
return [null, null, true, null];
229+
// Recursive call.
230+
} else if (logStart.startsWith(programStart)) {
231+
return [programId, null, false, null];
232+
// Consumed CU log.
233+
} else if (log.startsWith(`Program ${programId} consumed `)) {
234+
// Extract CU usage, e.g. 'Program ... consumed 29242 of 199700 compute units'
235+
// We need to extract the consumed value (29242)
236+
const matches = log.match(/consumed (\d+) of \d+ compute units/);
237+
if (matches) {
238+
return [null, null, false, Number(matches[1])];
239+
}
240+
return [null, null, false, null];
241+
}
242+
// CPI call.
243+
else if (logStart.includes('invoke')) {
244+
return ['cpi', null, false, null]; // Any string will do.
245+
} else {
246+
return [null, null, false, null];
247+
}
133248
}

sdk/src/events/types.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ export type DriftEvent =
149149
| Event<LPSettleRecord>
150150
| Event<LPMintRedeemRecord>
151151
| Event<LPSwapRecord>
152-
| Event<LPBorrowLendDepositRecord>;
152+
| Event<LPBorrowLendDepositRecord>
153+
| Event<CuUsage>;
153154

154155
export interface EventSubscriberEvents {
155156
newEvent: (event: WrappedEvent<EventType>) => void;
@@ -213,3 +214,24 @@ export type LogProviderConfig =
213214
| WebSocketLogProviderConfig
214215
| PollingLogProviderConfig
215216
| EventsServerLogProviderConfig;
217+
218+
export type CuUsageEvent = {
219+
name: 'CuUsage';
220+
fields: [
221+
{
222+
name: 'instruction';
223+
type: 'string';
224+
index: false;
225+
},
226+
{
227+
name: 'cuUsage';
228+
type: 'u32';
229+
index: false;
230+
},
231+
];
232+
};
233+
234+
export type CuUsage = {
235+
instruction: string;
236+
cuUsage: number;
237+
};
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { expect } from 'chai';
2+
import { parseLogsForCuUsage } from '../../src/events/parse';
3+
4+
// if you used the '@types/mocha' method to install mocha type definitions, uncomment the following line
5+
// import 'mocha';
6+
7+
describe('parseLogsForCuUsage Tests', () => {
8+
it('can parse single ix', () => {
9+
const logs = [
10+
'Program ComputeBudget111111111111111111111111111111 invoke [1]',
11+
'Program ComputeBudget111111111111111111111111111111 success',
12+
'Program ComputeBudget111111111111111111111111111111 invoke [1]',
13+
'Program ComputeBudget111111111111111111111111111111 success',
14+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH invoke [1]',
15+
'Program log: Instruction: UpdateFundingRate',
16+
'Program log: correcting mark twap update (oracle previously invalid for 1625 seconds)',
17+
'Program data: Ze4o5EYuPXWjSrNoAAAAADkUAAAAAAAAZTIKAAAAAAAAAAAAAAAAAGwIAGYvdqgAAAAAAAAAAAAxpQahVMKdAAAAAAAAAAAAK0cfnMcFowAAAAAAAAAAAGUyCgAAAAAAAAAAAAAAAAAbXpy4n6WoAAAAAAAAAAAAGAAXbsHunQAAAAAAAAAAAObum9evM6MAAAAAAAAAAAAA9PA5+UYKAAAAAAAAAAAAAKhuDZ0RCgAAAAAAAAAAAABMgixcNQAAAAAAAAAAAAA9NqYKSgAAAAAAAAAAAAAA4nylcy0AAAAAAAAAAAAAAMvCAAAAAAAAAAAAAAAAAACOjAkAAAAAAET3AgAAAAAAAAAAAAAAAAAMAQAAHgA=',
18+
'Program data: RAP/GoVbk/6jSrNoAAAAAA0rAAAAAAAAHgBxcgEAAAAAAHFyAQAAAAAAAAAAAAAAAABxcgEAAAAAAAAAAAAAAAAAZRuYAQAAAAAAAAAAAAAAAJMNmAEAAAAAAAAAAAAAAAC0eAkAAAAAAByBCQAAAAAAWUbv4v////8ATIIsXDUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==',
19+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH consumed 102636 of 143817 compute units',
20+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH success',
21+
];
22+
const cuUsage = parseLogsForCuUsage(logs);
23+
expect(cuUsage).to.deep.equal([
24+
{
25+
name: 'CuUsage',
26+
data: {
27+
instruction: 'UpdateFundingRate',
28+
cuUsage: 102636,
29+
}
30+
},
31+
]);
32+
});
33+
34+
it('can parse multiple ixs', () => {
35+
const logs = [
36+
'Program ComputeBudget111111111111111111111111111111 invoke [1]',
37+
'Program ComputeBudget111111111111111111111111111111 success',
38+
'Program ComputeBudget111111111111111111111111111111 invoke [1]',
39+
'Program ComputeBudget111111111111111111111111111111 success',
40+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH invoke [1]',
41+
'Program log: Instruction: PostPythLazerOracleUpdate',
42+
'Program log: Skipping new lazer update. current ts 1756622092550000 >= next ts 1756622092000000',
43+
'Program log: Skipping new lazer update. current ts 1756622092550000 >= next ts 1756622092000000',
44+
'Program log: Skipping new lazer update. current ts 1756622092550000 >= next ts 1756622092000000',
45+
'Program log: Price updated to 433158894',
46+
'Program log: Posting new lazer update. current ts 1756622079000000 < next ts 1756622092000000',
47+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH consumed 29242 of 199700 compute units',
48+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH success',
49+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH invoke [1]',
50+
'Program log: Instruction: UpdatePerpBidAskTwap',
51+
'Program log: estimated_bid = None estimated_ask = None',
52+
'Program log: after amm bid twap = 204332308 -> 204328128 \n ask twap = 204350474 -> 204347149 \n ts = 1756622080 -> 1756622092',
53+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH consumed 71006 of 170458 compute units',
54+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH success',
55+
];
56+
const cuUsage = parseLogsForCuUsage(logs);
57+
expect(cuUsage).to.deep.equal([
58+
{
59+
name: 'CuUsage',
60+
data: {
61+
instruction: 'PostPythLazerOracleUpdate',
62+
cuUsage: 29242,
63+
}
64+
},
65+
{
66+
name: 'CuUsage',
67+
data: {
68+
instruction: 'UpdatePerpBidAskTwap',
69+
cuUsage: 71006,
70+
}
71+
},
72+
]);
73+
});
74+
75+
it('can parse ixs with CPI (swaps)', () => {
76+
const logs = [
77+
'Program ComputeBudget111111111111111111111111111111 invoke [1]',
78+
'Program ComputeBudget111111111111111111111111111111 success',
79+
'Program ComputeBudget111111111111111111111111111111 invoke [1]',
80+
'Program ComputeBudget111111111111111111111111111111 success',
81+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH invoke [1]',
82+
'Program log: Instruction: BeginSwap',
83+
'Program data: t7rLuuG7X4K7X+loAAAAAAAAoDz72UK0JwQAAAAAAAAAAMS7r8ACAAAAAAAAAAAAAAB36Aiv26cZAwAAAAAAAAAArDDOLAMAAAAAAAAAAAAAAAA1DAAUzQAAoLsNAA==',
84+
'Program data: t7rLuuG7X4K7X+loAAAAAAEASQcRhBUVAQAAAAAAAAAAAG3WGn8CAAAAAAAAAAAAAADBBakRTIoAAAAAAAAAAAAAFfFDwgIAAAAAAAAAAAAAAAA1DACghgEAYOMWAA==',
85+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]',
86+
'Program log: Instruction: Transfer',
87+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 1336324 compute units',
88+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success',
89+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH consumed 79071 of 1399700 compute units',
90+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH success',
91+
'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 invoke [1]',
92+
'Program log: Instruction: Route',
93+
'Program SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF invoke [2]',
94+
'Program data: S3VCwUhV8CXSyrcV3EtPUNCsQJvXpBqCGUobEJZVRnl5bVAAAAAAAFUFNBYAAAAAAAAAAAAAAAAAAAAAAAAAAA==',
95+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]',
96+
'Program log: Instruction: Transfer',
97+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 1255262 compute units',
98+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success',
99+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]',
100+
'Program log: Instruction: Transfer',
101+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4736 of 1249195 compute units',
102+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success',
103+
'Program SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF consumed 69257 of 1311915 compute units',
104+
'Program SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF success',
105+
'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 invoke [2]',
106+
'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 consumed 199 of 1241147 compute units',
107+
'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 success',
108+
'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 consumed 81059 of 1320629 compute units',
109+
'Program return: JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 Z/tXxXEAAAA=',
110+
'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 success',
111+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH invoke [1]',
112+
'Program log: Instruction: EndSwap',
113+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]',
114+
'Program log: Instruction: Transfer',
115+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4736 of 1187840 compute units',
116+
'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success',
117+
'Program data: ort7woo4+vG7X+loAAAAAJ1Bg8Gp9WhWrw9VRm1UiC0KW6LRC2am2mjhfd3lzm6WZ/tXxXEAAAAA6HZIFwAAAAEAAAAJdDEMAAAAANFBDwAAAAAAAAAAAAAAAAA=',
118+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH consumed 156076 of 1239570 compute units',
119+
'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH success',
120+
];
121+
const cuUsage = parseLogsForCuUsage(logs);
122+
expect(cuUsage).to.deep.equal([
123+
{
124+
name: 'CuUsage',
125+
data: {
126+
instruction: 'BeginSwap',
127+
cuUsage: 79071,
128+
}
129+
},
130+
{
131+
name: 'CuUsage',
132+
data: {
133+
instruction: 'EndSwap',
134+
cuUsage: 156076,
135+
}
136+
},
137+
]);
138+
});
139+
});

0 commit comments

Comments
 (0)