Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
54a6319
refactor: extract gateway injection helpers
jdogresorg Sep 17, 2026
6043a22
test(metering): the language version test reads its sources through m…
jdogresorg Sep 17, 2026
91fa28f
refactor: split VM worker message dispatch
jdogresorg Sep 17, 2026
5420fe0
merge: level develop with master for the v0.20.1 patch train
jdogresorg Sep 19, 2026
b7ef448
docs: refresh toolkit test count
jdogresorg Sep 20, 2026
fdd55fc
Land row L009-20 from cx-l009-20
jdogresorg Sep 20, 2026
c2bdcf8
Test mainnet simulator execute-time lint
jdogresorg Sep 20, 2026
61f0b95
Land row L025-17 from cx-sharp-l025-17
jdogresorg Sep 20, 2026
714c8af
test(metering): resolve language version sources extensionless and fa…
jdogresorg Sep 21, 2026
6ab911f
Merge: resolve language version sources extensionless and fail loudly…
jdogresorg Sep 21, 2026
1eb66e7
docs: restore cross-chain export rationale
jdogresorg Sep 21, 2026
67ead53
Merge: docs: restore cross-chain export rationale
jdogresorg Sep 21, 2026
2a70082
fix(vm/gate): use CONTRACT_ECMA_VERSION in meta_validation and scan a…
jdogresorg Sep 22, 2026
a07d864
Merge: fix(vm/gate): use CONTRACT_ECMA_VERSION in meta_validation and…
jdogresorg Sep 22, 2026
4fa2e54
build(ci): grade the fast tier on a push, defer the heavy tiers to th…
jdogresorg Sep 22, 2026
4913783
test(toolkit/gate): add code-point unit tests for meta_validation
jdogresorg Sep 22, 2026
f9bfc0b
Merge: test(toolkit/gate): add code-point unit tests for meta_validation
jdogresorg Sep 22, 2026
0c455ac
test(vm): restore the default watchdog before flush in the queued-req…
jdogresorg Sep 23, 2026
71be32f
fix(vm/ci): install deps before the differential-fuzz diff job compar…
jdogresorg Sep 23, 2026
0c4afae
docs(vm): document the flag-gated tick field on PollResult
jdogresorg Sep 23, 2026
bdaedda
fix(vm): make the simulator match chain deploy gating, oracle roundFl…
jdogresorg Sep 23, 2026
e6d4265
Merge: fix(vm/ci): install deps before the differential-fuzz diff job…
jdogresorg Sep 23, 2026
c22d8ab
chore(release): v0.20.1
jdogresorg Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions .github/workflows/vm-differential-fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ jobs:
with:
node-version: "22"

# compare only diffs already-recorded JSON manifests (no VM execution,
# so isolated-vm need not build from source here), but differential.js
# itself requires fast-check/acorn/astring, so those still need to be
# on disk before the compare step below can run at all.
- name: Install deps
run: npm install --ignore-scripts

- name: Download all manifests
uses: actions/download-artifact@v4
with:
Expand All @@ -121,14 +128,27 @@ jobs:
set -e
REF="manifests/differential.linux-x64-glibc.json"
test -f "$REF" || { echo "reference manifest missing"; exit 1; }
status=0
divergence=0
crash=0
for m in manifests/differential.*.json; do
[ "$m" = "$REF" ] && continue
echo "::group::compare $(basename "$m") vs reference"
node test/fuzz/helpers/differential_run.js compare "$REF" "$m" || status=1
out="$(node test/fuzz/helpers/differential_run.js compare "$REF" "$m" 2>&1)" && rc=0 || rc=$?
printf '%s\n' "$out"
if [ "$rc" -ne 0 ]; then
if printf '%s\n' "$out" | grep -q 'DIFFERENTIAL DIVERGENCE:'; then
divergence=1
else
crash=1
fi
fi
echo "::endgroup::"
done
if [ "$status" -ne 0 ]; then
if [ "$crash" -ne 0 ]; then
echo "::error::VM differential fuzz tool crashed before it could render a verdict (see the compare group's output above); this is not a consensus divergence"
exit 1
fi
if [ "$divergence" -ne 0 ]; then
echo "::error::VM differential fuzz found a cross-build consensus divergence"
exit 1
fi
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.20.1] - 2026-09-23

### Fixed
- Aligned simulator deploy gating, oracle round floors, and balance scoping with chain execution behavior.


## [0.20.0] - 2026-09-17

### Changed
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# XChain Platform Virtual Machine (VM)

<p align="center">
<img src="https://img.shields.io/badge/version-0.20.0-blue" alt="Version">
<img src="https://img.shields.io/badge/version-0.20.1-blue" alt="Version">
<img src="https://img.shields.io/badge/tests-2%2C557%2B%20passing-brightgreen" alt="Tests">
<img src="https://img.shields.io/badge/node-%3E%3D22-green" alt="Node">
<img src="https://img.shields.io/badge/license-AGPL--3.0--or--later-blue" alt="License">
Expand Down Expand Up @@ -201,8 +201,10 @@ runGate(source); // { ok, errors, advisories, warnings, g
// string ("invalid: CONTRACT_MANIFEST (meta required)"). A meta the static walk
// cannot read (computed, spread, non-literal) is an advisory, not a block.

const sim = new ContractSimulator({ coin: 'BTC' });
sim.setBalance('alice', 'GOLD', '1000'); // seed read-only ledger/oracle state
const sim = new ContractSimulator({ coin: 'BTC', defaultCaller: 'alice' });
// Seed read-only ledger/oracle state. A node preloads balances for the caller and
// the contract's own address only, so seed those two; any other address reads null on chain.
sim.setBalance('alice', 'GOLD', '1000');
sim.setAttestationResponse('req-1', '{"ok":true}'); // and the async read surfaces
const { contractIndex } = await sim.deploy(source, { constructorParams: ['5'] });
const res = await sim.call(contractIndex, 'increment', ['3']); // state persists across calls
Expand Down Expand Up @@ -310,7 +312,7 @@ Cross-run and cross-process determinism guarantees in `test/determinism/`: golde

Latency/throughput assertions in `test/performance/` (`npm run test:performance`), distinct from the `bench/` scenario scripts below.

### Toolkit Tests (52)
### Toolkit Tests (141)

`xchain-foundry` / `create-xchain-contract` developer-toolkit coverage: gate, scaffold, and TypeScript-strip logic run on any OS; simulator-backed cases need the isolated-vm binding (Node 22 / Linux).

Expand Down Expand Up @@ -413,7 +415,7 @@ xchain-vm/
| |-- smoke/ (10 tests)
| |-- determinism/ (79 tests, incl. a known-red probe subset)
| |-- performance/ (5 tests, distinct from bench/ below)
| |-- toolkit/ (52 tests: xchain-foundry gate/scaffold/simulate)
| |-- toolkit/ (141 tests: xchain-foundry gate/scaffold/simulate)
| |-- e2e/ (10 E2E test files, 64 tests + helpers + contracts)
| |-- fuzz/ (9 fuzz test files, 57 tests + harness + generators)
| |-- chaos/ (3-phase chaos tests, 76 tests + helpers + contracts)
Expand Down
42 changes: 41 additions & 1 deletion bin/ci-full.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,35 @@ SELF="$(pwd)"
SIB="$(cd .. && pwd)"

FAILED=""
# >>> ci-tier (generated block; re-run the tier wirer to update) >>>
# Tier classes. A push grades the FAST tier only: the unit job, the pin and
# drift guards, and the structure and hygiene checks the hook runs before it
# dispatches. The tiers named below (coverage re-runs, perf scenarios) are
# skipped when the gate sets CI_TIER=fast, and each skip is recorded so the
# closing verdict can never claim a green it did not earn. Nothing stops
# being graded: a scheduled sweep re-runs this same script with CI_TIER=full
# on every repo every three hours and before any release or deploy, and a
# red there is tracked down and fixed first. CI_TIER is unset for a hand
# run, so a bare `npm run ci:full` still runs every tier as it always did.
CI_TIER_FULL_ONLY=(
"coverage ratchet (coverage:check)"
)
DEFERRED=""
ci_tier_deferred() {
[ "${CI_TIER:-full}" = "fast" ] || return 1
local t
for t in ${CI_TIER_FULL_ONLY[@]+"${CI_TIER_FULL_ONLY[@]}"}; do
if [ "$t" = "$1" ]; then
DEFERRED="$DEFERRED [$1]"
echo; echo "ci:full ===== $1 DEFERRED (CI_TIER=fast, runs in the full sweep) ====="
return 0
fi
done
return 1
}
# <<< ci-tier <<<
run_tier() {
ci_tier_deferred "$1" && return 0 # ci-tier guard (generated)
local name="$1"; shift
echo; echo "ci:full ===== $name ====="
if "$@"; then
Expand Down Expand Up @@ -106,8 +134,20 @@ run_tier "identity pin (vendored lint trio)" identity_pin_check
run_tier "coverage ratchet (coverage:check)" npm run coverage:check

echo
# >>> ci-tier summary (generated) >>>
echo "ci:full: tier class ${CI_TIER:-full}"
if [ -n "${DEFERRED:-}" ]; then
echo "ci:full: DEFERRED to the full sweep:$DEFERRED"
fi
# <<< ci-tier summary <<<
if [ -n "$FAILED" ]; then
echo "ci:full: RED tiers:$FAILED"
exit 1
fi
echo "ci:full: all tiers green (same set GitHub CI runs)"
# >>> ci-tier verdict (generated) >>>
if [ "${CI_TIER:-full}" = "fast" ]; then
echo "ci:full: all FAST tiers green; the DEFERRED tiers above were NOT graded here"
else
echo "ci:full: all tiers green (same set GitHub CI runs)"
fi
# <<< ci-tier verdict <<<
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "xchain-vm",
"description": "XChain Platform Virtual Machine (VM) runtime - deterministic smart contract execution in sandboxed V8 isolates",
"version": "0.20.0",
"version": "0.20.1",
"ciPlatform": "linux",
"license": "AGPL-3.0-or-later",
"main": "src/index.js",
Expand Down
6 changes: 6 additions & 0 deletions src/gateway.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ export interface PollResult {
total_voters: number;
decided_early: boolean;
options: Array<{ index: number; weight: string; voters: number }>;
/**
* The poll's electorate token tick (null for a poll bound to no token).
* Present only once the host enables VOTE_POLL_TICK_VISIBLE; absent below it.
* Electorate-pinning contracts (treasury.executeProposal) read it.
*/
tick?: string | null;
}

/** Terminal outcome of a cross-chain call this chain originated. */
Expand Down
7 changes: 4 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1716,7 +1716,7 @@ class XChainVM {
// Map<sha256(code):evalOrderBit+callSpreadBit+restPatternBit, meteredCode>.
// meterCode() is a pure AST transform (acorn parse + walk + astring regen over
// up to maxCodeSize bytes), the single most expensive step of a warm execute,
// and its output depends ONLY on the contract source plus the two consensus
// and its output depends ONLY on the contract source plus the consensus
// gate flags (specEvalOrder, meterCallSpread, meterRestPattern), all baked into
// the key. So a
// hit returns byte-identical metered source to a fresh call: no consensus
Expand All @@ -1731,7 +1731,7 @@ class XChainVM {
this.limits.maxMeteredCacheSize = this.limits.maxBlockCacheSize || 1000;

// Execute-time lint-verdict cache:
// Map<sha256(code):asyncBit:hardenBit:pkg3Bit:aliasBit, {valid, error?}>.
// Map<sha256(code):<one bit per flag parameter of getLintVerdict>, {valid, error?}>.
// INVARIANT: the key carries the code digest plus EVERY consensus flag the
// verdict depends on, with no count written down here that a new flag can
// falsify. Adding a flag to getLintVerdict without adding its bit lets a warm
Expand Down Expand Up @@ -2614,7 +2614,8 @@ module.exports.isSlashTokenDelimGuardActive = isSlashTokenDelimGuardActive;
// exported so a test can pin it against the indexer's MAX_TOKEN_DECIMALS.
module.exports.isSlashAmountPrecisionActive = isSlashAmountPrecisionActive;
module.exports.MAX_SLASH_AMOUNT_DECIMALS = MAX_SLASH_AMOUNT_DECIMALS;
// Cross-CHAIN call (XCALL) protocol constants, same canonical source.
// Export the cross-chain call protocol limits so validator hosts can pin the
// VM's bounds against the values they revalidate while processing emissions.
module.exports.XCALL_MIN_GAS = XCALL_MIN_GAS;
module.exports.XCALL_MAX_GAS = XCALL_MAX_GAS;
module.exports.XCALL_MAX_HOPS = XCALL_MAX_HOPS;
Expand Down
Loading
Loading