mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
## Summary Reorganizes the test directory structure for better clarity and maintainability: - **Rename `test/datahaven/` → `test/moonwall/`**: Clearly identifies these as Moonwall single-node tests - **Move `test/framework/` → `test/e2e/framework/`**: Groups e2e test utilities under a dedicated folder - **Move `test/suites/` → `test/e2e/suites/`**: Groups e2e test suites with the framework - **Add `test/e2e/framework/validators.ts`**: Extracts validator test helpers from utils into the e2e framework - **Update documentation**: README.md and E2E_FRAMEWORK_OVERVIEW.md reflect the new structure ### New Directory Structure ``` test/ ├── e2e/ │ ├── suites/ # E2E test suites (Kurtosis-based) │ └── framework/ # E2E test utilities & helpers ├── moonwall/ # Moonwall single-node tests ├── launcher/ # Network deployment tools └── ... ``` --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
159 lines
4.8 KiB
TypeScript
159 lines
4.8 KiB
TypeScript
/**
|
|
* EVM-related helper utilities for DataHaven tests
|
|
* Adapted from Moonbeam test helpers
|
|
*/
|
|
|
|
import { type DevModeContext, expect } from "@moonwall/cli";
|
|
import type { EventRecord } from "@polkadot/types/interfaces";
|
|
import type {
|
|
EvmCoreErrorExitError,
|
|
EvmCoreErrorExitFatal,
|
|
EvmCoreErrorExitReason,
|
|
EvmCoreErrorExitRevert,
|
|
EvmCoreErrorExitSucceed
|
|
} from "@polkadot/types/lookup";
|
|
|
|
export type Errors = {
|
|
Succeed: EvmCoreErrorExitSucceed["type"];
|
|
Error: EvmCoreErrorExitError["type"];
|
|
Revert: EvmCoreErrorExitRevert["type"];
|
|
Fatal: EvmCoreErrorExitFatal["type"];
|
|
};
|
|
|
|
/**
|
|
* Validate EVM execution result from Ethereum.Executed events
|
|
*
|
|
* @param events - Array of event records from block execution
|
|
* @param resultType - Expected result type (Succeed, Error, Revert, Fatal)
|
|
* @param reason - Optional specific reason within the result type
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* expectEVMResult(result.events, "Succeed");
|
|
* expectEVMResult(result.events, "Revert", "Reverted");
|
|
* ```
|
|
*/
|
|
export function expectEVMResult<T extends Errors, Type extends keyof T>(
|
|
events: EventRecord[],
|
|
resultType: Type,
|
|
reason?: T[Type]
|
|
) {
|
|
expect(events, "Missing events, probably failed execution").toHaveLength;
|
|
expect(events.length).toBeGreaterThan(0);
|
|
|
|
const ethereumExecuted = events.find(
|
|
({ event: { section, method } }) => section === "ethereum" && method === "Executed"
|
|
);
|
|
|
|
expect(ethereumExecuted, "Ethereum.Executed event not found").toBeDefined();
|
|
|
|
const ethereumResult = ethereumExecuted!.event.data[3] as EvmCoreErrorExitReason;
|
|
|
|
const _foundReason = ethereumResult.isError
|
|
? ethereumResult.asError.type
|
|
: ethereumResult.isFatal
|
|
? ethereumResult.asFatal.type
|
|
: ethereumResult.isRevert
|
|
? ethereumResult.asRevert.type
|
|
: ethereumResult.asSucceed.type;
|
|
|
|
expect(ethereumResult.type).toBe(resultType);
|
|
|
|
if (reason) {
|
|
if (ethereumResult.isError) {
|
|
expect(ethereumResult.asError.type).toBe(reason);
|
|
} else if (ethereumResult.isFatal) {
|
|
expect(ethereumResult.asFatal.type).toBe(reason);
|
|
} else if (ethereumResult.isRevert) {
|
|
expect(ethereumResult.asRevert.type).toBe(reason);
|
|
} else {
|
|
expect(ethereumResult.asSucceed.type).toBe(reason);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract signature parameters (r, s, v) from a hex signature string
|
|
*
|
|
* @param signature - Hex signature string
|
|
* @returns Object containing r, s, v components
|
|
*/
|
|
export function getSignatureParameters(signature: string): {
|
|
r: string;
|
|
s: string;
|
|
v: number;
|
|
} {
|
|
const r = signature.slice(0, 66); // 32 bytes
|
|
const s = `0x${signature.slice(66, 130)}`; // 32 bytes
|
|
let v = Number.parseInt(signature.slice(130, 132), 16); // 1 byte
|
|
|
|
if (![27, 28].includes(v)) {
|
|
v += 27;
|
|
}
|
|
|
|
return { r, s, v };
|
|
}
|
|
|
|
/**
|
|
* Get transaction receipt with retry logic for async block production
|
|
*
|
|
* @param context - Moonwall dev context
|
|
* @param hash - Transaction hash
|
|
* @param options - Retry configuration
|
|
* @returns Transaction receipt
|
|
*/
|
|
export async function getTransactionReceiptWithRetry(
|
|
context: DevModeContext,
|
|
hash: `0x${string}`,
|
|
options?: {
|
|
maxAttempts?: number;
|
|
delayMs?: number;
|
|
exponentialBackoff?: boolean;
|
|
}
|
|
) {
|
|
const maxAttempts = options?.maxAttempts ?? 4;
|
|
const delayMs = options?.delayMs ?? 2000;
|
|
const exponentialBackoff = options?.exponentialBackoff ?? true;
|
|
|
|
let lastError: Error | undefined;
|
|
|
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
try {
|
|
const receipt = await context.viem().getTransactionReceipt({ hash });
|
|
return receipt;
|
|
} catch (error: unknown) {
|
|
lastError = error as Error;
|
|
|
|
// Check if it's the specific error we want to retry
|
|
if (
|
|
(error as Error).name === "TransactionReceiptNotFoundError" ||
|
|
(error as Error).message?.includes("Transaction receipt with hash") ||
|
|
(error as Error).message?.includes("could not be found")
|
|
) {
|
|
if (attempt < maxAttempts) {
|
|
const delay = exponentialBackoff ? delayMs * 1.5 ** (attempt - 1) : delayMs;
|
|
await new Promise((resolve) => setTimeout(resolve, Math.min(delay, 10000)));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// If it's a different error, throw immediately
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// If we've exhausted all attempts, throw the last error
|
|
throw lastError || new Error(`Failed to get transaction receipt after ${maxAttempts} attempts`);
|
|
}
|
|
|
|
/**
|
|
* Calculate total transaction fees (gasUsed * effectiveGasPrice)
|
|
*
|
|
* @param context - Moonwall dev context
|
|
* @param hash - Transaction hash
|
|
* @returns Total fees in wei
|
|
*/
|
|
export async function getTransactionFees(context: DevModeContext, hash: string): Promise<bigint> {
|
|
const receipt = await getTransactionReceiptWithRetry(context, hash as `0x${string}`);
|
|
return receipt.gasUsed * receipt.effectiveGasPrice;
|
|
}
|