diff --git a/packages/consensus/source/commit-state.ts b/packages/consensus/source/commit-state.ts index 320ed24a41..e250ddcaf6 100644 --- a/packages/consensus/source/commit-state.ts +++ b/packages/consensus/source/commit-state.ts @@ -12,6 +12,7 @@ export class CommitState implements Contracts.Processor.ProcessableUnit { #processorResult?: Contracts.Processor.BlockProcessorResult; #validators = new Map(); #accountUpdates: Array = []; + #contractEvents: Array = []; public get blockNumber(): number { return this.#commit.block.number; @@ -65,6 +66,14 @@ export class CommitState implements Contracts.Processor.ProcessableUnit { this.#accountUpdates = accounts; } + public getContractEvents(): Array { + return this.#contractEvents; + } + + public setContractEvents(events: Array): void { + this.#contractEvents = events; + } + public async getCommit(): Promise { return this.#commit; } diff --git a/packages/consensus/source/round-state.ts b/packages/consensus/source/round-state.ts index 694b748c29..1fb1d11761 100644 --- a/packages/consensus/source/round-state.ts +++ b/packages/consensus/source/round-state.ts @@ -30,6 +30,7 @@ export class RoundState implements Contracts.Consensus.RoundState { #proposal?: Contracts.Crypto.Proposal; #processorResult?: Contracts.Processor.BlockProcessorResult; #accountUpdates: Array = []; + #contractEvents: Array = []; #prevotes = new Map(); #prevotesCount = new Map(); #precommits = new Map(); @@ -163,6 +164,14 @@ export class RoundState implements Contracts.Consensus.RoundState { this.#accountUpdates = accounts; } + public getContractEvents(): Array { + return this.#contractEvents; + } + + public setContractEvents(events: Array): void { + this.#contractEvents = events; + } + public hasPrevote(validatorIndex: number): boolean { return this.#prevotes.has(validatorIndex); } diff --git a/packages/contracts/source/contracts/evm/evm.ts b/packages/contracts/source/contracts/evm/evm.ts index d8d78ac0c8..9c7e705949 100644 --- a/packages/contracts/source/contracts/evm/evm.ts +++ b/packages/contracts/source/contracts/evm/evm.ts @@ -80,6 +80,24 @@ export interface AccountUpdate { readonly legacyMergeInfo?: AccountMergeInfo; } +export type ContractEvent = { + readonly txHash: string; + readonly txIndex: number; +} & ( + | { readonly event: "Voted"; readonly voter: string; readonly validator: string } + | { readonly event: "Unvoted"; readonly voter: string; readonly validator: string } + | { readonly event: "ValidatorRegistered"; readonly addr: string; readonly blsPublicKey: string } + | { readonly event: "ValidatorResigned"; readonly addr: string } + | { readonly event: "ValidatorUpdated"; readonly addr: string; readonly blsPublicKey: string } + | { + readonly event: "UsernameRegistered"; + readonly addr: string; + readonly username: string; + readonly previousUsername?: string; + } + | { readonly event: "UsernameResigned"; readonly addr: string; readonly username: string } +); + export interface AccountUpdateContext { readonly account: string; readonly commitKey: CommitKey; diff --git a/packages/contracts/source/contracts/processor/processable-unit.ts b/packages/contracts/source/contracts/processor/processable-unit.ts index 60d67db080..b062c3c1ea 100644 --- a/packages/contracts/source/contracts/processor/processable-unit.ts +++ b/packages/contracts/source/contracts/processor/processable-unit.ts @@ -1,6 +1,6 @@ import type { Block } from "../crypto/block.js"; import type { Commit } from "../crypto/commit.js"; -import type { AccountUpdate } from "../evm/evm.js"; +import type { AccountUpdate, ContractEvent } from "../evm/evm.js"; import type { BlockProcessorResult } from "./block-processor-result.js"; export interface ProcessableUnit { @@ -11,6 +11,8 @@ export interface ProcessableUnit { setProcessorResult(processorResult: BlockProcessorResult): void; setAccountUpdates(accounts: Array): void; getAccountUpdates(): Array; + setContractEvents(events: Array): void; + getContractEvents(): Array; getBlock(): Block; getCommit(): Promise; } diff --git a/packages/evm-consensus/source/deployer.ts b/packages/evm-consensus/source/deployer.ts index 2001511b34..6bc8b63116 100644 --- a/packages/evm-consensus/source/deployer.ts +++ b/packages/evm-consensus/source/deployer.ts @@ -98,6 +98,7 @@ export class Deployer implements Contracts.EvmConsensus.Deployer { commitKey, getBlock: () => ({ ...commitKey, number: commitKey.blockNumber }), setAccountUpdates: () => ({}), + setContractEvents: () => ({}), } as unknown as Contracts.Processor.ProcessableUnit); } } diff --git a/packages/evm-service/package.json b/packages/evm-service/package.json index a9f871b393..a5f9286224 100644 --- a/packages/evm-service/package.json +++ b/packages/evm-service/package.json @@ -41,6 +41,7 @@ "@mainsail/crypto-transaction": "workspace:*", "@mainsail/crypto-validation": "workspace:*", "@mainsail/crypto-wif": "workspace:*", + "@mainsail/evm-contracts": "workspace:*", "@mainsail/serializer": "workspace:*", "@mainsail/test-runner": "workspace:*", "@mainsail/validation": "workspace:*", diff --git a/packages/evm-service/source/instances/evm-boundary.test.ts b/packages/evm-service/source/instances/evm-boundary.test.ts index a3c4edabc9..e8b7d2d931 100644 --- a/packages/evm-service/source/instances/evm-boundary.test.ts +++ b/packages/evm-service/source/instances/evm-boundary.test.ts @@ -40,10 +40,12 @@ describe<{ ({ blockNumber: genesisCommit.block.number, getAccountUpdates: () => [], + getContractEvents: () => [], getBlock: () => genesisCommit.block, getCommit: async () => genesisCommit, round: genesisCommit.block.round, setAccountUpdates: () => {}, + setContractEvents: () => {}, }) as unknown as Contracts.Processor.ProcessableUnit; // Valid legacy (base58check) addresses: one imported as a cold wallet by the tests diff --git a/packages/evm-service/source/instances/evm-contract-events.test.ts b/packages/evm-service/source/instances/evm-contract-events.test.ts new file mode 100644 index 0000000000..fda4ce268b --- /dev/null +++ b/packages/evm-service/source/instances/evm-contract-events.test.ts @@ -0,0 +1,311 @@ +import { randomBytes } from "node:crypto"; +import { Enums } from "@mainsail/constants"; +import type { Contracts } from "@mainsail/contracts"; +import { Evm } from "@mainsail/evm"; +import { ConsensusAbi, UsernamesAbi } from "@mainsail/evm-contracts"; +import { Application } from "@mainsail/kernel"; +import { encodeFunctionData, getAddress, getContractAddress, zeroAddress } from "viem"; + +import { describe } from "@mainsail/test-runner"; +import * as MainsailContractEvents from "../../test/fixtures/MainsailContractEvents.json"; +import { wallets } from "../../test/fixtures/wallets"; +import { prepareSandbox } from "../../test/helpers/prepare-sandbox"; +import { EvmInstance } from "./evm"; +import { setGracefulCleanup } from "tmp"; + +describe<{ + app: Application; + evm: Evm; +}>("EvmInstance - contract events", ({ it, assert, afterAll, afterEach, beforeEach }) => { + afterAll(() => setGracefulCleanup()); + + beforeEach(async (context) => { + await prepareSandbox(context); + + context.evm = new Evm({ path: context.app.dataPath("contract-events") }); + }); + + afterEach(async (context) => { + await context.evm.dispose(); + }); + + const getRandomTxHash = () => Buffer.from(randomBytes(32)).toString("hex"); + + const txConfig = { + gasPrice: BigInt(0), + specId: Enums.Evm.SpecId.OSAKA, + }; + + const blockContext: Omit = { + gasLimit: BigInt(10_000_000), + timestamp: BigInt(12_345), + validatorAddress: zeroAddress, + prevrandao: Buffer.alloc(32), + }; + + const normalize = ({ + event, + txHash, + txIndex, + voter, + validator, + addr, + username, + previousUsername, + blsPublicKey, + }: any) => ({ addr, blsPublicKey, event, previousUsername, txHash, txIndex, username, validator, voter }); + + it("#commit - should return the decoded consensus and username contract events", async ({ evm }) => { + const [sender, other] = wallets; + const voter = getAddress(sender.address); + const validator = getAddress(other.address); + const blsPublicKey = "aa".repeat(48); + + const consensusContract = getContractAddress({ from: voter, nonce: 0n }); + const usernamesContract = getContractAddress({ from: voter, nonce: 1n }); + + await evm.initializeGenesis({ + account: voter, + deployerAccount: "0x0000000000000000000000000000000000000001", + initialBlockNumber: 0n, + initialSupply: 0n, + usernameContract: usernamesContract, + validatorContract: consensusContract, + }); + + // Block 0: deploy the two emitter contracts. + let commitKey = { blockNumber: BigInt(0), round: BigInt(0) }; + await evm.prepareNextCommit({ blockContext: { ...blockContext, commitKey } }); + + for (const nonce of [0n, 1n]) { + const { receipt } = await evm.process({ + commitKey, + data: Buffer.from(MainsailContractEvents.bytecode.object.slice(2), "hex"), + from: voter, + gasLimit: BigInt(1_000_000), + nonce, + txHash: getRandomTxHash(), + value: 0n, + ...txConfig, + }); + assert.equal(receipt.status, 1); + } + + const genesisResult = await evm.commit(commitKey); + assert.empty(genesisResult.events); + + // Block 1: one transaction per contract, emitting every tracked event. + commitKey = { blockNumber: BigInt(1), round: BigInt(0) }; + await evm.prepareNextCommit({ blockContext: { ...blockContext, commitKey } }); + + const consensusTxHash = getRandomTxHash(); + const usernamesTxHash = getRandomTxHash(); + + for (const [nonce, txHash, to, functionName, args] of [ + [2n, consensusTxHash, consensusContract, "emitConsensusEvents", [voter, validator, `0x${blsPublicKey}`]], + [3n, usernamesTxHash, usernamesContract, "emitUsernameEvents", [voter]], + ] as const) { + const { receipt } = await evm.process({ + commitKey, + data: Buffer.from( + encodeFunctionData({ abi: MainsailContractEvents.abi, args: [...args], functionName }).slice(2), + "hex", + ), + from: voter, + gasLimit: BigInt(200_000), + nonce, + to, + txHash, + value: 0n, + ...txConfig, + }); + assert.equal(receipt.status, 1); + } + + const { dirtyAccounts, events } = await evm.commit(commitKey); + + assert.equal( + events.map(normalize), + [ + { event: "Voted", txHash: consensusTxHash, txIndex: 0, validator, voter }, + { event: "Unvoted", txHash: consensusTxHash, txIndex: 0, validator, voter }, + { addr: voter, blsPublicKey, event: "ValidatorRegistered", txHash: consensusTxHash, txIndex: 0 }, + { addr: voter, event: "ValidatorResigned", txHash: consensusTxHash, txIndex: 0 }, + { addr: voter, blsPublicKey, event: "ValidatorUpdated", txHash: consensusTxHash, txIndex: 0 }, + { + addr: voter, + event: "UsernameRegistered", + txHash: usernamesTxHash, + txIndex: 1, + username: "alice", + }, + { + addr: voter, + event: "UsernameRegistered", + previousUsername: "alice", + txHash: usernamesTxHash, + txIndex: 1, + username: "bob", + }, + { addr: voter, event: "UsernameResigned", txHash: usernamesTxHash, txIndex: 1, username: "bob" }, + ].map(normalize), + ); + + const senderUpdate = dirtyAccounts.find(({ address }) => address === voter); + assert.defined(senderUpdate); + assert.equal(senderUpdate!.unvote, validator); + assert.undefined(senderUpdate!.vote); + assert.undefined(senderUpdate!.username); + assert.true(senderUpdate!.usernameResigned); + }); + + it("#commit - should return no events when genesis info is not initialized", async ({ evm }) => { + const [sender] = wallets; + const voter = getAddress(sender.address); + + const commitKey = { blockNumber: BigInt(0), round: BigInt(0) }; + await evm.prepareNextCommit({ blockContext: { ...blockContext, commitKey } }); + + const { receipt } = await evm.process({ + commitKey, + data: Buffer.from(MainsailContractEvents.bytecode.object.slice(2), "hex"), + from: voter, + gasLimit: BigInt(1_000_000), + nonce: 0n, + txHash: getRandomTxHash(), + value: 0n, + ...txConfig, + }); + assert.equal(receipt.status, 1); + + const emitterContract = getContractAddress({ from: voter, nonce: 0n }); + + const { receipt: emitReceipt } = await evm.process({ + commitKey, + data: Buffer.from( + encodeFunctionData({ + abi: MainsailContractEvents.abi, + args: [voter], + functionName: "emitUsernameEvents", + }).slice(2), + "hex", + ), + from: voter, + gasLimit: BigInt(200_000), + nonce: 1n, + to: emitterContract, + txHash: getRandomTxHash(), + value: 0n, + ...txConfig, + }); + assert.equal(emitReceipt.status, 1); + + // Without genesis info the contract addresses are unknown, so nothing decodes. + const { events } = await evm.commit(commitKey); + assert.empty(events); + }); + + it("#onCommit - should forward the contract events to the processable unit", async ({ app }) => { + const instance = app.resolve(EvmInstance); + + try { + const [sender] = wallets; + const voter = getAddress(sender.address); + const usernamesContract = getContractAddress({ from: voter, nonce: 0n }); + + await instance.initializeGenesis({ + account: voter, + deployerAccount: "0x0000000000000000000000000000000000000001", + initialBlockNumber: 0n, + initialSupply: 0n, + usernameContract: usernamesContract, + validatorContract: "0x0000000000000000000000000000000000000002", + }); + + const commitKey = { blockNumber: BigInt(0), round: BigInt(0) }; + await instance.prepareNextCommit({ blockContext: { ...blockContext, commitKey } }); + + for (const [nonce, data, to] of [ + [0n, MainsailContractEvents.bytecode.object, undefined], + [ + 1n, + encodeFunctionData({ + abi: MainsailContractEvents.abi, + args: [voter], + functionName: "emitUsernameEvents", + }), + usernamesContract, + ], + ] as const) { + const { receipt } = await instance.process({ + commitKey, + data: Buffer.from(data.slice(2), "hex"), + from: voter, + gasLimit: BigInt(1_000_000), + nonce, + to, + txHash: getRandomTxHash(), + value: 0n, + ...txConfig, + }); + assert.equal(receipt.status, 1); + } + + let capturedUpdates: Contracts.Evm.AccountUpdate[] | undefined; + let capturedEvents: Contracts.Evm.ContractEvent[] | undefined; + + await instance.onCommit({ + blockNumber: commitKey.blockNumber, + getBlock: () => ({ number: commitKey.blockNumber, round: commitKey.round }), + round: commitKey.round, + setAccountUpdates: (accounts: Contracts.Evm.AccountUpdate[]) => { + capturedUpdates = accounts; + }, + setContractEvents: (events: Contracts.Evm.ContractEvent[]) => { + capturedEvents = events; + }, + } as unknown as Contracts.Processor.ProcessableUnit); + + assert.defined(capturedUpdates); + assert.equal( + capturedEvents!.map((event) => ({ event: event.event, username: (event as any).username })), + [ + { event: "UsernameRegistered", username: "alice" }, + { event: "UsernameRegistered", username: "bob" }, + { event: "UsernameResigned", username: "bob" }, + ], + ); + } finally { + await instance.dispose(); + } + }); + + it("fixture - should declare the exact event signatures of the deployed contracts", () => { + const eventAbi = (abi: Record[], name: string) => { + const item = abi.find((entry) => entry.type === "event" && entry.name === name) as any; + assert.defined(item); + + return { + anonymous: item.anonymous ?? false, + inputs: item.inputs.map(({ indexed, name: inputName, type }: any) => ({ + indexed, + name: inputName, + type, + })), + name: item.name, + }; + }; + + for (const [realAbi, eventNames] of [ + [ConsensusAbi.abi, ["Voted", "Unvoted", "ValidatorRegistered", "ValidatorResigned", "ValidatorUpdated"]], + [UsernamesAbi.abi, ["UsernameRegistered", "UsernameResigned"]], + ] as [Record[], string[]][]) { + for (const eventName of eventNames) { + assert.equal( + eventAbi(MainsailContractEvents.abi as Record[], eventName), + eventAbi(realAbi, eventName), + ); + } + } + }); +}); diff --git a/packages/evm-service/source/instances/evm-genesis.test.ts b/packages/evm-service/source/instances/evm-genesis.test.ts index 4c223cb499..e90c993d90 100644 --- a/packages/evm-service/source/instances/evm-genesis.test.ts +++ b/packages/evm-service/source/instances/evm-genesis.test.ts @@ -175,6 +175,7 @@ describe<{ getBlock: () => ({ number: 0n, round: 0n }), round: 0n, setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); // After: the block reward has been credited to the proposer. diff --git a/packages/evm-service/source/instances/evm.test.ts b/packages/evm-service/source/instances/evm.test.ts index fe7bc1cb16..ed29e6dd59 100644 --- a/packages/evm-service/source/instances/evm.test.ts +++ b/packages/evm-service/source/instances/evm.test.ts @@ -139,6 +139,7 @@ describe<{ round: BigInt(0), }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); const encodedCall = encodeFunctionData({ @@ -222,6 +223,7 @@ describe<{ round: BigInt(0), }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); assert.equal(receipt.status, 1); @@ -265,6 +267,7 @@ describe<{ round: BigInt(0), }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); assert.equal(receipt.status, 1); @@ -289,6 +292,7 @@ describe<{ round: commitKey.round, }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); // No legacy balance present yet @@ -402,6 +406,7 @@ describe<{ round: commitKey.round, }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); await instance.prepareNextCommit({ blockContext: { ...blockContext, commitKey } }); @@ -489,6 +494,7 @@ describe<{ round: commitKey.round, }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); // @@ -551,6 +557,7 @@ describe<{ round: commitKey1.round, }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any), ); @@ -563,6 +570,7 @@ describe<{ round: commitKey2.round, }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); }, "commit is missing commit key"); @@ -585,6 +593,7 @@ describe<{ round: 0, }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any), ); }); @@ -629,6 +638,7 @@ describe<{ round: commitKey.round, }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); const randomTxHash = getRandomTxHash(); @@ -670,6 +680,7 @@ describe<{ round: BigInt(0), }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); assert.equal(receipt.status, 1); @@ -735,6 +746,7 @@ describe<{ round: BigInt(0), }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); // Balance updated correctly @@ -849,6 +861,7 @@ describe<{ round: BigInt(0), }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); code = await instance.codeAt(receipt.contractAddress!); @@ -903,6 +916,7 @@ describe<{ round: BigInt(0), }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); const nextCommitKey = { blockNumber: BigInt(1), round: BigInt(0) }; @@ -952,6 +966,7 @@ describe<{ round: BigInt(0), }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); // look up slot containing user balance @@ -1059,6 +1074,7 @@ describe<{ round: BigInt(0), }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); const contractAddress = receipt.contractAddress; @@ -1126,6 +1142,7 @@ describe<{ round: BigInt(0), }), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); // @@ -1183,6 +1200,7 @@ describe<{ getBlock: () => ({ number: BigInt(0), round: BigInt(0) }), round: BigInt(0), setAccountUpdates: () => {}, + setContractEvents: () => {}, } as any); assert.equal((await instance.getAccountInfo(sender.address)).balance, 1234n); diff --git a/packages/evm-service/source/instances/evm.ts b/packages/evm-service/source/instances/evm.ts index 4f2812cadf..c5d469e662 100644 --- a/packages/evm-service/source/instances/evm.ts +++ b/packages/evm-service/source/instances/evm.ts @@ -175,6 +175,7 @@ export class EvmInstance implements Contracts.Evm.Instance, Contracts.Evm.Storag commitData, ); unit.setAccountUpdates(result.dirtyAccounts); + unit.setContractEvents(result.events as Contracts.Evm.ContractEvent[]); } public async codeAt(address: string, blockNumber?: bigint): Promise { diff --git a/packages/evm-service/test/fixtures/MainsailContractEvents.json b/packages/evm-service/test/fixtures/MainsailContractEvents.json new file mode 100644 index 0000000000..2c9d3b1d08 --- /dev/null +++ b/packages/evm-service/test/fixtures/MainsailContractEvents.json @@ -0,0 +1,200 @@ +{ + "abi": [ + { + "type": "function", + "name": "emitConsensusEvents", + "inputs": [ + { "name": "voter", "type": "address", "internalType": "address" }, + { "name": "validator", "type": "address", "internalType": "address" }, + { "name": "blsPublicKey", "type": "bytes", "internalType": "bytes" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "emitUsernameEvents", + "inputs": [{ "name": "addr", "type": "address", "internalType": "address" }], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Unvoted", + "inputs": [ + { "name": "voter", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "validator", "type": "address", "indexed": false, "internalType": "address" } + ], + "anonymous": false + }, + { + "type": "event", + "name": "UsernameRegistered", + "inputs": [ + { "name": "addr", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "username", "type": "string", "indexed": false, "internalType": "string" }, + { "name": "previousUsername", "type": "string", "indexed": false, "internalType": "string" } + ], + "anonymous": false + }, + { + "type": "event", + "name": "UsernameResigned", + "inputs": [ + { "name": "addr", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "username", "type": "string", "indexed": false, "internalType": "string" } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ValidatorRegistered", + "inputs": [ + { "name": "addr", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "blsPublicKey", "type": "bytes", "indexed": false, "internalType": "bytes" } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ValidatorResigned", + "inputs": [{ "name": "addr", "type": "address", "indexed": false, "internalType": "address" }], + "anonymous": false + }, + { + "type": "event", + "name": "ValidatorUpdated", + "inputs": [ + { "name": "addr", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "blsPublicKey", "type": "bytes", "indexed": false, "internalType": "bytes" } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Voted", + "inputs": [ + { "name": "voter", "type": "address", "indexed": false, "internalType": "address" }, + { "name": "validator", "type": "address", "indexed": false, "internalType": "address" } + ], + "anonymous": false + } + ], + "bytecode": { + "object": "0x6080604052348015600e575f5ffd5b5061061c8061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063996ee84f14610038578063f305bc1e14610054575b5f5ffd5b610052600480360381019061004d9190610300565b610070565b005b61006e60048036038101906100699190610371565b610195565b005b7fce0c7a2a940807f7dc2ce7a615c2532e915e6c0ac9a08bc4ed9d515a710a53e284846040516100a19291906103ab565b60405180910390a17f6572af8bf9a0a86efb88dcc30011626a15c9c4603503aa4466a3f87a1867deef84846040516100da9291906103ab565b60405180910390a17f61809fa303a3a57f4d70552f533f3e0b003173d424590cd4bb22a2afe000990c8483836040516101159392919061042c565b60405180910390a17f24250fc1ec78a1405ddd4cc8b75964858af228d05faa8d4bc1302966d8a541178460405161014c919061045c565b60405180910390a17f4af0b5984c9f88659d661fb64dcb63a5b946809cf1c78a8c6e0f6ef93c9170328483836040516101879392919061042c565b60405180910390a150505050565b7fdc393f1f31882fea068a12acfed8ed6e9f7e88a6ed213355b5afb78ad76a7045816040516101c491906104f2565b60405180910390a17fdc393f1f31882fea068a12acfed8ed6e9f7e88a6ed213355b5afb78ad76a7045816040516101fb919061057b565b60405180910390a17ff5a79d28213d53340730f0c5a952f4809e33db20cbe21a2a0b5fa7d77fa107b38160405161023291906105ba565b60405180910390a150565b5f5ffd5b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61026e82610245565b9050919050565b61027e81610264565b8114610288575f5ffd5b50565b5f8135905061029981610275565b92915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f8401126102c0576102bf61029f565b5b8235905067ffffffffffffffff8111156102dd576102dc6102a3565b5b6020830191508360018202830111156102f9576102f86102a7565b5b9250929050565b5f5f5f5f606085870312156103185761031761023d565b5b5f6103258782880161028b565b94505060206103368782880161028b565b935050604085013567ffffffffffffffff81111561035757610356610241565b5b610363878288016102ab565b925092505092959194509250565b5f602082840312156103865761038561023d565b5b5f6103938482850161028b565b91505092915050565b6103a581610264565b82525050565b5f6040820190506103be5f83018561039c565b6103cb602083018461039c565b9392505050565b5f82825260208201905092915050565b828183375f83830152505050565b5f601f19601f8301169050919050565b5f61040b83856103d2565b93506104188385846103e2565b610421836103f0565b840190509392505050565b5f60408201905061043f5f83018661039c565b8181036020830152610452818486610400565b9050949350505050565b5f60208201905061046f5f83018461039c565b92915050565b5f82825260208201905092915050565b7f616c6963650000000000000000000000000000000000000000000000000000005f82015250565b5f6104b9600583610475565b91506104c482610485565b602082019050919050565b50565b5f6104dd5f83610475565b91506104e8826104cf565b5f82019050919050565b5f6060820190506105055f83018461039c565b8181036020830152610516816104ad565b90508181036040830152610529816104d2565b905092915050565b7f626f6200000000000000000000000000000000000000000000000000000000005f82015250565b5f610565600383610475565b915061057082610531565b602082019050919050565b5f60608201905061058e5f83018461039c565b818103602083015261059f81610559565b905081810360408301526105b2816104ad565b905092915050565b5f6040820190506105cd5f83018461039c565b81810360208301526105de81610559565b90509291505056fea26469706673582212206ab90ba0af9389d48c6a85c9a0b00b51bc9f3a5ded285d2cdcbff1ecfb86f51664736f6c63430008240033", + "sourceMap": "260:1007:0:-:0;;;;;;;;;;;;;;;;;;;", + "linkReferences": {} + }, + "methodIdentifiers": { + "emitConsensusEvents(address,address,bytes)": "996ee84f", + "emitUsernameEvents(address)": "f305bc1e" + }, + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.36+commit.8a079791\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"}],\"name\":\"Unvoted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"username\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"previousUsername\",\"type\":\"string\"}],\"name\":\"UsernameRegistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"username\",\"type\":\"string\"}],\"name\":\"UsernameResigned\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"blsPublicKey\",\"type\":\"bytes\"}],\"name\":\"ValidatorRegistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"ValidatorResigned\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"blsPublicKey\",\"type\":\"bytes\"}],\"name\":\"ValidatorUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"}],\"name\":\"Voted\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"blsPublicKey\",\"type\":\"bytes\"}],\"name\":\"emitConsensusEvents\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"emitUsernameEvents\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Test fixture emitting the exact event signatures of ConsensusV1 and UsernamesV1, so the Rust-side commit event decoding can be exercised without deploying the full consensus system.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/MainsailContractEvents.sol\":\"MainsailContractEvents\"},\"evmVersion\":\"osaka\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":false,\"runs\":200},\"remappings\":[]},\"sources\":{\"src/MainsailContractEvents.sol\":{\"keccak256\":\"0xdb8d8d51861f5d165a6784a5c82bf29d45eca18eecd8819839a231d6ab863dfe\",\"license\":\"GPL-3.0-only\",\"urls\":[\"bzz-raw://869722dfdea82c07465d0aa00044f90b0c5bffc7e05dd5bc738b0a2fd4016be9\",\"dweb:/ipfs/QmaEtoFDB6Bxvjh3uiv322ZsBDUAEHAP4pGnCWDnPA3bWr\"]}},\"version\":1}", + "metadata": { + "compiler": { "version": "0.8.36+commit.8a079791" }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { "internalType": "address", "name": "voter", "type": "address", "indexed": false }, + { "internalType": "address", "name": "validator", "type": "address", "indexed": false } + ], + "type": "event", + "name": "Unvoted", + "anonymous": false + }, + { + "inputs": [ + { "internalType": "address", "name": "addr", "type": "address", "indexed": false }, + { "internalType": "string", "name": "username", "type": "string", "indexed": false }, + { "internalType": "string", "name": "previousUsername", "type": "string", "indexed": false } + ], + "type": "event", + "name": "UsernameRegistered", + "anonymous": false + }, + { + "inputs": [ + { "internalType": "address", "name": "addr", "type": "address", "indexed": false }, + { "internalType": "string", "name": "username", "type": "string", "indexed": false } + ], + "type": "event", + "name": "UsernameResigned", + "anonymous": false + }, + { + "inputs": [ + { "internalType": "address", "name": "addr", "type": "address", "indexed": false }, + { "internalType": "bytes", "name": "blsPublicKey", "type": "bytes", "indexed": false } + ], + "type": "event", + "name": "ValidatorRegistered", + "anonymous": false + }, + { + "inputs": [{ "internalType": "address", "name": "addr", "type": "address", "indexed": false }], + "type": "event", + "name": "ValidatorResigned", + "anonymous": false + }, + { + "inputs": [ + { "internalType": "address", "name": "addr", "type": "address", "indexed": false }, + { "internalType": "bytes", "name": "blsPublicKey", "type": "bytes", "indexed": false } + ], + "type": "event", + "name": "ValidatorUpdated", + "anonymous": false + }, + { + "inputs": [ + { "internalType": "address", "name": "voter", "type": "address", "indexed": false }, + { "internalType": "address", "name": "validator", "type": "address", "indexed": false } + ], + "type": "event", + "name": "Voted", + "anonymous": false + }, + { + "inputs": [ + { "internalType": "address", "name": "voter", "type": "address" }, + { "internalType": "address", "name": "validator", "type": "address" }, + { "internalType": "bytes", "name": "blsPublicKey", "type": "bytes" } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "emitConsensusEvents" + }, + { + "inputs": [{ "internalType": "address", "name": "addr", "type": "address" }], + "stateMutability": "nonpayable", + "type": "function", + "name": "emitUsernameEvents" + } + ], + "devdoc": { "kind": "dev", "methods": {}, "version": 1 }, + "userdoc": { "kind": "user", "methods": {}, "version": 1 } + }, + "settings": { + "remappings": [], + "optimizer": { "enabled": false, "runs": 200 }, + "metadata": { "bytecodeHash": "ipfs" }, + "compilationTarget": { "src/MainsailContractEvents.sol": "MainsailContractEvents" }, + "evmVersion": "osaka", + "libraries": {} + }, + "sources": { + "src/MainsailContractEvents.sol": { + "keccak256": "0xdb8d8d51861f5d165a6784a5c82bf29d45eca18eecd8819839a231d6ab863dfe", + "urls": [ + "bzz-raw://869722dfdea82c07465d0aa00044f90b0c5bffc7e05dd5bc738b0a2fd4016be9", + "dweb:/ipfs/QmaEtoFDB6Bxvjh3uiv322ZsBDUAEHAP4pGnCWDnPA3bWr" + ], + "license": "GPL-3.0-only" + } + }, + "version": 1 + }, + "id": 0 +} diff --git a/packages/evm-service/test/fixtures/MainsailContractEvents.sol b/packages/evm-service/test/fixtures/MainsailContractEvents.sol new file mode 100644 index 0000000000..52905a6bf1 --- /dev/null +++ b/packages/evm-service/test/fixtures/MainsailContractEvents.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity 0.8.36; + +/// Test fixture emitting the exact event signatures of ConsensusV1 and +/// UsernamesV1, so the Rust-side commit event decoding can be exercised +/// without deploying the full consensus system. +contract MainsailContractEvents { + event Voted(address voter, address validator); + event Unvoted(address voter, address validator); + event ValidatorRegistered(address addr, bytes blsPublicKey); + event ValidatorResigned(address addr); + event ValidatorUpdated(address addr, bytes blsPublicKey); + event UsernameRegistered(address addr, string username, string previousUsername); + event UsernameResigned(address addr, string username); + + function emitConsensusEvents(address voter, address validator, bytes calldata blsPublicKey) external { + emit Voted(voter, validator); + emit Unvoted(voter, validator); + emit ValidatorRegistered(voter, blsPublicKey); + emit ValidatorResigned(voter); + emit ValidatorUpdated(voter, blsPublicKey); + } + + function emitUsernameEvents(address addr) external { + emit UsernameRegistered(addr, "alice", ""); + emit UsernameRegistered(addr, "bob", "alice"); + emit UsernameResigned(addr, "bob"); + } +} diff --git a/packages/evm-service/test/helpers/commit-genesis.ts b/packages/evm-service/test/helpers/commit-genesis.ts index c33315a27f..36dd486f3c 100644 --- a/packages/evm-service/test/helpers/commit-genesis.ts +++ b/packages/evm-service/test/helpers/commit-genesis.ts @@ -80,6 +80,7 @@ export const commitGenesis = async ( await instance.onCommit({ blockNumber: block.number, getAccountUpdates: () => [], + getContractEvents: () => [], getBlock: () => block, getCommit: async () => genesisCommit, getProcessorResult: () => ({ @@ -91,6 +92,7 @@ export const commitGenesis = async ( hasProcessorResult: () => false, round: block.round, setAccountUpdates: () => {}, + setContractEvents: () => {}, setProcessorResult: () => {}, }); diff --git a/packages/evm/bindings/src/lib.rs b/packages/evm/bindings/src/lib.rs index fd3518d2f9..84da846e2f 100644 --- a/packages/evm/bindings/src/lib.rs +++ b/packages/evm/bindings/src/lib.rs @@ -15,6 +15,7 @@ use mainsail_evm_core::{ BlockContext, BlockHeaderData, CommitData, CommitKey, GenesisInfo, PendingCommit, PersistentDB, PersistentDBOptions, ProofData, TransactionData, TxnDatabaseReader, }, + events::ContractEvent, legacy::{LegacyAccountAttributes, LegacyAddress, LegacyColdWallet}, logger::LogLevel, logs_bloom, @@ -895,7 +896,7 @@ impl EvmInner { &mut self, commit_key: CommitKey, commit_data: Option, - ) -> std::result::Result, EVMError> { + ) -> std::result::Result<(Vec, Vec), EVMError> { if !self.pending_commits.contains_key(&commit_key) { return Err(EVMError::Custom(format!( "commit is missing commit key {:?}", @@ -1848,9 +1849,10 @@ impl JsEvmWrapper { self.write( env, move |evm| evm.commit(commit_key, commit_data), - |_, result| { + |_, (dirty_accounts, events)| { Ok(result::JsCommitResult::new(CommitResult { - dirty_accounts: result, + dirty_accounts, + events, })?) }, ) diff --git a/packages/evm/bindings/src/result.rs b/packages/evm/bindings/src/result.rs index d857c3e856..19928513db 100644 --- a/packages/evm/bindings/src/result.rs +++ b/packages/evm/bindings/src/result.rs @@ -1,6 +1,7 @@ use mainsail_evm_core::{ account::AccountInfoExtended, db::{BlockHeaderData, ProofData, TransactionData}, + events::{ContractEvent, ContractEventData}, legacy::{LegacyAccountAttributes, LegacyColdWallet, LegacyMultiSignatureAttribute}, receipt::TxReceipt, state_changes::AccountUpdate, @@ -44,6 +45,7 @@ impl JsSimulateResult { #[napi(object, object_from_js = false)] pub struct JsCommitResult { pub dirty_accounts: Vec, + pub events: Vec, } impl JsCommitResult { @@ -53,7 +55,77 @@ impl JsCommitResult { dirty_accounts.push(JsAccountUpdate::new(item)); } - Ok(Self { dirty_accounts }) + let mut events = Vec::with_capacity(result.events.len()); + for item in result.events { + events.push(JsContractEvent::new(item)); + } + + Ok(Self { + dirty_accounts, + events, + }) + } +} + +#[napi(object)] +#[derive(Default)] +pub struct JsContractEvent { + pub event: String, + pub tx_hash: String, + pub tx_index: u32, + pub voter: Option, + pub validator: Option, + pub addr: Option, + pub username: Option, + pub previous_username: Option, + pub bls_public_key: Option, +} + +impl JsContractEvent { + pub fn new(event: ContractEvent) -> Self { + let mut js_event = Self { + event: event.data.name().to_string(), + tx_hash: format!("{:x}", event.tx_hash), + tx_index: event.tx_index, + ..Default::default() + }; + + match event.data { + ContractEventData::Voted { voter, validator } + | ContractEventData::Unvoted { voter, validator } => { + js_event.voter = Some(voter.to_checksum(None)); + js_event.validator = Some(validator.to_checksum(None)); + } + ContractEventData::ValidatorRegistered { + addr, + bls_public_key, + } + | ContractEventData::ValidatorUpdated { + addr, + bls_public_key, + } => { + js_event.addr = Some(addr.to_checksum(None)); + js_event.bls_public_key = Some(bls_public_key.encode_hex()); + } + ContractEventData::ValidatorResigned { addr } => { + js_event.addr = Some(addr.to_checksum(None)); + } + ContractEventData::UsernameRegistered { + addr, + username, + previous_username, + } => { + js_event.addr = Some(addr.to_checksum(None)); + js_event.username = Some(username); + js_event.previous_username = previous_username; + } + ContractEventData::UsernameResigned { addr, username } => { + js_event.addr = Some(addr.to_checksum(None)); + js_event.username = Some(username); + } + } + + js_event } } @@ -106,6 +178,7 @@ pub struct JsTransactionReceipt { #[derive(Default)] pub struct CommitResult { pub dirty_accounts: Vec, + pub events: Vec, } pub struct TxViewResult { diff --git a/packages/evm/core/src/events.rs b/packages/evm/core/src/events.rs index 7aaff26f94..9fd5495a81 100644 --- a/packages/evm/core/src/events.rs +++ b/packages/evm/core/src/events.rs @@ -1,9 +1,76 @@ +use alloy_primitives::{Address, B256, Bytes}; use alloy_sol_types::sol; sol! { - event Voted(address voter, address validator); - event Unvoted(address voter, address validator); + interface ConsensusV1 { + event Voted(address voter, address validator); + event Unvoted(address voter, address validator); - event UsernameRegistered(address addr, string username, string previousUsername); - event UsernameResigned(address addr, string username); + event ValidatorRegistered(address addr, bytes blsPublicKey); + event ValidatorResigned(address addr); + event ValidatorUpdated(address addr, bytes blsPublicKey); + } + + interface UsernamesV1 { + event UsernameRegistered(address addr, string username, string previousUsername); + event UsernameResigned(address addr, string username); + } +} + +pub use ConsensusV1::{ + ConsensusV1Events, Unvoted, ValidatorRegistered, ValidatorResigned, ValidatorUpdated, Voted, +}; +pub use UsernamesV1::{UsernameRegistered, UsernameResigned, UsernamesV1Events}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ContractEvent { + pub tx_hash: B256, + pub tx_index: u32, + pub data: ContractEventData, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ContractEventData { + Voted { + voter: Address, + validator: Address, + }, + Unvoted { + voter: Address, + validator: Address, + }, + ValidatorRegistered { + addr: Address, + bls_public_key: Bytes, + }, + ValidatorResigned { + addr: Address, + }, + ValidatorUpdated { + addr: Address, + bls_public_key: Bytes, + }, + UsernameRegistered { + addr: Address, + username: String, + previous_username: Option, + }, + UsernameResigned { + addr: Address, + username: String, + }, +} + +impl ContractEventData { + pub fn name(&self) -> &'static str { + match self { + Self::Voted { .. } => "Voted", + Self::Unvoted { .. } => "Unvoted", + Self::ValidatorRegistered { .. } => "ValidatorRegistered", + Self::ValidatorResigned { .. } => "ValidatorResigned", + Self::ValidatorUpdated { .. } => "ValidatorUpdated", + Self::UsernameRegistered { .. } => "UsernameRegistered", + Self::UsernameResigned { .. } => "UsernameResigned", + } + } } diff --git a/packages/evm/core/src/lib.rs b/packages/evm/core/src/lib.rs index 897cda88e9..5afc549c94 100644 --- a/packages/evm/core/src/lib.rs +++ b/packages/evm/core/src/lib.rs @@ -2,7 +2,7 @@ pub mod account; mod bytecode; mod compression; pub mod db; -mod events; +pub mod events; pub mod historical; pub mod legacy; pub mod logger; diff --git a/packages/evm/core/src/state_commit.rs b/packages/evm/core/src/state_commit.rs index ec0dfd0d23..44af229677 100644 --- a/packages/evm/core/src/state_commit.rs +++ b/packages/evm/core/src/state_commit.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, collections::BTreeMap}; -use alloy_sol_types::SolEvent; +use alloy_sol_types::SolEventInterface; use rayon::{ iter::{IntoParallelRefMutIterator, ParallelIterator}, slice::ParallelSliceMut, @@ -14,6 +14,7 @@ use revm::{ use crate::{ db::{CommitData, CommitKey, Error, GenesisInfo, PendingCommit, PersistentDB}, + events::{self, ConsensusV1Events, ContractEvent, ContractEventData, UsernamesV1Events}, state_changes::{self, AccountMergeInfo, AccountUpdate}, }; @@ -119,7 +120,7 @@ pub fn commit_to_db( db: &mut PersistentDB, mut pending_commit: PendingCommit, commit_data: Option, -) -> Result, crate::db::Error> { +) -> Result<(Vec, Vec), crate::db::Error> { let genesis_info = db.genesis_info.clone(); let mut commit = match pending_commit.built_commit { Some(commit) => commit, @@ -128,7 +129,7 @@ pub fn commit_to_db( commit_with_resize_retry(|| db.commit(&mut commit, &commit_data), || db.resize())?; - Ok(collect_dirty_accounts(commit, &genesis_info)) + Ok(collect_dirty_accounts_and_events(commit, &genesis_info)) } /// Maximum number of resize-and-retry attempts after an initial `DbFull` on commit. @@ -164,11 +165,12 @@ fn finalize(state: &mut StateCommit) { .par_sort_unstable_by_key(|a| a.address); } -fn collect_dirty_accounts( +fn collect_dirty_accounts_and_events( commit: StateCommit, genesis_info: &Option, -) -> Vec { +) -> (Vec, Vec) { let mut dirty_accounts = HashMap::with_capacity(commit.change_set.accounts.len()); + let mut events: Vec = Vec::new(); for (address, account) in commit.change_set.accounts { // A destroyed (selfdestructed) account comes through as `None`; surface it as a @@ -205,83 +207,144 @@ fn collect_dirty_accounts( // least the 21000-gas intrinsic cost, so each entry's cumulative total is strictly // greater than the previous one's — the key is guaranteed unique and monotonic, // never tied. - let mut results: Vec<&(ExecutionResult, u64)> = commit.results.values().collect(); - results.sort_by_key(|(_, cumulative_gas_used)| *cumulative_gas_used); + let mut results: Vec<(&B256, &(ExecutionResult, u64))> = commit.results.iter().collect(); + results.sort_by_key(|(_, (_, cumulative_gas_used))| *cumulative_gas_used); + + for (tx_index, (tx_hash, (receipt, _))) in results.into_iter().enumerate() { + let make_event = |data: ContractEventData| ContractEvent { + tx_hash: *tx_hash, + tx_index: tx_index as u32, + data, + }; - for (receipt, _) in results { match receipt { ExecutionResult::Success { logs, .. } => { for log in logs { match log.address { _ if log.address == info.validator_contract => { - // Attempt to decode the log as a Voted event - if let Ok(event) = crate::events::Voted::decode_log(&log) { - // println!( - // "Voted event (from={:?} to={:?})", - // event.data.voter, event.data.validator, - // ); - - dirty_accounts.get_mut(&event.voter).and_then(|account| { - account.vote = Some(event.validator); - account.unvote = None; // cancel out any previous unvote if one happened in same commit - Some(account) - }); - - continue; - } - - // Attempt to decode the log as a Unvoted event - if let Ok(event) = crate::events::Unvoted::decode_log(&log) { - // println!( - // "Unvoted event (from={:?} removed vote={:?})", - // event.data.voter, event.data.validator, - // ); - - dirty_accounts.get_mut(&event.voter).and_then(|account| { - account.unvote = Some(event.validator); - account.vote = None; // cancel out any previous vote if one happened in same commit - Some(account) - }); - + let Ok(decoded) = ConsensusV1Events::decode_log(log) else { continue; + }; + + match decoded.data { + ConsensusV1Events::Voted(events::Voted { + voter, + validator, + }) => { + dirty_accounts.get_mut(&voter).and_then(|account| { + account.vote = Some(validator); + account.unvote = None; // cancel out any previous unvote if one happened in same commit + Some(account) + }); + + events.push(make_event(ContractEventData::Voted { + voter, + validator, + })); + } + ConsensusV1Events::Unvoted(events::Unvoted { + voter, + validator, + }) => { + dirty_accounts.get_mut(&voter).and_then(|account| { + account.unvote = Some(validator); + account.vote = None; // cancel out any previous vote if one happened in same commit + Some(account) + }); + + events.push(make_event(ContractEventData::Unvoted { + voter, + validator, + })); + } + ConsensusV1Events::ValidatorRegistered( + events::ValidatorRegistered { + addr, + blsPublicKey: bls_public_key, + }, + ) => { + events.push(make_event( + ContractEventData::ValidatorRegistered { + addr, + bls_public_key, + }, + )); + } + ConsensusV1Events::ValidatorResigned( + events::ValidatorResigned { addr }, + ) => { + events.push(make_event( + ContractEventData::ValidatorResigned { addr }, + )); + } + ConsensusV1Events::ValidatorUpdated( + events::ValidatorUpdated { + addr, + blsPublicKey: bls_public_key, + }, + ) => { + events.push(make_event( + ContractEventData::ValidatorUpdated { + addr, + bls_public_key, + }, + )); + } } } _ if log.address == info.username_contract => { - // Attempt to decode log as a UsernameRegistered event - if let Ok(event) = - crate::events::UsernameRegistered::decode_log(&log) - { - dirty_accounts.get_mut(&event.addr).and_then(|account| { - account.username = Some(event.username.clone()); - account.username_resigned = false; // cancel out any previous resignation if one happened in same commit - Some(account) - }); - continue; - } - - // Attempt to decode log as a UsernameResigned event - if let Ok(event) = crate::events::UsernameResigned::decode_log(&log) - { - dirty_accounts.get_mut(&event.addr).and_then(|account| { - account.username = None; // cancel out any previous registration if one happened in same commit - account.username_resigned = true; - Some(account) - }); + let Ok(decoded) = UsernamesV1Events::decode_log(log) else { continue; + }; + + match decoded.data { + UsernamesV1Events::UsernameRegistered( + events::UsernameRegistered { + addr, + username, + previousUsername: previous_username, + }, + ) => { + dirty_accounts.get_mut(&addr).and_then(|account| { + account.username = Some(username.clone()); + account.username_resigned = false; // cancel out any previous resignation if one happened in same commit + Some(account) + }); + + events.push(make_event( + ContractEventData::UsernameRegistered { + addr, + username, + previous_username: (!previous_username.is_empty()) + .then_some(previous_username), + }, + )); + } + UsernamesV1Events::UsernameResigned( + events::UsernameResigned { addr, username }, + ) => { + dirty_accounts.get_mut(&addr).and_then(|account| { + account.username = None; // cancel out any previous registration if one happened in same commit + account.username_resigned = true; + Some(account) + }); + + events.push(make_event( + ContractEventData::UsernameResigned { addr, username }, + )); + } } } _ => (), // ignore } } - - // } ExecutionResult::Revert { .. } | ExecutionResult::Halt { .. } => (), // ignore } } } - dirty_accounts.into_values().collect() + (dirty_accounts.into_values().collect(), events) } #[cfg(test)] @@ -292,8 +355,11 @@ mod tests { use crate::{ db::{Error, GenesisInfo, PendingCommit, PersistentDB}, events, + events::{ContractEvent, ContractEventData}, state_changes::{AccountMergeInfo, AccountUpdate, StateChangeset}, - state_commit::{StateCommit, apply_rewards, build_commit, collect_dirty_accounts}, + state_commit::{ + StateCommit, apply_rewards, build_commit, collect_dirty_accounts_and_events, + }, }; use crate::{ legacy::{LegacyAccountAttributes, LegacyAddress}, @@ -404,6 +470,29 @@ mod tests { } .encode_log_data(), }, + Log { + address: genesis_info.validator_contract, + data: events::ValidatorRegistered { + addr: address!("0000000000000000000000000000000000000001"), + blsPublicKey: alloy_primitives::Bytes::from_static(&[0xaa; 48]), + } + .encode_log_data(), + }, + Log { + address: genesis_info.validator_contract, + data: events::ValidatorResigned { + addr: address!("0000000000000000000000000000000000000002"), + } + .encode_log_data(), + }, + Log { + address: genesis_info.validator_contract, + data: events::ValidatorUpdated { + addr: address!("0000000000000000000000000000000000000001"), + blsPublicKey: alloy_primitives::Bytes::from_static(&[0xbb; 48]), + } + .encode_log_data(), + }, Log { address: genesis_info.username_contract, data: events::UsernameRegistered { @@ -461,7 +550,8 @@ mod tests { ..Default::default() }; - let mut account_updates = collect_dirty_accounts(state, &Some(genesis_info)); + let (mut account_updates, events) = + collect_dirty_accounts_and_events(state, &Some(genesis_info)); account_updates.sort_by_key(|k| k.address); assert_eq!( @@ -494,6 +584,47 @@ mod tests { } ] ); + + let tx_hash = b256!("0000000000000000000000000000000000000000000000000000000000000001"); + let event = |data: ContractEventData| ContractEvent { + tx_hash, + tx_index: 0, + data, + }; + + assert_eq!( + events, + vec![ + event(ContractEventData::Voted { + voter: address!("0000000000000000000000000000000000000001"), + validator: address!("0000000000000000000000000000000000000002"), + }), + event(ContractEventData::Unvoted { + voter: address!("0000000000000000000000000000000000000002"), + validator: address!("0000000000000000000000000000000000000004"), + }), + event(ContractEventData::ValidatorRegistered { + addr: address!("0000000000000000000000000000000000000001"), + bls_public_key: alloy_primitives::Bytes::from_static(&[0xaa; 48]), + }), + event(ContractEventData::ValidatorResigned { + addr: address!("0000000000000000000000000000000000000002"), + }), + event(ContractEventData::ValidatorUpdated { + addr: address!("0000000000000000000000000000000000000001"), + bls_public_key: alloy_primitives::Bytes::from_static(&[0xbb; 48]), + }), + event(ContractEventData::UsernameRegistered { + addr: address!("0000000000000000000000000000000000000001"), + username: "test".into(), + previous_username: None, + }), + event(ContractEventData::UsernameResigned { + addr: address!("0000000000000000000000000000000000000002"), + username: "resigned".into(), + }), + ] + ); } #[test] @@ -512,9 +643,11 @@ mod tests { ..Default::default() }; - let mut account_updates = collect_dirty_accounts(state, &None); + let (mut account_updates, events) = collect_dirty_accounts_and_events(state, &None); account_updates.sort_by_key(|u| u.address); + assert!(events.is_empty()); + // A selfdestructed account must surface as a zeroed update — consumers (api-sync // wallet table) would otherwise keep the stale pre-destruction balance forever. assert_eq!( @@ -622,7 +755,8 @@ mod tests { ..Default::default() }; - let account_updates = collect_dirty_accounts(state, &Some(genesis_info)); + let (account_updates, events) = + collect_dirty_accounts_and_events(state, &Some(genesis_info)); assert_eq!( account_updates, @@ -637,6 +771,47 @@ mod tests { merge_info: None }] ); + + assert_eq!( + events, + vec![ + ContractEvent { + tx_hash: b256!( + "0000000000000000000000000000000000000000000000000000000000000004" + ), + tx_index: 0, + data: ContractEventData::Voted { voter, validator }, + }, + ContractEvent { + tx_hash: b256!( + "0000000000000000000000000000000000000000000000000000000000000003" + ), + tx_index: 1, + data: ContractEventData::Unvoted { voter, validator }, + }, + ContractEvent { + tx_hash: b256!( + "0000000000000000000000000000000000000000000000000000000000000002" + ), + tx_index: 2, + data: ContractEventData::UsernameRegistered { + addr: voter, + username: "test".into(), + previous_username: None, + }, + }, + ContractEvent { + tx_hash: b256!( + "0000000000000000000000000000000000000000000000000000000000000001" + ), + tx_index: 3, + data: ContractEventData::UsernameResigned { + addr: voter, + username: "test".into(), + }, + }, + ] + ); } #[test] diff --git a/packages/processor/source/block-processor.test.ts b/packages/processor/source/block-processor.test.ts new file mode 100644 index 0000000000..73d1f831c3 --- /dev/null +++ b/packages/processor/source/block-processor.test.ts @@ -0,0 +1,90 @@ +import { Events, Identifiers } from "@mainsail/constants"; +import type { Contracts } from "@mainsail/contracts"; +import { Application } from "@mainsail/kernel"; + +import { describe } from "@mainsail/test-runner"; +import { BlockProcessor } from "./block-processor"; + +describe<{ + app: Application; + blockProcessor: BlockProcessor; + events: any; + state: any; + unit: any; +}>("BlockProcessor", ({ beforeEach, it, assert, spy }) => { + const blockHash = "ab".repeat(32); + const voter = "0xBd6F65c58A46427AF4B257cBE231D0eD69eD5508"; + const validator = "0xEcC2717Ac3558141bFe0f512ACD5c62C5AB303C7"; + + const blockData = { hash: blockHash, number: 3 }; + const contractEvents: Contracts.Evm.ContractEvent[] = [ + { event: "Voted", txHash: "1".repeat(64), txIndex: 0, validator, voter }, + { addr: validator, event: "ValidatorResigned", txHash: "2".repeat(64), txIndex: 1 }, + ]; + + beforeEach((context) => { + const block = { + gasUsed: 0, + hash: blockHash, + number: 3, + round: 0, + toData: () => blockData, + transactions: [], + transactionsCount: 0, + }; + + context.unit = { + blockNumber: 3, + getBlock: () => block, + getCommit: async () => ({ block }), + getContractEvents: () => contractEvents, + round: 0, + }; + + context.events = { dispatch: async () => {} }; + context.state = { isBootstrap: () => false }; + + const app = new Application(); + app.bind(Identifiers.State.Store).toConstantValue({ onCommit: async () => {} }); + app.bind(Identifiers.State.State).toConstantValue(context.state); + app.bind(Identifiers.Cryptography.Configuration).toConstantValue({ getGenesisHeight: () => 0 }); + app.bind(Identifiers.BlockchainUtils.RoundCalculator).toConstantValue({ isNewRound: () => false }); + app.bind(Identifiers.Database.Service).toConstantValue({ onCommit: async () => {} }); + app.bind(Identifiers.Evm.Instance) + .toConstantValue({ onCommit: async () => {} }) + .whenTagged("instance", "evm"); + app.bind(Identifiers.Processor.TransactionProcessor).toConstantValue({}); + app.bind(Identifiers.Services.EventDispatcher.Service).toConstantValue(context.events); + app.bind(Identifiers.Services.Log.Service).toConstantValue({ debug: () => {}, info: () => {} }); + app.bind(Identifiers.ValidatorSet.Service).toConstantValue({ onCommit: async () => {} }); + app.bind(Identifiers.Processor.BlockVerifier).toConstantValue({}); + app.bind(Identifiers.TransactionPool.Worker).toConstantValue({ onCommit: async () => {} }); + app.bind(Identifiers.Evm.Worker).toConstantValue({ onCommit: async () => {} }); + app.bind(Identifiers.BlockchainUtils.FeeCalculator).toConstantValue({}); + app.bind(Identifiers.Cryptography.Hash.Factory).toConstantValue({}); + + context.app = app; + context.blockProcessor = app.resolve(BlockProcessor); + }); + + it("#commit - should emit block.applied enriched with the unit's contract events", async ({ + blockProcessor, + events, + unit, + }) => { + const dispatch = spy(events, "dispatch"); + + await blockProcessor.commit(unit); + + dispatch.calledWith(Events.BlockEvent.Applied, { ...blockData, contractEvents }); + }); + + it("#commit - should not emit block.applied during bootstrap", async ({ blockProcessor, events, state, unit }) => { + state.isBootstrap = () => true; + const dispatch = spy(events, "dispatch"); + + await blockProcessor.commit(unit); + + dispatch.neverCalled(); + }); +}); diff --git a/packages/processor/source/block-processor.ts b/packages/processor/source/block-processor.ts index 04f572c28f..9a40a775e7 100644 --- a/packages/processor/source/block-processor.ts +++ b/packages/processor/source/block-processor.ts @@ -149,7 +149,10 @@ export class BlockProcessor implements Contracts.Processor.BlockProcessor { this.#logBlockCommitted(unit); this.#logNewRound(unit); - void this.#emit(Events.BlockEvent.Applied, commit.block.toData()); + void this.#emit(Events.BlockEvent.Applied, { + ...commit.block.toData(), + contractEvents: unit.getContractEvents(), + }); } #logBlockCommitted(unit: Contracts.Processor.ProcessableUnit): void { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d06ee723c..d7368029f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,13 +16,13 @@ importers: devDependencies: '@eslint/compat': specifier: ^2.1.0 - version: 2.1.0(eslint@10.9.0) + version: 2.1.0(eslint@10.9.0(supports-color@7.2.0)) '@eslint/eslintrc': specifier: ^3.3.6 - version: 3.3.6 + version: 3.3.6(supports-color@7.2.0) '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.9.0) + version: 10.0.1(eslint@10.9.0(supports-color@7.2.0)) '@types/node': specifier: 24.13.3 version: 24.13.3 @@ -37,43 +37,43 @@ importers: version: 10.1.0 depcheck: specifier: 1.4.7 - version: 1.4.7 + version: 1.4.7(supports-color@7.2.0) eslint: specifier: 10.9.0 - version: 10.9.0 + version: 10.9.0(supports-color@7.2.0) eslint-plugin-import: specifier: 2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0)(typescript@6.0.3))(eslint@10.9.0) + version: 2.32.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0) eslint-plugin-perfectionist: specifier: 5.10.1 - version: 5.10.1(eslint@10.9.0)(typescript@6.0.3) + version: 5.10.1(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) eslint-plugin-prettier: specifier: 5.5.6 - version: 5.5.6(eslint@10.9.0)(prettier@3.9.6) + version: 5.5.6(eslint@10.9.0(supports-color@7.2.0))(prettier@3.9.6) eslint-plugin-promise: specifier: 7.3.0 - version: 7.3.0(eslint@10.9.0) + version: 7.3.0(eslint@10.9.0(supports-color@7.2.0)) eslint-plugin-sonarjs: specifier: 4.2.0 - version: 4.2.0(eslint@10.9.0) + version: 4.2.0(eslint@10.9.0(supports-color@7.2.0)) eslint-plugin-unicorn: specifier: 73.0.0 - version: 73.0.0(eslint@10.9.0) + version: 73.0.0(eslint@10.9.0(supports-color@7.2.0)) eslint-plugin-unused-imports: specifier: 4.4.1 - version: 4.4.1(@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0)(typescript@6.0.3))(eslint@10.9.0)(typescript@6.0.3))(eslint@10.9.0) + version: 4.4.1(@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.9.0(supports-color@7.2.0)) husky: specifier: 9.1.7 version: 9.1.7 lerna: specifier: 10.0.1 - version: 10.0.1(@types/node@24.13.3)(typescript@6.0.3) + version: 10.0.1(@types/node@24.13.3)(supports-color@7.2.0)(typescript@6.0.3) lint-staged: specifier: 17.3.0 version: 17.3.0 madge: specifier: 8.0.0 - version: 8.0.0(typescript@6.0.3) + version: 8.0.0(supports-color@7.2.0)(typescript@6.0.3) monocart-coverage-reports: specifier: 2.13.0 version: 2.13.0 @@ -97,13 +97,13 @@ importers: version: 6.0.3 typescript-eslint: specifier: ^8.67.0 - version: 8.67.0(eslint@10.9.0)(typescript@6.0.3) + version: 8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) typescript-native: specifier: npm:typescript@7.0.2 version: typescript@7.0.2 typesync: specifier: 0.14.3 - version: 0.14.3 + version: 0.14.3(supports-color@7.2.0) yaml: specifier: 2.9.0 version: 2.9.0 @@ -157,7 +157,7 @@ importers: version: 4.1.5 pm2: specifier: ^7.0.3 - version: 7.0.3 + version: 7.0.3(supports-color@7.2.0) devDependencies: '@mainsail/contracts': specifier: workspace:* @@ -273,7 +273,7 @@ importers: version: 8.23.0 typeorm: specifier: 1.1.0 - version: 1.1.0(better-sqlite3@12.11.1)(pg@8.23.0) + version: 1.1.0(better-sqlite3@12.11.1)(pg@8.23.0)(supports-color@7.2.0) devDependencies: '@mainsail/contracts': specifier: workspace:* @@ -1152,7 +1152,7 @@ importers: version: 4.1.5 pm2: specifier: ^7.0.3 - version: 7.0.3 + version: 7.0.3(supports-color@7.2.0) prompts: specifier: 2.4.2 version: 2.4.2 @@ -2025,7 +2025,7 @@ importers: dependencies: '@napi-rs/cli': specifier: 3.8.6 - version: 3.8.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@26.2.0)(emnapi@1.9.1(node-addon-api@8.9.2)) + version: 3.8.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@26.2.0)(emnapi@1.9.1(node-addon-api@8.9.2))(supports-color@7.2.0) optionalDependencies: '@mainsail/evm-linux-arm64-gnu': specifier: workspace:* @@ -2169,6 +2169,9 @@ importers: '@mainsail/crypto-wif': specifier: workspace:* version: link:../crypto-wif + '@mainsail/evm-contracts': + specifier: workspace:* + version: link:../evm-contracts '@mainsail/serializer': specifier: workspace:* version: link:../serializer @@ -2760,7 +2763,7 @@ importers: version: 0.2.7 typeorm: specifier: 1.1.0 - version: 1.1.0(better-sqlite3@12.11.1)(pg@8.23.0) + version: 1.1.0(better-sqlite3@12.11.1)(pg@8.23.0)(supports-color@7.2.0) devDependencies: '@mainsail/contracts': specifier: workspace:* @@ -3376,7 +3379,7 @@ importers: dependencies: express: specifier: 5.2.1 - version: 5.2.1 + version: 5.2.1(supports-color@7.2.0) got: specifier: 15.1.0 version: 15.1.0 @@ -3388,13 +3391,13 @@ importers: dependencies: express: specifier: 5.2.1 - version: 5.2.1 + version: 5.2.1(supports-color@7.2.0) tests/e2e/snapshot/checks: dependencies: express: specifier: 5.2.1 - version: 5.2.1 + version: 5.2.1(supports-color@7.2.0) got: specifier: 15.1.0 version: 15.1.0 @@ -3403,13 +3406,13 @@ importers: dependencies: express: specifier: 5.2.1 - version: 5.2.1 + version: 5.2.1(supports-color@7.2.0) tests/e2e/sync/checks: dependencies: express: specifier: 5.2.1 - version: 5.2.1 + version: 5.2.1(supports-color@7.2.0) got: specifier: 15.1.0 version: 15.1.0 @@ -10505,7 +10508,7 @@ snapshots: '@babel/parser': 7.29.8 '@babel/types': 7.29.8 - '@babel/traverse@7.29.8': + '@babel/traverse@7.29.8(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 @@ -10736,20 +10739,20 @@ snapshots: '@esbuild/win32-x64@0.28.2': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@10.9.0)': + '@eslint-community/eslint-utils@4.10.1(eslint@10.9.0(supports-color@7.2.0))': dependencies: - eslint: 10.9.0 + eslint: 10.9.0(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/compat@2.1.0(eslint@10.9.0)': + '@eslint/compat@2.1.0(eslint@10.9.0(supports-color@7.2.0))': dependencies: '@eslint/core': 1.2.1 optionalDependencies: - eslint: 10.9.0 + eslint: 10.9.0(supports-color@7.2.0) - '@eslint/config-array@0.23.5': + '@eslint/config-array@0.23.5(supports-color@7.2.0)': dependencies: '@eslint/object-schema': 3.0.5 debug: 4.4.3(supports-color@7.2.0) @@ -10770,7 +10773,7 @@ snapshots: mdn-data: 2.29.0 source-map-js: 1.2.1 - '@eslint/eslintrc@3.3.6': + '@eslint/eslintrc@3.3.6(supports-color@7.2.0)': dependencies: ajv: 6.15.0 debug: 4.4.3(supports-color@7.2.0) @@ -10784,9 +10787,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@10.0.1(eslint@10.9.0)': + '@eslint/js@10.0.1(eslint@10.9.0(supports-color@7.2.0))': optionalDependencies: - eslint: 10.9.0 + eslint: 10.9.0(supports-color@7.2.0) '@eslint/object-schema@3.0.5': {} @@ -11377,10 +11380,10 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@napi-rs/cli@3.8.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@26.2.0)(emnapi@1.9.1(node-addon-api@8.9.2))': + '@napi-rs/cli@3.8.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(@types/node@26.2.0)(emnapi@1.9.1(node-addon-api@8.9.2))(supports-color@7.2.0)': dependencies: '@inquirer/prompts': 8.6.0(@types/node@26.2.0) - '@napi-rs/cross-toolchain': 1.0.3 + '@napi-rs/cross-toolchain': 1.0.3(supports-color@7.2.0) '@napi-rs/wasm-tools': 1.1.0 '@octokit/rest': 22.0.1 clipanion: 4.0.0-rc.4(typanion@3.14.0) @@ -11409,7 +11412,7 @@ snapshots: - '@types/node' - supports-color - '@napi-rs/cross-toolchain@1.0.3': + '@napi-rs/cross-toolchain@1.0.3(supports-color@7.2.0)': dependencies: '@napi-rs/lzma': 1.5.1 '@napi-rs/tar': 1.1.1 @@ -11684,33 +11687,33 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@npmcli/agent@3.0.0': + '@npmcli/agent@3.0.0(supports-color@7.2.0)': dependencies: agent-base: 7.1.4 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + http-proxy-agent: 7.0.2(supports-color@7.2.0) + https-proxy-agent: 7.0.6(supports-color@7.2.0) lru-cache: 10.4.3 - socks-proxy-agent: 8.0.5 + socks-proxy-agent: 8.0.5(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@npmcli/agent@4.0.2': + '@npmcli/agent@4.0.2(supports-color@7.2.0)': dependencies: agent-base: 7.1.4 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + http-proxy-agent: 7.0.2(supports-color@7.2.0) + https-proxy-agent: 7.0.6(supports-color@7.2.0) lru-cache: 11.5.2 - socks-proxy-agent: 8.0.5 + socks-proxy-agent: 8.0.5(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@npmcli/arborist@9.1.6': + '@npmcli/arborist@9.1.6(supports-color@7.2.0)': dependencies: '@isaacs/string-locale-compare': 1.1.0 '@npmcli/fs': 4.0.0 '@npmcli/installed-package-contents': 3.0.0 '@npmcli/map-workspaces': 5.0.3 - '@npmcli/metavuln-calculator': 9.0.3 + '@npmcli/metavuln-calculator': 9.0.3(supports-color@7.2.0) '@npmcli/name-from-folder': 3.0.0 '@npmcli/node-gyp': 4.0.0 '@npmcli/package-json': 7.0.5 @@ -11728,8 +11731,8 @@ snapshots: npm-install-checks: 7.1.2 npm-package-arg: 13.0.2 npm-pick-manifest: 11.0.3 - npm-registry-fetch: 19.1.1 - pacote: 21.5.1 + npm-registry-fetch: 19.1.1(supports-color@7.2.0) + pacote: 21.5.1(supports-color@7.2.0) parse-conflict-json: 4.0.0 proc-log: 5.0.0 proggy: 3.0.0 @@ -11789,11 +11792,11 @@ snapshots: glob: 13.0.6 minimatch: 10.2.6 - '@npmcli/metavuln-calculator@9.0.3': + '@npmcli/metavuln-calculator@9.0.3(supports-color@7.2.0)': dependencies: cacache: 20.0.4 json-parse-even-better-errors: 5.0.0 - pacote: 21.5.1 + pacote: 21.5.1(supports-color@7.2.0) proc-log: 6.1.0 semver: 7.8.5 transitivePeerDependencies: @@ -12052,19 +12055,19 @@ snapshots: '@pm2/blessed@0.1.81': {} - '@pm2/js-api@0.8.1': + '@pm2/js-api@0.8.1(supports-color@7.2.0)': dependencies: async: 2.6.4 - debug: 4.3.7 + debug: 4.3.7(supports-color@7.2.0) eventemitter2: 6.4.9 - extrareqp2: 1.0.0(debug@4.3.7) + extrareqp2: 1.0.0(debug@4.3.7(supports-color@7.2.0)) ws: 8.21.3 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@pm2/pm2-version-check@1.0.4': + '@pm2/pm2-version-check@1.0.4(supports-color@7.2.0)': dependencies: debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: @@ -12152,21 +12155,21 @@ snapshots: '@sigstore/protobuf-specs@0.5.2': {} - '@sigstore/sign@4.1.1': + '@sigstore/sign@4.1.1(supports-color@7.2.0)': dependencies: '@gar/promise-retry': 1.0.3 '@sigstore/bundle': 4.0.0 '@sigstore/core': 3.2.1 '@sigstore/protobuf-specs': 0.5.2 - make-fetch-happen: 15.0.6 + make-fetch-happen: 15.0.6(supports-color@7.2.0) proc-log: 6.1.0 transitivePeerDependencies: - supports-color - '@sigstore/tuf@4.0.2': + '@sigstore/tuf@4.0.2(supports-color@7.2.0)': dependencies: '@sigstore/protobuf-specs': 0.5.2 - tuf-js: 4.1.0 + tuf-js: 4.1.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -12382,15 +12385,15 @@ snapshots: '@types/yargs-parser@21.0.3': {} - '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0)(typescript@6.0.3))(eslint@10.9.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.67.0(eslint@10.9.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/type-utils': 8.67.0(eslint@10.9.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.9.0)(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.67.0 - eslint: 10.9.0 + eslint: 10.9.0(supports-color@7.2.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -12398,19 +12401,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.67.0(eslint@10.9.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.67.0 '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3(supports-color@7.2.0) - eslint: 10.9.0 + eslint: 10.9.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.67.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.67.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.9.3) '@typescript-eslint/types': 8.67.0 @@ -12419,7 +12422,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.67.0(typescript@6.0.3)': + '@typescript-eslint/project-service@8.67.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) '@typescript-eslint/types': 8.67.0 @@ -12441,13 +12444,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.67.0(eslint@10.9.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.9.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) debug: 4.4.3(supports-color@7.2.0) - eslint: 10.9.0 + eslint: 10.9.0(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -12455,9 +12458,9 @@ snapshots: '@typescript-eslint/types@8.67.0': {} - '@typescript-eslint/typescript-estree@8.67.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.67.0(supports-color@7.2.0)(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.67.0(typescript@5.9.3) + '@typescript-eslint/project-service': 8.67.0(supports-color@7.2.0)(typescript@5.9.3) '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@5.9.3) '@typescript-eslint/types': 8.67.0 '@typescript-eslint/visitor-keys': 8.67.0 @@ -12470,9 +12473,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.67.0(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.67.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.67.0(typescript@6.0.3) + '@typescript-eslint/project-service': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) '@typescript-eslint/types': 8.67.0 '@typescript-eslint/visitor-keys': 8.67.0 @@ -12485,13 +12488,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.67.0(eslint@10.9.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(supports-color@7.2.0)) '@typescript-eslint/scope-manager': 8.67.0 '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) - eslint: 10.9.0 + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.9.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -12892,7 +12895,7 @@ snapshots: bluebird@3.7.2: {} - body-parser@2.3.0: + body-parser@2.3.0(supports-color@7.2.0): dependencies: bytes: 3.1.2 content-type: 2.1.0 @@ -13391,13 +13394,17 @@ snapshots: dayjs@1.11.23: {} - debug@3.2.7: + debug@3.2.7(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 - debug@4.3.7: + debug@4.3.7(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 debug@4.4.3(supports-color@7.2.0): dependencies: @@ -13461,10 +13468,10 @@ snapshots: delayed-stream@1.0.0: {} - depcheck@1.4.7: + depcheck@1.4.7(supports-color@7.2.0): dependencies: '@babel/parser': 7.29.8 - '@babel/traverse': 7.29.8 + '@babel/traverse': 7.29.8(supports-color@7.2.0) '@vue/compiler-sfc': 3.5.41 callsite: 1.0.0 camelcase: 6.3.0 @@ -13491,12 +13498,12 @@ snapshots: depd@2.0.0: {} - dependency-tree@11.5.0: + dependency-tree@11.5.0(supports-color@7.2.0): dependencies: '@discoveryjs/json-ext': 1.1.0 commander: 12.1.0 filing-cabinet: 5.5.1 - precinct: 12.3.2 + precinct: 12.3.2(supports-color@7.2.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -13549,16 +13556,16 @@ snapshots: detective-stylus@5.0.1: {} - detective-typescript@14.1.2(typescript@5.9.3): + detective-typescript@14.1.2(supports-color@7.2.0)(typescript@5.9.3): dependencies: - '@typescript-eslint/typescript-estree': 8.67.0(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@5.9.3) ast-module-types: 6.0.2 node-source-walk: 7.0.2 typescript: 5.9.3 transitivePeerDependencies: - supports-color - detective-vue2@2.3.0(typescript@5.9.3): + detective-vue2@2.3.0(supports-color@7.2.0)(typescript@5.9.3): dependencies: '@dependents/detective-less': 5.0.3 '@vue/compiler-sfc': 3.5.41 @@ -13566,7 +13573,7 @@ snapshots: detective-sass: 6.0.2 detective-scss: 5.0.2 detective-stylus: 5.0.1 - detective-typescript: 14.1.2(typescript@5.9.3) + detective-typescript: 14.1.2(supports-color@7.2.0)(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -13833,36 +13840,36 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-import-resolver-node@0.3.10: + eslint-import-resolver-node@0.3.10(supports-color@7.2.0): dependencies: - debug: 3.2.7 + debug: 3.2.7(supports-color@7.2.0) is-core-module: 2.16.2 resolve: 2.0.0-next.7 transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.9.0): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: - debug: 3.2.7 + debug: 3.2.7(supports-color@7.2.0) optionalDependencies: - '@typescript-eslint/parser': 8.67.0(eslint@10.9.0)(typescript@6.0.3) - eslint: 10.9.0 - eslint-import-resolver-node: 0.3.10 + '@typescript-eslint/parser': 8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.9.0(supports-color@7.2.0) + eslint-import-resolver-node: 0.3.10(supports-color@7.2.0) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0)(typescript@6.0.3))(eslint@10.9.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 - debug: 3.2.7 + debug: 3.2.7(supports-color@7.2.0) doctrine: 2.1.0 - eslint: 10.9.0 - eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.9.0) + eslint: 10.9.0(supports-color@7.2.0) + eslint-import-resolver-node: 0.3.10(supports-color@7.2.0) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10(supports-color@7.2.0))(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -13874,39 +13881,39 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.67.0(eslint@10.9.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-perfectionist@5.10.1(eslint@10.9.0)(typescript@6.0.3): + eslint-plugin-perfectionist@5.10.1(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/utils': 8.67.0(eslint@10.9.0)(typescript@6.0.3) - eslint: 10.9.0 + '@typescript-eslint/utils': 8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.9.0(supports-color@7.2.0) natural-orderby: 5.0.0 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-prettier@5.5.6(eslint@10.9.0)(prettier@3.9.6): + eslint-plugin-prettier@5.5.6(eslint@10.9.0(supports-color@7.2.0))(prettier@3.9.6): dependencies: - eslint: 10.9.0 + eslint: 10.9.0(supports-color@7.2.0) prettier: 3.9.6 prettier-linter-helpers: 1.0.1 synckit: 0.11.13 - eslint-plugin-promise@7.3.0(eslint@10.9.0): + eslint-plugin-promise@7.3.0(eslint@10.9.0(supports-color@7.2.0)): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0) - eslint: 10.9.0 + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(supports-color@7.2.0)) + eslint: 10.9.0(supports-color@7.2.0) - eslint-plugin-sonarjs@4.2.0(eslint@10.9.0): + eslint-plugin-sonarjs@4.2.0(eslint@10.9.0(supports-color@7.2.0)): dependencies: '@eslint-community/regexpp': 4.12.2 builtin-modules: 3.3.0 bytes: 3.1.2 - eslint: 10.9.0 + eslint: 10.9.0(supports-color@7.2.0) functional-red-black-tree: 1.0.1 globals: 17.11.0 jsx-ast-utils-x: 0.1.0 @@ -13918,9 +13925,9 @@ snapshots: typescript: 6.0.3 yaml: 2.9.0 - eslint-plugin-unicorn@73.0.0(eslint@10.9.0): + eslint-plugin-unicorn@73.0.0(eslint@10.9.0(supports-color@7.2.0)): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(supports-color@7.2.0)) '@eslint/css-tree': 4.0.5 browserslist: 4.28.8 change-case: 5.4.4 @@ -13928,7 +13935,7 @@ snapshots: core-js-compat: 3.50.0 detect-indent: 7.0.2 entities: 4.5.0 - eslint: 10.9.0 + eslint: 10.9.0(supports-color@7.2.0) find-up-simple: 1.0.1 globals: 17.11.0 indent-string: 5.0.0 @@ -13942,11 +13949,11 @@ snapshots: strip-indent: 4.1.1 yaml: 2.9.0 - eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0)(typescript@6.0.3))(eslint@10.9.0)(typescript@6.0.3))(eslint@10.9.0): + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.9.0(supports-color@7.2.0)): dependencies: - eslint: 10.9.0 + eslint: 10.9.0(supports-color@7.2.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0)(typescript@6.0.3))(eslint@10.9.0)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) eslint-scope@9.1.2: dependencies: @@ -13961,11 +13968,11 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.9.0: + eslint@10.9.0(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.9.0(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.5 + '@eslint/config-array': 0.23.5(supports-color@7.2.0) '@eslint/config-helpers': 0.7.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.2 @@ -14111,10 +14118,10 @@ snapshots: exponential-backoff@3.1.3: {} - express@5.2.1: + express@5.2.1(supports-color@7.2.0): dependencies: accepts: 2.0.0 - body-parser: 2.3.0 + body-parser: 2.3.0(supports-color@7.2.0) content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 @@ -14124,7 +14131,7 @@ snapshots: encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1 + finalhandler: 2.1.1(supports-color@7.2.0) fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -14135,9 +14142,9 @@ snapshots: proxy-addr: 2.0.7 qs: 6.15.3 range-parser: 1.3.0 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 + router: 2.2.0(supports-color@7.2.0) + send: 1.2.1(supports-color@7.2.0) + serve-static: 2.2.1(supports-color@7.2.0) statuses: 2.0.2 type-is: 2.1.0 vary: 1.1.2 @@ -14148,9 +14155,9 @@ snapshots: dependencies: type: 2.7.3 - extrareqp2@1.0.0(debug@4.3.7): + extrareqp2@1.0.0(debug@4.3.7(supports-color@7.2.0)): dependencies: - follow-redirects: 1.16.0(debug@4.3.7) + follow-redirects: 1.16.0(debug@4.3.7(supports-color@7.2.0)) transitivePeerDependencies: - debug @@ -14249,7 +14256,7 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@2.1.1: + finalhandler@2.1.1(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 @@ -14288,9 +14295,9 @@ snapshots: flatted@3.4.4: {} - follow-redirects@1.16.0(debug@4.3.7): + follow-redirects@1.16.0(debug@4.3.7(supports-color@7.2.0)): optionalDependencies: - debug: 4.3.7 + debug: 4.3.7(supports-color@7.2.0) follow-redirects@1.16.0(debug@4.4.3(supports-color@7.2.0)): optionalDependencies: @@ -14405,7 +14412,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-uri@6.0.5: + get-uri@6.0.5(supports-color@7.2.0): dependencies: basic-ftp: 5.3.1 data-uri-to-buffer: 6.0.2 @@ -14577,7 +14584,7 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-proxy-agent@7.0.2: + http-proxy-agent@7.0.2(supports-color@7.2.0): dependencies: agent-base: 7.1.4 debug: 4.4.3(supports-color@7.2.0) @@ -14596,7 +14603,7 @@ snapshots: transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@7.2.0): dependencies: agent-base: 7.1.4 debug: 4.4.3(supports-color@7.2.0) @@ -15038,9 +15045,9 @@ snapshots: dependencies: package-json: 10.0.1 - lerna@10.0.1(@types/node@24.13.3)(typescript@6.0.3): + lerna@10.0.1(@types/node@24.13.3)(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@npmcli/arborist': 9.1.6 + '@npmcli/arborist': 9.1.6(supports-color@7.2.0) '@npmcli/package-json': 7.0.2 '@npmcli/run-script': 10.0.3 '@nx/devkit': 23.1.1(nx@23.1.1) @@ -15066,18 +15073,18 @@ snapshots: init-package-json: 8.2.2 inquirer: 12.9.6(@types/node@24.13.3) js-yaml: 4.3.1 - libnpmaccess: 10.0.3 - libnpmpublish: 11.1.2 + libnpmaccess: 10.0.3(supports-color@7.2.0) + libnpmpublish: 11.1.2(supports-color@7.2.0) load-json-file: 6.2.0 - make-fetch-happen: 15.0.2 + make-fetch-happen: 15.0.2(supports-color@7.2.0) minimatch: 3.1.4 npm-package-arg: 13.0.1 npm-packlist: 10.0.3 - npm-registry-fetch: 19.1.0 + npm-registry-fetch: 19.1.0(supports-color@7.2.0) nx: 23.1.1 p-map: 4.0.0 p-queue: 6.6.2 - pacote: 21.0.1 + pacote: 21.0.1(supports-color@7.2.0) read-cmd-shim: 4.0.0 semver: 7.7.2 signal-exit: 3.0.7 @@ -15102,22 +15109,22 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libnpmaccess@10.0.3: + libnpmaccess@10.0.3(supports-color@7.2.0): dependencies: npm-package-arg: 13.0.2 - npm-registry-fetch: 19.1.1 + npm-registry-fetch: 19.1.1(supports-color@7.2.0) transitivePeerDependencies: - supports-color - libnpmpublish@11.1.2: + libnpmpublish@11.1.2(supports-color@7.2.0): dependencies: '@npmcli/package-json': 7.0.5 ci-info: 4.4.0 npm-package-arg: 13.0.2 - npm-registry-fetch: 19.1.1 + npm-registry-fetch: 19.1.1(supports-color@7.2.0) proc-log: 5.0.0 semver: 7.8.5 - sigstore: 4.1.1 + sigstore: 4.1.1(supports-color@7.2.0) ssri: 12.0.0 transitivePeerDependencies: - supports-color @@ -15271,13 +15278,13 @@ snapshots: lz-utils@2.1.1: {} - madge@8.0.0(typescript@6.0.3): + madge@8.0.0(supports-color@7.2.0)(typescript@6.0.3): dependencies: chalk: 4.1.2 commander: 7.2.0 commondir: 1.0.1 debug: 4.4.3(supports-color@7.2.0) - dependency-tree: 11.5.0 + dependency-tree: 11.5.0(supports-color@7.2.0) ora: 5.4.1 pluralize: 8.0.0 pretty-ms: 7.0.1 @@ -15304,9 +15311,9 @@ snapshots: dependencies: semver: 7.8.5 - make-fetch-happen@14.0.3: + make-fetch-happen@14.0.3(supports-color@7.2.0): dependencies: - '@npmcli/agent': 3.0.0 + '@npmcli/agent': 3.0.0(supports-color@7.2.0) cacache: 19.0.1 http-cache-semantics: 4.2.0 minipass: 7.1.3 @@ -15320,9 +15327,9 @@ snapshots: transitivePeerDependencies: - supports-color - make-fetch-happen@15.0.2: + make-fetch-happen@15.0.2(supports-color@7.2.0): dependencies: - '@npmcli/agent': 4.0.2 + '@npmcli/agent': 4.0.2(supports-color@7.2.0) cacache: 20.0.4 http-cache-semantics: 4.2.0 minipass: 7.1.3 @@ -15336,10 +15343,10 @@ snapshots: transitivePeerDependencies: - supports-color - make-fetch-happen@15.0.6: + make-fetch-happen@15.0.6(supports-color@7.2.0): dependencies: '@gar/promise-retry': 1.0.3 - '@npmcli/agent': 4.0.2 + '@npmcli/agent': 4.0.2(supports-color@7.2.0) '@npmcli/redact': 4.0.0 cacache: 20.0.4 http-cache-semantics: 4.2.0 @@ -15720,11 +15727,11 @@ snapshots: npm-package-arg: 13.0.2 semver: 7.8.5 - npm-registry-fetch@18.0.2: + npm-registry-fetch@18.0.2(supports-color@7.2.0): dependencies: '@npmcli/redact': 3.2.2 jsonparse: 1.3.1 - make-fetch-happen: 14.0.3 + make-fetch-happen: 14.0.3(supports-color@7.2.0) minipass: 7.1.3 minipass-fetch: 4.0.1 minizlib: 3.1.0 @@ -15733,11 +15740,11 @@ snapshots: transitivePeerDependencies: - supports-color - npm-registry-fetch@19.1.0: + npm-registry-fetch@19.1.0(supports-color@7.2.0): dependencies: '@npmcli/redact': 3.2.2 jsonparse: 1.3.1 - make-fetch-happen: 15.0.6 + make-fetch-happen: 15.0.6(supports-color@7.2.0) minipass: 7.1.3 minipass-fetch: 4.0.1 minizlib: 3.1.0 @@ -15746,11 +15753,11 @@ snapshots: transitivePeerDependencies: - supports-color - npm-registry-fetch@19.1.1: + npm-registry-fetch@19.1.1(supports-color@7.2.0): dependencies: '@npmcli/redact': 4.0.0 jsonparse: 1.3.1 - make-fetch-happen: 15.0.6 + make-fetch-happen: 15.0.6(supports-color@7.2.0) minipass: 7.1.3 minipass-fetch: 5.0.2 minizlib: 3.1.0 @@ -16078,16 +16085,16 @@ snapshots: p-try@2.2.0: {} - pac-proxy-agent@7.2.0: + pac-proxy-agent@7.2.0(supports-color@7.2.0): dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.4 debug: 4.4.3(supports-color@7.2.0) - get-uri: 6.0.5 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + get-uri: 6.0.5(supports-color@7.2.0) + http-proxy-agent: 7.0.2(supports-color@7.2.0) + https-proxy-agent: 7.0.6(supports-color@7.2.0) pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.5 + socks-proxy-agent: 8.0.5(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -16105,7 +16112,7 @@ snapshots: registry-url: 6.0.1 semver: 7.8.5 - pacote@21.0.1: + pacote@21.0.1(supports-color@7.2.0): dependencies: '@npmcli/git': 6.0.3 '@npmcli/installed-package-contents': 3.0.0 @@ -16118,16 +16125,16 @@ snapshots: npm-package-arg: 13.0.2 npm-packlist: 10.0.4 npm-pick-manifest: 10.0.0 - npm-registry-fetch: 19.1.1 + npm-registry-fetch: 19.1.1(supports-color@7.2.0) proc-log: 5.0.0 promise-retry: 2.0.1 - sigstore: 4.1.1 + sigstore: 4.1.1(supports-color@7.2.0) ssri: 12.0.0 tar: 7.5.22 transitivePeerDependencies: - supports-color - pacote@21.5.1: + pacote@21.5.1(supports-color@7.2.0): dependencies: '@gar/promise-retry': 1.0.3 '@npmcli/git': 7.0.2 @@ -16141,9 +16148,9 @@ snapshots: npm-package-arg: 13.0.2 npm-packlist: 10.0.4 npm-pick-manifest: 11.0.3 - npm-registry-fetch: 19.1.1 + npm-registry-fetch: 19.1.1(supports-color@7.2.0) proc-log: 6.1.0 - sigstore: 4.1.1 + sigstore: 4.1.1(supports-color@7.2.0) ssri: 13.0.1 tar: 7.5.22 transitivePeerDependencies: @@ -16302,11 +16309,11 @@ snapshots: run-series: 1.1.9 tv4: 1.3.0 - pm2@7.0.3: + pm2@7.0.3(supports-color@7.2.0): dependencies: '@pm2/blessed': 0.1.81 - '@pm2/js-api': 0.8.1 - '@pm2/pm2-version-check': 1.0.4 + '@pm2/js-api': 0.8.1(supports-color@7.2.0) + '@pm2/pm2-version-check': 1.0.4(supports-color@7.2.0) amp: 0.3.1 amp-message: 0.1.2 ansis: 4.0.0-node10 @@ -16322,7 +16329,7 @@ snapshots: js-yaml: 4.3.1 pidusage: 4.0.1 pm2-deploy: 1.0.2 - proxy-agent: 6.5.0 + proxy-agent: 6.5.0(supports-color@7.2.0) semver: 7.7.2 tx2: 1.0.5 ws: 8.21.0 @@ -16376,7 +16383,7 @@ snapshots: tar-fs: 2.1.5 tunnel-agent: 0.6.0 - precinct@12.3.2: + precinct@12.3.2(supports-color@7.2.0): dependencies: '@dependents/detective-less': 5.0.3 commander: 12.1.0 @@ -16387,8 +16394,8 @@ snapshots: detective-sass: 6.0.2 detective-scss: 5.0.2 detective-stylus: 5.0.1 - detective-typescript: 14.1.2(typescript@5.9.3) - detective-vue2: 2.3.0(typescript@5.9.3) + detective-typescript: 14.1.2(supports-color@7.2.0)(typescript@5.9.3) + detective-vue2: 2.3.0(supports-color@7.2.0)(typescript@5.9.3) module-definition: 6.0.2 node-source-walk: 7.0.2 postcss: 8.5.26 @@ -16466,16 +16473,16 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-agent@6.5.0: + proxy-agent@6.5.0(supports-color@7.2.0): dependencies: agent-base: 7.1.4 debug: 4.4.3(supports-color@7.2.0) - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + http-proxy-agent: 7.0.2(supports-color@7.2.0) + https-proxy-agent: 7.0.6(supports-color@7.2.0) lru-cache: 7.18.3 - pac-proxy-agent: 7.2.0 + pac-proxy-agent: 7.2.0(supports-color@7.2.0) proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.5 + socks-proxy-agent: 8.0.5(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -16699,7 +16706,7 @@ snapshots: rotating-file-stream@3.2.10: {} - router@2.2.0: + router@2.2.0(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) depd: 2.0.0 @@ -16781,7 +16788,7 @@ snapshots: semver@7.8.5: {} - send@1.2.1: + send@1.2.1(supports-color@7.2.0): dependencies: debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 @@ -16797,12 +16804,12 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@2.2.1: + serve-static@2.2.1(supports-color@7.2.0): dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1 + send: 1.2.1(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -16872,13 +16879,13 @@ snapshots: signal-exit@4.1.0: {} - sigstore@4.1.1: + sigstore@4.1.1(supports-color@7.2.0): dependencies: '@sigstore/bundle': 4.0.0 '@sigstore/core': 3.2.1 '@sigstore/protobuf-specs': 0.5.2 - '@sigstore/sign': 4.1.1 - '@sigstore/tuf': 4.0.2 + '@sigstore/sign': 4.1.1(supports-color@7.2.0) + '@sigstore/tuf': 4.0.2(supports-color@7.2.0) '@sigstore/verify': 3.1.1 transitivePeerDependencies: - supports-color @@ -16906,7 +16913,7 @@ snapshots: smol-toml@1.6.1: {} - socks-proxy-agent@8.0.5: + socks-proxy-agent@8.0.5(supports-color@7.2.0): dependencies: agent-base: 7.1.4 debug: 4.4.3(supports-color@7.2.0) @@ -17229,11 +17236,11 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - tuf-js@4.1.0: + tuf-js@4.1.0(supports-color@7.2.0): dependencies: '@tufjs/models': 4.1.0 debug: 4.4.3(supports-color@7.2.0) - make-fetch-happen: 15.0.6 + make-fetch-happen: 15.0.6(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -17315,7 +17322,7 @@ snapshots: typescript: 6.0.3 yaml: 2.9.0 - typeorm@1.1.0(better-sqlite3@12.11.1)(pg@8.23.0): + typeorm@1.1.0(better-sqlite3@12.11.1)(pg@8.23.0)(supports-color@7.2.0): dependencies: '@sqltools/formatter': 1.2.5 ansis: 4.3.1 @@ -17334,13 +17341,13 @@ snapshots: - babel-plugin-macros - supports-color - typescript-eslint@8.67.0(eslint@10.9.0)(typescript@6.0.3): + typescript-eslint@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0)(typescript@6.0.3))(eslint@10.9.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.67.0(eslint@10.9.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.67.0(typescript@6.0.3) - '@typescript-eslint/utils': 8.67.0(eslint@10.9.0)(typescript@6.0.3) - eslint: 10.9.0 + '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.9.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + eslint: 10.9.0(supports-color@7.2.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -17372,13 +17379,13 @@ snapshots: '@typescript/typescript-win32-arm64': 7.0.2 '@typescript/typescript-win32-x64': 7.0.2 - typesync@0.14.3: + typesync@0.14.3(supports-color@7.2.0): dependencies: ansis: 3.17.0 awilix: 12.1.1 detect-indent: 7.0.2 lilconfig: 3.1.3 - npm-registry-fetch: 18.0.2 + npm-registry-fetch: 18.0.2(supports-color@7.2.0) picospinner: 3.1.2 semver: 7.8.5 tinyglobby: 0.2.17