diff --git a/apps/order-book/contracts/src/OrderBook.sol.d.ts b/apps/order-book/contracts/src/OrderBook.sol.d.ts index 9dbe47a6..add2f86f 100644 --- a/apps/order-book/contracts/src/OrderBook.sol.d.ts +++ b/apps/order-book/contracts/src/OrderBook.sol.d.ts @@ -28,12 +28,12 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint256"; }, { - readonly astId: 43903; + readonly astId: 43935; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "state"; readonly offset: 0; readonly slot: "3"; - readonly type: "t_struct(State)43420_storage"; + readonly type: "t_struct(State)43452_storage"; }, ]; readonly types: { @@ -48,11 +48,11 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly numberOfBytes: "32"; readonly base: "t_struct(Credential)15_storage"; }; - readonly "t_array(t_struct(Order)43440_storage)dyn_storage": { + readonly "t_array(t_struct(Order)43472_storage)dyn_storage": { readonly encoding: "dynamic_array"; readonly label: "struct Order[]"; readonly numberOfBytes: "32"; - readonly base: "t_struct(Order)43440_storage"; + readonly base: "t_struct(Order)43472_storage"; }; readonly "t_array(t_struct(QueuedMutation)657_storage)dyn_storage": { readonly encoding: "dynamic_array"; @@ -89,12 +89,12 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly numberOfBytes: "32"; readonly value: "t_struct(Account)26_storage"; }; - readonly "t_mapping(t_bytes32,t_struct(Account)43429_storage)": { + readonly "t_mapping(t_bytes32,t_struct(Account)43461_storage)": { readonly encoding: "mapping"; readonly key: "t_bytes32"; readonly label: "mapping(bytes32 => struct Account)"; readonly numberOfBytes: "32"; - readonly value: "t_struct(Account)43429_storage"; + readonly value: "t_struct(Account)43461_storage"; }; readonly "t_mapping(t_uint192,t_uint64)": { readonly encoding: "mapping"; @@ -103,19 +103,19 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly numberOfBytes: "32"; readonly value: "t_uint64"; }; - readonly "t_mapping(t_uint64,t_struct(Instrument)43463_storage)": { + readonly "t_mapping(t_uint64,t_struct(Instrument)43495_storage)": { readonly encoding: "mapping"; readonly key: "t_uint64"; readonly label: "mapping(uint64 => struct Instrument)"; readonly numberOfBytes: "32"; - readonly value: "t_struct(Instrument)43463_storage"; + readonly value: "t_struct(Instrument)43495_storage"; }; - readonly "t_mapping(t_uint64,t_struct(Tick)43474_storage)": { + readonly "t_mapping(t_uint64,t_struct(Tick)43506_storage)": { readonly encoding: "mapping"; readonly key: "t_uint64"; readonly label: "mapping(uint64 => struct Tick)"; readonly numberOfBytes: "32"; - readonly value: "t_struct(Tick)43474_storage"; + readonly value: "t_struct(Tick)43506_storage"; }; readonly "t_struct(Account)26_storage": { readonly encoding: "inplace"; @@ -148,13 +148,13 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ }, ]; }; - readonly "t_struct(Account)43429_storage": { + readonly "t_struct(Account)43461_storage": { readonly encoding: "inplace"; readonly label: "struct Account"; readonly numberOfBytes: "64"; readonly members: readonly [ { - readonly astId: 43424; + readonly astId: 43456; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "balances"; readonly offset: 0; @@ -162,12 +162,12 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_mapping(t_address,t_uint256)"; }, { - readonly astId: 43428; + readonly astId: 43460; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "orders"; readonly offset: 0; readonly slot: "1"; - readonly type: "t_array(t_struct(Order)43440_storage)dyn_storage"; + readonly type: "t_array(t_struct(Order)43472_storage)dyn_storage"; }, ]; }; @@ -210,13 +210,13 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ }, ]; }; - readonly "t_struct(Instrument)43463_storage": { + readonly "t_struct(Instrument)43495_storage": { readonly encoding: "inplace"; readonly label: "struct Instrument"; readonly numberOfBytes: "160"; readonly members: readonly [ { - readonly astId: 43442; + readonly astId: 43474; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "base"; readonly offset: 0; @@ -224,7 +224,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_address"; }, { - readonly astId: 43444; + readonly astId: 43476; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "quote"; readonly offset: 0; @@ -232,7 +232,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_address"; }, { - readonly astId: 43446; + readonly astId: 43478; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "baseLotExp"; readonly offset: 20; @@ -240,7 +240,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint8"; }, { - readonly astId: 43448; + readonly astId: 43480; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "quoteLotExp"; readonly offset: 21; @@ -248,7 +248,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint8"; }, { - readonly astId: 43450; + readonly astId: 43482; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "bestBid"; readonly offset: 22; @@ -256,7 +256,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint64"; }, { - readonly astId: 43452; + readonly astId: 43484; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "bestAsk"; readonly offset: 0; @@ -264,30 +264,30 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint64"; }, { - readonly astId: 43457; + readonly astId: 43489; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "bids"; readonly offset: 0; readonly slot: "3"; - readonly type: "t_mapping(t_uint64,t_struct(Tick)43474_storage)"; + readonly type: "t_mapping(t_uint64,t_struct(Tick)43506_storage)"; }, { - readonly astId: 43462; + readonly astId: 43494; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "asks"; readonly offset: 0; readonly slot: "4"; - readonly type: "t_mapping(t_uint64,t_struct(Tick)43474_storage)"; + readonly type: "t_mapping(t_uint64,t_struct(Tick)43506_storage)"; }, ]; }; - readonly "t_struct(Order)43440_storage": { + readonly "t_struct(Order)43472_storage": { readonly encoding: "inplace"; readonly label: "struct Order"; readonly numberOfBytes: "32"; readonly members: readonly [ { - readonly astId: 43431; + readonly astId: 43463; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "quantity"; readonly offset: 0; @@ -295,7 +295,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint64"; }, { - readonly astId: 43433; + readonly astId: 43465; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "instrumentId"; readonly offset: 8; @@ -303,7 +303,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint64"; }, { - readonly astId: 43435; + readonly astId: 43467; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "price"; readonly offset: 16; @@ -311,7 +311,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint64"; }, { - readonly astId: 43437; + readonly astId: 43469; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "tickVolume"; readonly offset: 24; @@ -319,7 +319,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint32"; }, { - readonly astId: 43439; + readonly astId: 43471; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "side"; readonly offset: 28; @@ -367,36 +367,36 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ }, ]; }; - readonly "t_struct(State)43420_storage": { + readonly "t_struct(State)43452_storage": { readonly encoding: "inplace"; readonly label: "struct State"; readonly numberOfBytes: "64"; readonly members: readonly [ { - readonly astId: 43414; + readonly astId: 43446; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "accounts"; readonly offset: 0; readonly slot: "0"; - readonly type: "t_mapping(t_bytes32,t_struct(Account)43429_storage)"; + readonly type: "t_mapping(t_bytes32,t_struct(Account)43461_storage)"; }, { - readonly astId: 43419; + readonly astId: 43451; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "instruments"; readonly offset: 0; readonly slot: "1"; - readonly type: "t_mapping(t_uint64,t_struct(Instrument)43463_storage)"; + readonly type: "t_mapping(t_uint64,t_struct(Instrument)43495_storage)"; }, ]; }; - readonly "t_struct(Tick)43474_storage": { + readonly "t_struct(Tick)43506_storage": { readonly encoding: "inplace"; readonly label: "struct Tick"; readonly numberOfBytes: "64"; readonly members: readonly [ { - readonly astId: 43465; + readonly astId: 43497; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "quantity"; readonly offset: 0; @@ -404,7 +404,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint64"; }, { - readonly astId: 43467; + readonly astId: 43499; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "remainingQuantity"; readonly offset: 8; @@ -412,7 +412,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint64"; }, { - readonly astId: 43469; + readonly astId: 43501; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "volume"; readonly offset: 16; @@ -420,7 +420,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint32"; }, { - readonly astId: 43471; + readonly astId: 43503; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "prev"; readonly offset: 20; @@ -428,7 +428,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint64"; }, { - readonly astId: 43473; + readonly astId: 43505; readonly contract: "src/OrderBook.sol:OrderBook"; readonly label: "next"; readonly offset: 0; diff --git a/apps/token/contracts/src/Token.sol.d.ts b/apps/token/contracts/src/Token.sol.d.ts index 129c0bd1..4003f733 100644 --- a/apps/token/contracts/src/Token.sol.d.ts +++ b/apps/token/contracts/src/Token.sol.d.ts @@ -28,12 +28,12 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint256"; }, { - readonly astId: 42145; + readonly astId: 42177; readonly contract: "src/Token.sol:Token"; readonly label: "state"; readonly offset: 0; readonly slot: "3"; - readonly type: "t_struct(State)42134_storage"; + readonly type: "t_struct(State)42166_storage"; }, ]; readonly types: { @@ -194,13 +194,13 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ }, ]; }; - readonly "t_struct(State)42134_storage": { + readonly "t_struct(State)42166_storage": { readonly encoding: "inplace"; readonly label: "struct State"; readonly numberOfBytes: "64"; readonly members: readonly [ { - readonly astId: 42129; + readonly astId: 42161; readonly contract: "src/Token.sol:Token"; readonly label: "totalSupply"; readonly offset: 0; @@ -208,7 +208,7 @@ declare const entrypoint: import("typewriter").TypewriterSolidityEntrypoint<{ readonly type: "t_uint256"; }, { - readonly astId: 42133; + readonly astId: 42165; readonly contract: "src/Token.sol:Token"; readonly label: "balances"; readonly offset: 0; diff --git a/packages/typewriter/src/Typewriter.sol b/packages/typewriter/src/Typewriter.sol index 64aa77fd..d53bc02f 100644 --- a/packages/typewriter/src/Typewriter.sol +++ b/packages/typewriter/src/Typewriter.sol @@ -194,7 +194,9 @@ abstract contract Typewriter { revert LengthMismatch(); } for (uint256 i; i < batch.mutations.length; i++) { + uint256 freeMemoryPointer = _getFreeMemoryPointer(); _execute(batch.mutations[i], batch.mutationData[i], batch.authorizationData[i]); + _setFreeMemoryPointer(freeMemoryPointer); executionIndex++; } } @@ -205,13 +207,27 @@ abstract contract Typewriter { if (queued.enqueuedBlock == 0) revert ForceInclusionAlreadyExecuted(index); + uint256 freeMemoryPointer = _getFreeMemoryPointer(); _execute(queued.mutation, queued.mutationData, queued.authorizationData); + _setFreeMemoryPointer(freeMemoryPointer); executionIndex++; delete queue[index]; } } + function _getFreeMemoryPointer() private pure returns (uint256 pointer) { + assembly ("memory-safe") { + pointer := mload(0x40) + } + } + + function _setFreeMemoryPointer(uint256 pointer) private pure { + assembly ("memory-safe") { + mstore(0x40, pointer) + } + } + function enqueue(uint8 mutation, bytes calldata mutationData, bytes calldata authorizationData) external returns (uint256) diff --git a/packages/typewriter/test/harness.benchmark.ts b/packages/typewriter/test/harness.benchmark.ts index f607f7c6..f72f9362 100644 --- a/packages/typewriter/test/harness.benchmark.ts +++ b/packages/typewriter/test/harness.benchmark.ts @@ -1,10 +1,12 @@ import { test } from "bun:test"; +import type { Hex } from "viem"; import { anvil } from "viem/chains"; import type { TypewriterConfig } from "../src"; import Harness from "./contracts/src/Harness.sol"; import { SCHEDULER_ACCOUNT, TEST_DB_URL, + TEST_PUBLIC_CLIENT, TEST_RPC_URL, USER_ACCOUNT, USER_PRIVATE_KEY, @@ -23,7 +25,8 @@ Object.assign(process.env, { }); const LATENCY_MUTATION_COUNT = 1_000; -const THROUGHPUT_MUTATION_COUNT = 10_000; +// Keep the throughput phase below Monad's 30M gas limit per transaction. +const THROUGHPUT_MUTATION_COUNT = 500; test("harness benchmark", async () => { const { createTypewriter } = await import("../src"); @@ -65,6 +68,25 @@ test("harness benchmark", async () => { const throughputMutations = mutations.slice(LATENCY_MUTATION_COUNT); const typewriter = await createTypewriter(Harness, config); + + // `execute` resolves when the runtime accepts a mutation. Wait for every + // mutation to be included before closing, so no settlement transaction is + // in flight when the test database is dropped. + const allIncluded = Promise.withResolvers(); + let pendingCount = 1 + mutations.length; + const unsubscribe = typewriter.on("mutation", (event) => { + if (event.status === "included" && --pendingCount === 0) { + allIncluded.resolve(); + } + }); + + const settlementHashes: Hex[] = []; + const unsubscribeBlock = typewriter.on("block", (event) => { + if (event.status === "included") { + settlementHashes.push(event.transactionHash); + } + }); + await typewriter.execute(createAccount); const latenciesMs: number[] = []; @@ -80,6 +102,22 @@ test("harness benchmark", async () => { ); const throughputDurationMs = performance.now() - start; + await allIncluded.promise; + unsubscribe(); + unsubscribeBlock(); + await typewriter.close(); + + const receipts = await Promise.all( + settlementHashes.map((hash) => + TEST_PUBLIC_CLIENT.getTransactionReceipt({ hash }), + ), + ); + const totalGas = receipts.reduce( + (total, receipt) => total + receipt.gasUsed, + 0n, + ); + const meanGas = Number(totalGas) / (1 + mutations.length); + latenciesMs.sort((a, b) => a - b); const averageMs = latenciesMs.reduce((total, latency) => total + latency, 0) / @@ -94,5 +132,7 @@ Harness benchmark single user, ${LATENCY_MUTATION_COUNT.toLocaleString()} sequential mutations: average ${averageMs.toFixed(2)}ms, p50 ${p50Ms.toFixed(2)}ms, p99 ${p99Ms.toFixed(2)}ms max throughput, ${THROUGHPUT_MUTATION_COUNT.toLocaleString()} concurrent mutations: - ${Math.round(mutationsPerSecond).toLocaleString()} mutations/s`); + ${Math.round(mutationsPerSecond).toLocaleString()} mutations/s +settlement gas: + ${Math.round(meanGas).toLocaleString()} per mutation`); }, 300_000);