import NodeWallet from '@project-serum/anchor/dist/cjs/nodewallet'; import { BN } from 'bn.js'; import { AnchorProvider } from '@project-serum/anchor'; import { Connection, Keypair, PublicKey } from '@solana/web3.js'; import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'; import { assert } from 'chai'; import * as crypto from 'crypto'; import { createAppendIx, deserializeChangeLogEventV1, SPL_NOOP_PROGRAM_ID, } from '../../src'; import { MerkleTree } from '../../src/merkle-tree'; import { execute, createTreeOnChain } from '../utils'; describe('Serde tests', () => { let offChainTree: MerkleTree; let cmtKeypair: Keypair; let payerKeypair: Keypair; let payer: PublicKey; let connection: Connection; let provider: AnchorProvider; const MAX_SIZE = 64; const MAX_DEPTH = 14; beforeEach(async () => { payerKeypair = Keypair.generate(); payer = payerKeypair.publicKey; connection = new Connection('http://localhost:8899', { commitment: 'confirmed', }); const wallet = new NodeWallet(payerKeypair); provider = new AnchorProvider(connection, wallet, { commitment: connection.commitment, skipPreflight: true, }); await provider.connection.confirmTransaction( await provider.connection.requestAirdrop(payer, 1e10), 'confirmed' ); }); describe('ChangeLogEvent tests', () => { let cmt: PublicKey; beforeEach(async () => { [cmtKeypair, offChainTree] = await createTreeOnChain( provider, payerKeypair, 0, { maxDepth: MAX_DEPTH, maxBufferSize: MAX_SIZE } ); cmt = cmtKeypair.publicKey; }); it('Can deserialize a ChangeLogEvent', async () => { const newLeaf = crypto.randomBytes(32); const txId = await execute( provider, [createAppendIx(cmt, payer, newLeaf)], [payerKeypair] ); offChainTree.updateLeaf(0, newLeaf); const transaction = await connection.getTransaction(txId, { commitment: 'confirmed', maxSupportedTransactionVersion: 2, }); // Get noop program instruction const accountKeys = transaction!.transaction.message.getAccountKeys(); const noopInstruction = transaction!.meta!.innerInstructions![0].instructions[0]; const programId = accountKeys.get(noopInstruction.programIdIndex)!; if (!programId.equals(SPL_NOOP_PROGRAM_ID)) { throw Error( `Only inner ix should be a noop, but instead is a ${programId.toBase58()}` ); } const cpiData = Buffer.from(bs58.decode(noopInstruction.data)); const changeLogEvent = deserializeChangeLogEventV1(cpiData); assert( changeLogEvent.treeId.equals(cmt), `Tree id in changeLog differs from expected tree ${cmt.toBase58()}` ); assert( changeLogEvent.index === 0, `ChangeLog should have index 0, but has index ${changeLogEvent.index}` ); assert( new BN.BN(changeLogEvent.seq).toNumber() === 1, `ChangeLog should have sequence number of 1, but has seq number of ${changeLogEvent.seq.toString()}` ); // Check that the emitted ChangeLog path matches up with the updated Tree let nodeIndex = 0 + (1 << MAX_DEPTH); let realTreeNode = offChainTree.leaves[0]; while (nodeIndex > 0) { const clNode = changeLogEvent.path.shift()!; assert( nodeIndex === clNode.index, `Expected changeLog index to be ${nodeIndex} but is ${clNode.index}` ); assert( realTreeNode!.node.equals(Buffer.from(clNode.node)), `ChangeLog node differs at node index: ${clNode.index}` ); realTreeNode = realTreeNode.parent!; nodeIndex = nodeIndex >> 1; } }); }); });