@black-market/sdk 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -2
- package/dist/lifecycle/abi.d.ts +1 -0
- package/dist/lifecycle/abi.d.ts.map +1 -1
- package/dist/lifecycle/abi.js +1 -0
- package/dist/lifecycle/abi.js.map +1 -1
- package/dist/lifecycle/calibration.d.ts.map +1 -1
- package/dist/lifecycle/calibration.js +2 -0
- package/dist/lifecycle/calibration.js.map +1 -1
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.d.ts +71 -0
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.d.ts.map +1 -0
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.js +73 -0
- package/dist/lifecycle/deployments/ufg-dynamic-pool-20261021.js.map +1 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.d.ts +71 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.d.ts.map +1 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.js +73 -0
- package/dist/lifecycle/deployments/ufg-fixed-pool-20261021.js.map +1 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/index.js +1 -0
- package/dist/lifecycle/index.js.map +1 -1
- package/dist/lifecycle/markets.d.ts +3 -1
- package/dist/lifecycle/markets.d.ts.map +1 -1
- package/dist/lifecycle/markets.js +20 -3
- package/dist/lifecycle/markets.js.map +1 -1
- package/dist/lifecycle/planner.d.ts.map +1 -1
- package/dist/lifecycle/planner.js +83 -54
- package/dist/lifecycle/planner.js.map +1 -1
- package/dist/lifecycle/presets.d.ts +9 -2
- package/dist/lifecycle/presets.d.ts.map +1 -1
- package/dist/lifecycle/presets.js +26 -5
- package/dist/lifecycle/presets.js.map +1 -1
- package/dist/lifecycle/progress.d.ts.map +1 -1
- package/dist/lifecycle/progress.js +26 -5
- package/dist/lifecycle/progress.js.map +1 -1
- package/dist/lifecycle/rpc.js +3 -2
- package/dist/lifecycle/rpc.js.map +1 -1
- package/dist/lifecycle/simulation.d.ts +1 -1
- package/dist/lifecycle/simulation.d.ts.map +1 -1
- package/dist/lifecycle/simulation.js +119 -175
- package/dist/lifecycle/simulation.js.map +1 -1
- package/dist/lifecycle/transactions.d.ts +6 -0
- package/dist/lifecycle/transactions.d.ts.map +1 -1
- package/dist/lifecycle/transactions.js +39 -0
- package/dist/lifecycle/transactions.js.map +1 -1
- package/dist/lifecycle/types.d.ts +17 -4
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/lifecycle/ufg-abi.d.ts +2386 -0
- package/dist/lifecycle/ufg-abi.d.ts.map +1 -0
- package/dist/lifecycle/ufg-abi.js +171 -0
- package/dist/lifecycle/ufg-abi.js.map +1 -0
- package/dist/lifecycle/ufg-config.d.ts +38 -0
- package/dist/lifecycle/ufg-config.d.ts.map +1 -0
- package/dist/lifecycle/ufg-config.js +75 -0
- package/dist/lifecycle/ufg-config.js.map +1 -0
- package/dist/lifecycle/ufg.d.ts +21 -0
- package/dist/lifecycle/ufg.d.ts.map +1 -0
- package/dist/lifecycle/ufg.js +98 -0
- package/dist/lifecycle/ufg.js.map +1 -0
- package/docs/launch-lifecycle.md +215 -32
- package/examples/.env.example +4 -0
- package/examples/README.md +35 -2
- package/examples/launch-example-support.ts +1 -0
- package/examples/launch-example.ts +1 -0
- package/examples/launch.ts +1 -0
- package/examples/ufg-launch-config.ts +31 -0
- package/package.json +1 -1
- package/src/lifecycle/abi.ts +1 -0
- package/src/lifecycle/calibration.ts +1 -0
- package/src/lifecycle/deployments/ufg-dynamic-pool-20261021.ts +74 -0
- package/src/lifecycle/deployments/ufg-fixed-pool-20261021.ts +74 -0
- package/src/lifecycle/index.ts +1 -0
- package/src/lifecycle/markets.ts +16 -3
- package/src/lifecycle/planner.ts +75 -51
- package/src/lifecycle/presets.ts +27 -6
- package/src/lifecycle/progress.ts +22 -5
- package/src/lifecycle/rpc.ts +3 -2
- package/src/lifecycle/simulation.ts +114 -166
- package/src/lifecycle/transactions.ts +38 -1
- package/src/lifecycle/types.ts +15 -5
- package/src/lifecycle/ufg-abi.ts +173 -0
- package/src/lifecycle/ufg-config.ts +77 -0
- package/src/lifecycle/ufg.ts +86 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../src/lifecycle/simulation.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwD,KAAK,cAAc,EAAoF,KAAK,mBAAmB,EAAgC,KAAK,oBAAoB,EAAoD,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../../src/lifecycle/simulation.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwD,KAAK,cAAc,EAAoF,KAAK,mBAAmB,EAAgC,KAAK,oBAAoB,EAAoD,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AA0S9V,qFAAqF;AACrF,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAClF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAAE,YAAY,CAAC,EAAE,uBAAuB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC,GAAG;IACjK,4FAA4F;IAC5F,OAAO,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACrF,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAQF;wFACwF;AACxF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,yBAAyB,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,UAAO,EAAE,gBAAgB,CAAC,EAAE,uBAAuB,GAAG,wBAAwB,CAuH/L;AAED,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,SAAS,oBAAoB,EAAE,EAAE,WAAW,CAAC,EAAE,cAAc,EAAE,YAAY,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAOtO"}
|
|
@@ -4,6 +4,9 @@ import { hashLaunchIdentity, hashLaunchPlan, LifecyclePhase } from "./schema.js"
|
|
|
4
4
|
import { assertLifecycleBlock, assertLifecycleReadClientOpen, lifecycleFailureReason, lifecyclePinnedRpc, lifecycleRpc, lifecycleSourceClient, lifecycleStage, nitroArbSys, nitroArbSysAbi, nitroGasInfo, nitroGasInfoAbi, readLifecycleBlock, readLifecycleChainId, readNitroPosterGas, resolveLifecycleLimits, rpcHex, rpcObject, rpcQuantity } from "./rpc.js";
|
|
5
5
|
import { LifecyclePlanningError } from "./types.js";
|
|
6
6
|
import { decodeAbyssLifecycleMarketConfig, decodePoolBoundV4LifecycleMarketConfig, decodeV4LifecycleMarketConfig, isPoolBoundV4ConfigVersion } from "./markets.js";
|
|
7
|
+
import { hmmV2ForwardingReserve } from "./presets.js";
|
|
8
|
+
import { transactionGasEstimate } from "./transactions.js";
|
|
9
|
+
import { hashUfgLaunchConfig } from "./ufg-config.js";
|
|
7
10
|
function committedPositionCount(plan) {
|
|
8
11
|
let count = 0;
|
|
9
12
|
for (const market of plan.markets) {
|
|
@@ -119,9 +122,6 @@ async function proveNitroBackend(client, planned, block, limits, gasPrice) {
|
|
|
119
122
|
// (including every refusal path); a separate probe-only recheck is redundant.
|
|
120
123
|
}
|
|
121
124
|
async function simulateRpcPass(client, planned, block, transactions, gasLimits, limits, gasPrice, validation) {
|
|
122
|
-
const totalRequestedGas = gasLimits.reduce((total, gas) => total + gas, 0n);
|
|
123
|
-
if (limits.maxSimulationGas !== undefined && totalRequestedGas > limits.maxSimulationGas)
|
|
124
|
-
throw new LifecyclePlanningError("SIMULATION_RPC_CAP", "Sequential request exceeds the current RPC aggregate simulation gas cap; use an isolated controlled fork");
|
|
125
125
|
// Natural child-block fees must govern fee-bearing admission; overriding
|
|
126
126
|
// baseFeePerGas with the parent's would fabricate a lower envelope and admit
|
|
127
127
|
// calls that the actual chain would reject. eth_simulateV1 derives the child
|
|
@@ -184,7 +184,7 @@ async function simulateRpcPass(client, planned, block, transactions, gasLimits,
|
|
|
184
184
|
throw new LifecyclePlanningError("POSTCONDITION_FAILED", "Simulation did not satisfy every ordered buy's budget and output minimum");
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
-
else if (transaction.kind === "cancel" && !events.some((event) => event.eventName === "LaunchCancelled" && event.args.launchId.toLowerCase() === planned.launchId.toLowerCase() && event.args.creator.toLowerCase() === planned.
|
|
187
|
+
else if (transaction.kind === "cancel" && !events.some((event) => event.eventName === "LaunchCancelled" && event.args.launchId.toLowerCase() === planned.launchId.toLowerCase() && event.args.creator.toLowerCase() === planned.plan.creator.toLowerCase()))
|
|
188
188
|
throw new LifecyclePlanningError("POSTCONDITION_FAILED", "Simulation did not cancel the exact committed launch");
|
|
189
189
|
else if ((transaction.kind === "approve" || transaction.kind === "approve-reset") && returnData !== "0x" && decodeFunctionResult({ abi: lifecycleErc20Abi, functionName: "approve", data: returnData }) !== true)
|
|
190
190
|
throw new LifecyclePlanningError("POSTCONDITION_FAILED", "Funding approval returned failure");
|
|
@@ -358,7 +358,7 @@ function observedFields(value) {
|
|
|
358
358
|
}
|
|
359
359
|
/** Internal continuation owned by one SDK read invocation. Only its positive
|
|
360
360
|
* capability proof is reused; every candidate executes its entire economic sequence. */
|
|
361
|
-
export function prepareLaunchSimulation(options, block, hasTransactions = true) {
|
|
361
|
+
export function prepareLaunchSimulation(options, block, hasTransactions = true, reviewedEnvelope) {
|
|
362
362
|
const { client, planned, fork } = options;
|
|
363
363
|
const signal = options.signal;
|
|
364
364
|
if (signal?.aborted)
|
|
@@ -392,6 +392,8 @@ export function prepareLaunchSimulation(options, block, hasTransactions = true)
|
|
|
392
392
|
if (hashLaunchPlan(plan).toLowerCase() !== planHash.toLowerCase() || hashLaunchIdentity(plan).toLowerCase() !== launchId.toLowerCase()) {
|
|
393
393
|
throw new LifecyclePlanningError("PLAN_MUTATED", "Economic plan changed after its simulation context was bound");
|
|
394
394
|
}
|
|
395
|
+
if (planned.ufg !== undefined && hashUfgLaunchConfig(planned.ufg.config) !== planned.ufg.configHash)
|
|
396
|
+
throw new LifecyclePlanningError("PLAN_MUTATED", "UFG treasury config changed after its simulation context was bound");
|
|
395
397
|
};
|
|
396
398
|
const pending = (async () => {
|
|
397
399
|
if (fork !== undefined && lifecycleSourceClient(fork.client) === sourceClient)
|
|
@@ -400,7 +402,7 @@ export function prepareLaunchSimulation(options, block, hasTransactions = true)
|
|
|
400
402
|
resolveLifecycleLimits(context, policy),
|
|
401
403
|
readLifecycleChainId(client),
|
|
402
404
|
lifecyclePinnedRpc(client, "eth_getCode", [context.account, toHex(context.block.number)], context.block),
|
|
403
|
-
hasTransactions ? lifecycleRpc(client, "eth_gasPrice") : undefined,
|
|
405
|
+
hasTransactions && reviewedEnvelope === undefined ? lifecycleRpc(client, "eth_gasPrice") : undefined,
|
|
404
406
|
]));
|
|
405
407
|
})().then((observations) => ({ observations }), (failure) => ({ failure }));
|
|
406
408
|
// Refused inputs may discard this read-only preparation before it settles.
|
|
@@ -491,7 +493,7 @@ export async function simulateLaunchTransactions(options, transactions, pinnedBl
|
|
|
491
493
|
if (options.fork !== undefined && lifecycleSourceClient(options.fork.client) === lifecycleSourceClient(options.client))
|
|
492
494
|
throw new LifecyclePlanningError("UNSAFE_FORK", "Source RPC must never receive controlled simulation transactions/snapshots/resets");
|
|
493
495
|
const block = pinnedBlock ?? await readLifecycleBlock(options.client);
|
|
494
|
-
const prepared = prepareLaunchSimulation(options, block, transactions.length !== 0);
|
|
496
|
+
const prepared = prepareLaunchSimulation(options, block, transactions.length !== 0, nextEnvelope);
|
|
495
497
|
try {
|
|
496
498
|
return await prepared(transactions, nextEnvelope);
|
|
497
499
|
}
|
|
@@ -504,7 +506,7 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
|
|
|
504
506
|
const sourceClient = lifecycleSourceClient(client);
|
|
505
507
|
const { block } = context;
|
|
506
508
|
const [{ limits, configuration }, chainId, accountCodeValue, gasPriceValue] = observations;
|
|
507
|
-
const base = { confidence: "stateful", executionProof: "failed", protocolFit: "unknown", transportPreflight: "not-requested", blockNumber: block.number, blockHash: block.hash, account: planned.account, chainId: planned.chainId, limits };
|
|
509
|
+
const base = { confidence: "stateful", executionProof: "failed", protocolFit: "unknown", transportPreflight: "not-requested", blockNumber: block.number, blockHash: block.hash, account: planned.account, chainId: planned.chainId, limits, deferredTransactionIds: [] };
|
|
508
510
|
if (chainId !== planned.chainId)
|
|
509
511
|
throw new LifecyclePlanningError("CHAIN_MISMATCH", "RPC chain changed before sequential simulation");
|
|
510
512
|
const accountCode = rpcHex(accountCodeValue, "account code");
|
|
@@ -524,38 +526,50 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
|
|
|
524
526
|
await assertLifecycleBlock(client, block, planned.chainId);
|
|
525
527
|
return { ...base, executionProof: "proved", protocolFit: "proved", backend: "unavailable", admitted: true, steps: [] };
|
|
526
528
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
throw new LifecyclePlanningError("
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
529
|
+
if (nextEnvelope !== undefined && (transactions.length !== 1 || typeof nextEnvelope.gas !== "bigint" || nextEnvelope.gas <= 0n || nextEnvelope.gas >= 1n << 64n ||
|
|
530
|
+
typeof nextEnvelope.gasPrice !== "bigint" || nextEnvelope.gasPrice < 0n || nextEnvelope.gasPrice >= 1n << 256n)) {
|
|
531
|
+
throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Reviewed replay requires one immediate transaction with exact uint64 gas and uint256 price");
|
|
532
|
+
}
|
|
533
|
+
const allTransactions = transactions;
|
|
534
|
+
if (limits.maxSimulationGas !== undefined && nextEnvelope === undefined) {
|
|
535
|
+
let estimatedGas = 0n, count = 0;
|
|
536
|
+
for (const transaction of transactions) {
|
|
537
|
+
estimatedGas += transactionGasEstimate(planned.plan, transaction);
|
|
538
|
+
if (estimatedGas > limits.maxSimulationGas)
|
|
539
|
+
break;
|
|
540
|
+
count += 1;
|
|
541
|
+
}
|
|
542
|
+
transactions = transactions.slice(0, count);
|
|
543
|
+
base.deferredTransactionIds = allTransactions.slice(count).map((transaction) => transaction.id);
|
|
544
|
+
if (count === 0) {
|
|
545
|
+
const computeCap = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
|
|
546
|
+
const exceedsCompute = estimatedGas > computeCap;
|
|
547
|
+
const gasInMillions = (gas) => {
|
|
548
|
+
const tenths = (gas + 50000n) / 100000n;
|
|
549
|
+
return `${tenths / 10n}.${tenths % 10n}`;
|
|
550
|
+
};
|
|
551
|
+
const atomic = allTransactions[0].kind === "atomic";
|
|
552
|
+
const reason = exceedsCompute
|
|
553
|
+
? `${atomic ? "Atomic launch" : "Immediate transaction"} needs about ${gasInMillions(estimatedGas)}M gas, above the ${gasInMillions(computeCap)}M per-transaction limit${atomic ? "; use staged" : ""}`
|
|
554
|
+
: "The immediate transaction estimate exceeds the RPC aggregate simulation gas cap; no prefix can be proved";
|
|
555
|
+
await assertLifecycleBlock(client, block, planned.chainId);
|
|
556
|
+
return { ...base, confidence: "provisional", executionProof: "unavailable", backend: "unavailable", admitted: false, steps: [], failureCategory: "capacity", capacityConstraint: exceedsCompute ? "compute" : "simulation-gas", reason };
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const recommendedGasPrice = nextEnvelope === undefined ? rpcQuantity(gasPriceValue, "gas price") : nextEnvelope.gasPrice;
|
|
560
|
+
// New recommendations cover lagging quotes; reviewed fees are replayed unchanged
|
|
561
|
+
// under natural child fees, never refreshed or silently increased.
|
|
533
562
|
const baseFeeEnvelope = (block.baseFeePerGas ?? 0n) * 2n;
|
|
534
563
|
const gasPrice = nextEnvelope?.gasPrice ?? (recommendedGasPrice > baseFeeEnvelope ? recommendedGasPrice : baseFeeEnvelope);
|
|
535
564
|
if (gasPrice >= 1n << 256n)
|
|
536
565
|
throw new LifecyclePlanningError("INVALID_RPC_RESPONSE", "Gas price must be a uint256 quantity");
|
|
537
|
-
if (nextEnvelope !== undefined && (typeof nextEnvelope.gas !== "bigint" || nextEnvelope.gas <= 0n || nextEnvelope.gas >= 1n << 64n ||
|
|
538
|
-
typeof nextEnvelope.gasPrice !== "bigint" || nextEnvelope.gasPrice < 0n || nextEnvelope.gasPrice >= 1n << 256n)) {
|
|
539
|
-
throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Reviewed gas and gas price must be exact positive uint64 gas and nonnegative uint256 price");
|
|
540
|
-
}
|
|
541
566
|
const buffer = (gas) => (gas * BigInt(10000 + limits.headroomBps) + 9999n) / 10000n;
|
|
542
|
-
const computeCeiling = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < limits.executionGasCeiling ? limits.transactionGasCeiling : limits.executionGasCeiling;
|
|
543
|
-
// Nonnegative poster gas cannot make this complete discovery request smaller.
|
|
544
|
-
// A generic controlled fork retains the existing RPC-to-fork fallback.
|
|
545
|
-
if (limits.maxSimulationGas !== undefined && BigInt(transactions.length) * computeCeiling > limits.maxSimulationGas &&
|
|
546
|
-
(limits.protocol === "nitro" || options.fork === undefined)) {
|
|
547
|
-
assertCurrent();
|
|
548
|
-
await assertLifecycleBlock(client, block, planned.chainId);
|
|
549
|
-
return { ...base, confidence: "provisional", executionProof: "unavailable", backend: "unavailable", admitted: false, steps: [], failureCategory: "capacity", capacityConstraint: "simulation-gas", reason: `Stateful sequential RPC simulation unavailable: Sequential request exceeds the current RPC aggregate simulation gas cap; use an isolated controlled fork. ${limits.protocol === "nitro" ? "Generic Anvil/Hardhat forks cannot prove ArbOS compute/poster metering." : "Dependent eth_call estimates are not proof."}` };
|
|
550
|
-
}
|
|
551
567
|
const posterGas = [];
|
|
552
568
|
if (limits.protocol === "nitro") {
|
|
553
|
-
// Lightweight poster reads may overlap the isolated capability request.
|
|
554
|
-
// Economic work is not even queued until both prerequisites have succeeded.
|
|
555
569
|
try {
|
|
556
570
|
const [, estimates] = await Promise.all([
|
|
557
571
|
proveBackend(limits, gasPrice),
|
|
558
|
-
lifecycleStage(client, "simulation.poster", () => Promise.all(transactions.map(async (transaction) => buffer((await readNitroPosterGas(transaction, context)).posterGas)))),
|
|
572
|
+
nextEnvelope === undefined ? lifecycleStage(client, "simulation.poster", () => Promise.all(transactions.map(async (transaction) => buffer((await readNitroPosterGas(transaction, context)).posterGas)))) : [],
|
|
559
573
|
]);
|
|
560
574
|
posterGas.push(...estimates);
|
|
561
575
|
}
|
|
@@ -568,8 +582,6 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
|
|
|
568
582
|
}
|
|
569
583
|
}
|
|
570
584
|
assertCurrent();
|
|
571
|
-
// The poster quote allocates gas only. The native exact replay enforces actual
|
|
572
|
-
// compute hold; subtracting this approximate budget from gasUsed would not.
|
|
573
585
|
const discoveryGas = transactions.map((_, index) => {
|
|
574
586
|
const total = limits.executionGasCeiling + (posterGas[index] ?? 0n);
|
|
575
587
|
const capped = limits.transactionGasCeiling !== undefined && limits.transactionGasCeiling < total ? limits.transactionGasCeiling : total;
|
|
@@ -577,116 +589,96 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
|
|
|
577
589
|
throw new LifecyclePlanningError("INVALID_GAS_ENVELOPE", "Compute/poster envelope exceeds uint64 gas");
|
|
578
590
|
return capped;
|
|
579
591
|
});
|
|
580
|
-
let backend = "eth_simulateV1";
|
|
581
|
-
let measured;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
592
|
+
let backend = nextEnvelope !== undefined && limits.protocol === "evm" && options.fork !== undefined ? "controlled-fork" : "eth_simulateV1";
|
|
593
|
+
let measured = [];
|
|
594
|
+
if (nextEnvelope === undefined) {
|
|
595
|
+
try {
|
|
596
|
+
measured = await lifecycleStage(client, "simulation.measure", () => simulateRpcPass(client, planned, block, transactions, discoveryGas, limits, gasPrice, false));
|
|
597
|
+
}
|
|
598
|
+
catch (failure) {
|
|
599
|
+
if (simulationFailureKind(failure) === "abort")
|
|
600
|
+
throw failure;
|
|
601
|
+
assertCurrent();
|
|
602
|
+
if (options.fork === undefined || limits.protocol === "nitro") {
|
|
603
|
+
await assertLifecycleBlock(client, block, planned.chainId);
|
|
604
|
+
return { ...base, confidence: "provisional", executionProof: "unavailable", backend: "unavailable", admitted: false, steps: [], reason: `Stateful sequential RPC simulation unavailable: ${lifecycleFailureReason(failure)}. ${limits.protocol === "nitro" ? "Generic Anvil/Hardhat forks cannot prove ArbOS compute/poster metering." : "Dependent eth_call estimates are not proof."}` };
|
|
605
|
+
}
|
|
606
|
+
backend = "controlled-fork";
|
|
607
|
+
measured = await simulateForkPass(options.fork, planned, block, transactions, discoveryGas, gasPrice, true, limits.headroomBps);
|
|
608
|
+
}
|
|
590
609
|
assertCurrent();
|
|
591
|
-
|
|
610
|
+
const failedIndex = measured.findIndex((step) => !step.success);
|
|
611
|
+
if (failedIndex >= 0 && limits.maxSimulationGas !== undefined &&
|
|
612
|
+
measured.reduce((gas, step) => gas + (step.gasUsed ?? 0n), 0n) * 100n >= limits.maxSimulationGas * 95n) {
|
|
613
|
+
// The provider exhausted its aggregate used-gas budget, not the contract.
|
|
614
|
+
// Preserve only actual successful results; the failed call is deferred too.
|
|
615
|
+
measured = measured.slice(0, failedIndex);
|
|
616
|
+
transactions = transactions.slice(0, failedIndex);
|
|
617
|
+
base.deferredTransactionIds = allTransactions.slice(failedIndex).map((transaction) => transaction.id);
|
|
618
|
+
if (transactions.length === 0) {
|
|
619
|
+
await assertLifecycleBlock(client, block, planned.chainId);
|
|
620
|
+
return { ...base, confidence: "provisional", executionProof: "unavailable", backend, admitted: false, steps: [], failureCategory: "source", reason: "RPC aggregate simulation gas cap left no successful prefix to prove" };
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const failed = measured.find((step) => !step.success);
|
|
624
|
+
if (failed !== undefined) {
|
|
592
625
|
await assertLifecycleBlock(client, block, planned.chainId);
|
|
593
|
-
return { ...base,
|
|
626
|
+
return { ...base, backend, admitted: false, steps: measured, reason: failed.error, failedTransactionId: failed.transactionId, failureCategory: failed.failureCategory ?? "opaque", capacityConstraint: failed.failureCategory === "capacity" ? "compute" : undefined };
|
|
627
|
+
}
|
|
628
|
+
if (diagnostic) {
|
|
629
|
+
await assertLifecycleBlock(client, block, planned.chainId);
|
|
630
|
+
return { ...base, backend, admitted: false, executionProof: "unavailable", steps: measured };
|
|
594
631
|
}
|
|
595
|
-
backend = "controlled-fork";
|
|
596
|
-
measured = await simulateForkPass(options.fork, planned, block, transactions, discoveryGas, gasPrice, true, limits.headroomBps);
|
|
597
|
-
}
|
|
598
|
-
assertCurrent();
|
|
599
|
-
const failed = measured.find((step) => !step.success);
|
|
600
|
-
if (failed !== undefined) {
|
|
601
|
-
await assertLifecycleBlock(client, block, planned.chainId);
|
|
602
|
-
return { ...base, backend, admitted: false, steps: measured, reason: failed.error, failedTransactionId: failed.transactionId, failureCategory: failed.failureCategory ?? "opaque", capacityConstraint: failed.failureCategory === "capacity" ? "compute" : undefined };
|
|
603
|
-
}
|
|
604
|
-
if (diagnostic) {
|
|
605
|
-
await assertLifecycleBlock(client, block, planned.chainId);
|
|
606
|
-
return { ...base, backend, admitted: false, executionProof: "unavailable", steps: measured };
|
|
607
632
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const exceeds = gasLimits.findIndex((gas, index) => gas <= 0n || gas >= 1n << 64n ||
|
|
633
|
+
const gasLimits = nextEnvelope === undefined ? measured.map((step, index) => {
|
|
634
|
+
const gross = step.gasRequired ?? step.gasUsed;
|
|
635
|
+
const reserve = transactions[index]?.kind === "activate" || transactions[index]?.kind === "atomic" ? hmmV2ForwardingReserve(planned.plan) : 0n;
|
|
636
|
+
const desired = gross + reserve > buffer(gross) ? gross + reserve : buffer(gross);
|
|
637
|
+
const available = discoveryGas[index] - (posterGas[index] ?? 0n);
|
|
638
|
+
return (desired < available ? desired : available) + (posterGas[index] ?? 0n);
|
|
639
|
+
}) : [nextEnvelope.gas];
|
|
640
|
+
const exceeds = gasLimits.findIndex((gas, index) => gas <= 0n || gas >= 1n << 64n || limits.transactionGasCeiling !== undefined && gas > limits.transactionGasCeiling ||
|
|
641
|
+
nextEnvelope === undefined && (measured[index].gasRequired ?? measured[index].gasUsed) > gas - (posterGas[index] ?? 0n) ||
|
|
642
|
+
limits.protocol === "evm" && gas > limits.executionGasCeiling);
|
|
616
643
|
if (exceeds >= 0) {
|
|
617
644
|
await assertLifecycleBlock(client, block, planned.chainId);
|
|
618
|
-
return { ...base, executionProof: "unavailable", protocolFit: "failed", backend, admitted: false, steps: measured, failureCategory: "capacity", capacityConstraint:
|
|
645
|
+
return { ...base, executionProof: "unavailable", protocolFit: "failed", backend, admitted: false, steps: measured, failureCategory: "capacity", capacityConstraint: "gas-envelope", reason: "Measured execution or reviewed gas exceeds the current compute/complete transaction envelope", failedTransactionId: transactions[exceeds]?.id };
|
|
619
646
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
let verified;
|
|
627
|
-
let validatedReplayError;
|
|
628
|
-
assertCurrent();
|
|
647
|
+
let verifiedSteps = [];
|
|
648
|
+
let admittedGasLimits = gasLimits;
|
|
649
|
+
let replayError;
|
|
650
|
+
const replay = (gas) => backend === "eth_simulateV1"
|
|
651
|
+
? simulateRpcPass(client, planned, block, transactions, gas, limits, gasPrice, true)
|
|
652
|
+
: simulateForkPass(options.fork, planned, block, transactions, gas, gasPrice, false, limits.headroomBps);
|
|
629
653
|
try {
|
|
630
|
-
|
|
631
|
-
verified = await lifecycleStage(client, "simulation.replay", () => simulateRpcPass(client, planned, block, transactions, gasLimits, limits, gasPrice, true));
|
|
632
|
-
else {
|
|
633
|
-
if (options.fork === undefined)
|
|
634
|
-
throw new LifecyclePlanningError("INVALID_SIMULATION", "Controlled fork is missing for the exact-gas replay");
|
|
635
|
-
verified = await simulateForkPass(options.fork, planned, block, transactions, gasLimits, gasPrice, false, limits.headroomBps);
|
|
636
|
-
}
|
|
654
|
+
verifiedSteps = await lifecycleStage(client, "simulation.replay", () => replay(gasLimits));
|
|
637
655
|
}
|
|
638
656
|
catch (failure) {
|
|
639
657
|
const kind = simulationFailureKind(failure);
|
|
640
658
|
if (kind === "abort")
|
|
641
659
|
throw failure;
|
|
642
660
|
assertCurrent();
|
|
643
|
-
|
|
644
|
-
if (backend !== "eth_simulateV1") {
|
|
661
|
+
if (kind !== "out-of-gas" || backend !== "eth_simulateV1" || nextEnvelope !== undefined) {
|
|
645
662
|
await assertLifecycleBlock(client, block, planned.chainId);
|
|
646
|
-
return { ...base, backend, admitted: false, steps:
|
|
663
|
+
return { ...base, backend: "unavailable", confidence: "provisional", executionProof: "unavailable", admitted: false, steps: measured, failureCategory: kind === "source" ? "source" : "opaque", reason: `Exact-gas validated replay unavailable: ${lifecycleFailureReason(failure)}` };
|
|
647
664
|
}
|
|
648
|
-
|
|
649
|
-
await assertLifecycleBlock(client, block, planned.chainId);
|
|
650
|
-
return { ...base, backend: "unavailable", confidence: "provisional", executionProof: "unavailable", admitted: false, steps: measured, failureCategory: kind === "source" ? "source" : "opaque", reason: `Exact-gas validated replay unavailable: ${message}` };
|
|
651
|
-
}
|
|
652
|
-
validatedReplayError = message;
|
|
653
|
-
verified = [];
|
|
665
|
+
replayError = lifecycleFailureReason(failure);
|
|
654
666
|
}
|
|
655
667
|
assertCurrent();
|
|
656
|
-
let verifiedSteps = verified;
|
|
657
|
-
let admittedGasLimits = gasLimits;
|
|
658
668
|
let finalFailure = verifiedSteps.find((step) => !step.success);
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
// explicit envelope-discovery replay at the exact execution ceiling on the
|
|
667
|
-
// same pinned block; headroom is never silently raised — the carried gas is
|
|
668
|
-
// the validated envelope itself.
|
|
669
|
+
const failedIndex = verifiedSteps.findIndex((step) => !step.success);
|
|
670
|
+
const rescue = nextEnvelope === undefined && backend === "eth_simulateV1" &&
|
|
671
|
+
(replayError !== undefined && discoveryGas.some((gas, index) => index < transactions.length && gas > gasLimits[index]) ||
|
|
672
|
+
finalFailure !== undefined && looksGasExhausted(finalFailure, gasLimits[failedIndex]) && discoveryGas[failedIndex] > gasLimits[failedIndex]);
|
|
673
|
+
// One legal-envelope rescue covers thrown/returned genuine OOG. Never retry
|
|
674
|
+
// semantic errors, switch replay backends, or enlarge a held immediate envelope.
|
|
675
|
+
if (rescue) {
|
|
669
676
|
assertCurrent();
|
|
670
677
|
try {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const retryFailureIndex = retried?.findIndex((step) => !step.success) ?? -1;
|
|
676
|
-
const retryFailure = retried?.[retryFailureIndex];
|
|
677
|
-
if (retried === undefined) {
|
|
678
|
-
await assertLifecycleBlock(client, block, planned.chainId);
|
|
679
|
-
return { ...base, backend, admitted: false, steps: measured, reason: `Validated execution at the exact current gas ceiling still failed: ${validatedReplayError}` };
|
|
680
|
-
}
|
|
681
|
-
admittedGasLimits = ceilingLimits;
|
|
682
|
-
verifiedSteps = retried;
|
|
683
|
-
finalFailure = retryFailure;
|
|
684
|
-
validatedReplayError = undefined;
|
|
685
|
-
if (retryFailure !== undefined) {
|
|
686
|
-
const kind = transactions[retryFailureIndex]?.kind;
|
|
687
|
-
const indivisible = kind === "activate" || kind === "atomic";
|
|
688
|
-
finalFailureReason = `Validated execution at the exact current gas ceiling still failed: ${retryFailure.error ?? "Execution reverted"}${indivisible ? "; final mint/lock/all-buys/public-opening is indivisible and will not be partitioned" : ""}`;
|
|
689
|
-
}
|
|
678
|
+
admittedGasLimits = discoveryGas.slice(0, transactions.length);
|
|
679
|
+
verifiedSteps = await lifecycleStage(client, "simulation.envelope", () => replay(admittedGasLimits));
|
|
680
|
+
finalFailure = verifiedSteps.find((step) => !step.success);
|
|
681
|
+
replayError = undefined;
|
|
690
682
|
}
|
|
691
683
|
catch (failure) {
|
|
692
684
|
const kind = simulationFailureKind(failure);
|
|
@@ -695,63 +687,15 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
|
|
|
695
687
|
assertCurrent();
|
|
696
688
|
await assertLifecycleBlock(client, block, planned.chainId);
|
|
697
689
|
if (kind !== "out-of-gas")
|
|
698
|
-
return { ...base, backend: "unavailable", confidence: "provisional", executionProof: "unavailable", admitted: false, steps: measured, failureCategory: kind === "source" ? "source" : "opaque", reason: `Legal-envelope validated replay unavailable
|
|
699
|
-
return { ...base, backend, admitted: false, steps: measured, reason: `Validated
|
|
690
|
+
return { ...base, backend: "unavailable", confidence: "provisional", executionProof: "unavailable", admitted: false, steps: measured, failureCategory: kind === "source" ? "source" : "opaque", reason: `Legal-envelope validated replay unavailable: ${lifecycleFailureReason(failure)}` };
|
|
691
|
+
return { ...base, backend, admitted: false, steps: measured, reason: `Validated execution at the exact current gas ceiling still failed: ${lifecycleFailureReason(failure)}` };
|
|
700
692
|
}
|
|
701
693
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const failedTransaction = transactions[failedIndex];
|
|
706
|
-
if (bufferedLimit !== undefined && failedTransaction !== undefined && (nextEnvelope === undefined || failedIndex !== 0) &&
|
|
707
|
-
looksGasExhausted(finalFailure, bufferedLimit) && (envelopeDiscoveryGas[failedIndex] ?? 0n) > bufferedLimit) {
|
|
708
|
-
// Buffered replay returned an exhaustion-shaped failed step; learn the true
|
|
709
|
-
// validated envelope with one explicit replay at the exact execution ceiling.
|
|
710
|
-
assertCurrent();
|
|
711
|
-
try {
|
|
712
|
-
const ceilingLimits = envelopeDiscoveryGas;
|
|
713
|
-
const retried = backend === "eth_simulateV1"
|
|
714
|
-
? await lifecycleStage(client, "simulation.envelope", () => simulateRpcPass(client, planned, block, transactions, ceilingLimits, limits, gasPrice, true))
|
|
715
|
-
: options.fork === undefined ? undefined : await simulateForkPass(options.fork, planned, block, transactions, ceilingLimits, gasPrice, false, limits.headroomBps);
|
|
716
|
-
if (retried === undefined) {
|
|
717
|
-
await assertLifecycleBlock(client, block, planned.chainId);
|
|
718
|
-
return { ...base, backend, admitted: false, steps: measured, reason: "Validated execution at the exact current gas ceiling omitted transaction results" };
|
|
719
|
-
}
|
|
720
|
-
const retryFailureIndex = retried.findIndex((step) => !step.success);
|
|
721
|
-
const retryFailure = retried[retryFailureIndex];
|
|
722
|
-
admittedGasLimits = ceilingLimits;
|
|
723
|
-
verifiedSteps = retried;
|
|
724
|
-
finalFailure = retryFailure;
|
|
725
|
-
if (retryFailure !== undefined) {
|
|
726
|
-
const kind = transactions[retryFailureIndex]?.kind;
|
|
727
|
-
const indivisible = kind === "activate" || kind === "atomic";
|
|
728
|
-
finalFailureReason = `Validated execution at the exact current gas ceiling still failed: ${retryFailure.error ?? "Execution reverted"}${indivisible ? "; final mint/lock/all-buys/public-opening is indivisible and will not be partitioned" : ""}`;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
catch (failure) {
|
|
732
|
-
const kind = simulationFailureKind(failure);
|
|
733
|
-
if (kind === "abort")
|
|
734
|
-
throw failure;
|
|
735
|
-
assertCurrent();
|
|
736
|
-
if (kind !== "out-of-gas") {
|
|
737
|
-
await assertLifecycleBlock(client, block, planned.chainId);
|
|
738
|
-
return { ...base, backend: "unavailable", confidence: "provisional", executionProof: "unavailable", admitted: false, steps: measured, reason: `Legal-envelope validated replay unavailable: ${lifecycleFailureReason(failure)}` };
|
|
739
|
-
}
|
|
740
|
-
await assertLifecycleBlock(client, block, planned.chainId);
|
|
741
|
-
return { ...base, backend, admitted: false, steps: measured, reason: `Validated buffered replay failed (${finalFailure?.error ?? "Execution reverted"}) and execution at the exact gas ceiling still reverted: ${lifecycleFailureReason(failure)}` };
|
|
742
|
-
}
|
|
743
|
-
}
|
|
694
|
+
if (replayError !== undefined) {
|
|
695
|
+
await assertLifecycleBlock(client, block, planned.chainId);
|
|
696
|
+
return { ...base, backend, admitted: false, steps: measured, reason: `Validated execution at the exact current gas ceiling failed: ${replayError}` };
|
|
744
697
|
}
|
|
745
698
|
assertCurrent();
|
|
746
|
-
if (nextEnvelope !== undefined && finalFailure === undefined) {
|
|
747
|
-
const currentMinimum = verifiedSteps[0]?.gasRequired ?? verifiedSteps[0]?.gasUsed ?? nextEnvelope.gas;
|
|
748
|
-
if (nextEnvelope.gas < currentMinimum) {
|
|
749
|
-
await assertLifecycleBlock(client, block, planned.chainId);
|
|
750
|
-
return { ...base, backend, admitted: false, executionProof: "proved", protocolFit: "failed", steps: verifiedSteps,
|
|
751
|
-
failureCategory: "capacity", capacityConstraint: "gas-envelope", failedTransactionId: transactions[0]?.id,
|
|
752
|
-
reason: "Exact reviewed replay exceeds the held gas envelope" };
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
699
|
const estimates = [];
|
|
756
700
|
// A definitive returned replay failure already refuses execution. Retain its
|
|
757
701
|
// fee observations without inventing a balance or doing redundant payer work.
|
|
@@ -783,6 +727,6 @@ async function simulateLaunchTransactionsWithContext(options, transactions, cont
|
|
|
783
727
|
estimates.push({ ...step, estimate: { gasUsed: step.gasUsed ?? 0n, gasLimit, gasPrice, executionFee, dataFee, posterGas: poster, posterFee: poster === undefined ? undefined : poster * gasPrice, dataFeeIncludedInGas: limits.protocol === "nitro", totalFee: dataFee === undefined ? undefined : executionFee + dataFee, feeConfidence: dataFee === undefined ? "execution-only" : "execution-and-data" } });
|
|
784
728
|
}
|
|
785
729
|
await assertLifecycleBlock(client, block, planned.chainId);
|
|
786
|
-
return { ...base, backend, executionProof: finalFailure === undefined ? "proved" : "failed", protocolFit: finalFailure === undefined ? "proved" : "unknown", admitted: finalFailure === undefined && affordabilityFailure === undefined, steps: estimates, reason:
|
|
730
|
+
return { ...base, backend, executionProof: finalFailure === undefined ? "proved" : "failed", protocolFit: finalFailure === undefined ? "proved" : "unknown", admitted: finalFailure === undefined && affordabilityFailure === undefined, steps: estimates, reason: finalFailure?.error ?? affordabilityFailure, failedTransactionId: finalFailure?.transactionId, failureCategory: finalFailure?.failureCategory ?? (affordabilityFailure === undefined ? undefined : "affordability"), capacityConstraint: finalFailure?.failureCategory === "capacity" ? "compute" : undefined };
|
|
787
731
|
}
|
|
788
732
|
//# sourceMappingURL=simulation.js.map
|