From 41d34243e858bc1fd2b7a8561700f0913926f4ab Mon Sep 17 00:00:00 2001 From: Chii Yuen Date: Tue, 21 Jul 2026 14:51:51 +0800 Subject: [PATCH 1/7] refactor(node): extract litesvm core package --- .github/workflows/node-build.yml | 98 +-- .github/workflows/node-docs-check.yml | 8 +- .github/workflows/node-docs.yml | 10 +- .github/workflows/node-lint.yml | 14 +- .github/workflows/release-node.yml | 32 +- .gitignore | 6 +- .../.yarn => .yarn}/releases/yarn-4.10.3.cjs | 0 .../node-litesvm/.yarnrc.yml => .yarnrc.yml | 0 Cargo.toml | 6 +- crates/node-litesvm-core/.cargo/config.toml | 2 + crates/node-litesvm-core/.gitignore | 202 ++++++ crates/node-litesvm-core/.npmignore | 13 + crates/node-litesvm-core/.npmrc | 2 + crates/node-litesvm-core/Cargo.toml | 45 ++ crates/node-litesvm-core/README.md | 26 + crates/node-litesvm-core/build.rs | 5 + crates/node-litesvm-core/litesvm/index.d.ts | 636 ++++++++++++++++++ crates/node-litesvm-core/litesvm/index.js | 618 +++++++++++++++++ crates/node-litesvm-core/package.json | 43 ++ crates/node-litesvm-core/src/account.rs | 64 ++ .../node-litesvm-core/src/compute_budget.rs | 371 ++++++++++ crates/node-litesvm-core/src/feature_set.rs | 76 +++ crates/node-litesvm-core/src/lib.rs | 454 +++++++++++++ crates/node-litesvm-core/src/native_mint.rs | 43 ++ crates/node-litesvm-core/src/sysvar/clock.rs | 97 +++ .../src/sysvar/epoch_rewards.rs | 129 ++++ .../src/sysvar/epoch_schedule.rs | 94 +++ crates/node-litesvm-core/src/sysvar/mod.rs | 7 + crates/node-litesvm-core/src/sysvar/rent.rs | 187 +++++ .../src/sysvar/slot_hashes.rs | 31 + .../src/sysvar/slot_history.rs | 93 +++ .../src/sysvar/stake_history.rs | 87 +++ .../src/transaction_error.rs | 498 ++++++++++++++ .../src/transaction_metadata.rs | 196 ++++++ crates/node-litesvm-core/src/util.rs | 82 +++ package.json | 13 + crates/node-litesvm/yarn.lock => yarn.lock | 18 +- 37 files changed, 4205 insertions(+), 101 deletions(-) rename {crates/node-litesvm/.yarn => .yarn}/releases/yarn-4.10.3.cjs (100%) rename crates/node-litesvm/.yarnrc.yml => .yarnrc.yml (100%) create mode 100644 crates/node-litesvm-core/.cargo/config.toml create mode 100644 crates/node-litesvm-core/.gitignore create mode 100644 crates/node-litesvm-core/.npmignore create mode 100644 crates/node-litesvm-core/.npmrc create mode 100644 crates/node-litesvm-core/Cargo.toml create mode 100644 crates/node-litesvm-core/README.md create mode 100644 crates/node-litesvm-core/build.rs create mode 100644 crates/node-litesvm-core/litesvm/index.d.ts create mode 100644 crates/node-litesvm-core/litesvm/index.js create mode 100644 crates/node-litesvm-core/package.json create mode 100644 crates/node-litesvm-core/src/account.rs create mode 100644 crates/node-litesvm-core/src/compute_budget.rs create mode 100644 crates/node-litesvm-core/src/feature_set.rs create mode 100644 crates/node-litesvm-core/src/lib.rs create mode 100644 crates/node-litesvm-core/src/native_mint.rs create mode 100644 crates/node-litesvm-core/src/sysvar/clock.rs create mode 100644 crates/node-litesvm-core/src/sysvar/epoch_rewards.rs create mode 100644 crates/node-litesvm-core/src/sysvar/epoch_schedule.rs create mode 100644 crates/node-litesvm-core/src/sysvar/mod.rs create mode 100644 crates/node-litesvm-core/src/sysvar/rent.rs create mode 100644 crates/node-litesvm-core/src/sysvar/slot_hashes.rs create mode 100644 crates/node-litesvm-core/src/sysvar/slot_history.rs create mode 100644 crates/node-litesvm-core/src/sysvar/stake_history.rs create mode 100644 crates/node-litesvm-core/src/transaction_error.rs create mode 100644 crates/node-litesvm-core/src/transaction_metadata.rs create mode 100644 crates/node-litesvm-core/src/util.rs create mode 100644 package.json rename crates/node-litesvm/yarn.lock => yarn.lock (99%) diff --git a/.github/workflows/node-build.yml b/.github/workflows/node-build.yml index bef460e97..68bdd8596 100644 --- a/.github/workflows/node-build.yml +++ b/.github/workflows/node-build.yml @@ -25,35 +25,25 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - defaults: - run: - working-directory: crates/node-litesvm steps: - uses: actions/checkout@v6 - - name: Setup node uses: actions/setup-node@v6 with: node-version: 22 - cache: 'yarn' - cache-dependency-path: crates/node-litesvm/yarn.lock - - - name: Install + cache: yarn + cache-dependency-path: yarn.lock + - name: Install Rust uses: dtolnay/rust-toolchain@nightly with: components: clippy, rustfmt - - name: Install dependencies - run: yarn install - - - name: Oxlint - run: yarn lint - + run: corepack enable && yarn install --immutable - name: Cargo fmt - run: cargo fmt -- --check + run: cargo fmt --all -- --check + - name: Clippy core + run: yarn workspace litesvm-core lint - - name: Clippy - run: cargo clippy build: strategy: fail-fast: false @@ -61,27 +51,24 @@ jobs: settings: - host: macos-latest target: x86_64-apple-darwin - build: yarn build --target x86_64-apple-darwin + build: yarn workspace litesvm-core build --target x86_64-apple-darwin - host: macos-latest target: aarch64-apple-darwin - build: yarn build --target aarch64-apple-darwin + build: yarn workspace litesvm-core build --target aarch64-apple-darwin - host: ubuntu-22.04 target: x86_64-unknown-linux-gnu - build: yarn build --target x86_64-unknown-linux-gnu --use-napi-cross + build: yarn workspace litesvm-core build --target x86_64-unknown-linux-gnu --use-napi-cross - host: ubuntu-22.04 target: aarch64-unknown-linux-gnu - build: yarn build --target aarch64-unknown-linux-gnu --use-napi-cross + build: yarn workspace litesvm-core build --target aarch64-unknown-linux-gnu --use-napi-cross - host: ubuntu-latest target: x86_64-unknown-linux-musl - build: yarn build --target x86_64-unknown-linux-musl -x + build: yarn workspace litesvm-core build --target x86_64-unknown-linux-musl -x - host: ubuntu-latest target: aarch64-unknown-linux-musl - build: yarn build --target aarch64-unknown-linux-musl -x + build: yarn workspace litesvm-core build --target aarch64-unknown-linux-musl -x name: stable - ${{ matrix.settings.target }} - node@22 runs-on: ${{ matrix.settings.host }} - defaults: - run: - working-directory: crates/node-litesvm steps: - uses: actions/checkout@v6 - name: Setup node @@ -89,8 +76,8 @@ jobs: with: node-version: 22 cache: yarn - cache-dependency-path: crates/node-litesvm/yarn.lock - - name: Install + cache-dependency-path: yarn.lock + - name: Install Rust uses: dtolnay/rust-toolchain@nightly with: toolchain: stable @@ -122,7 +109,7 @@ jobs: if: ${{ matrix.settings.setup }} shell: bash - name: Install dependencies - run: yarn install + run: corepack enable && yarn install --immutable - name: Build run: ${{ matrix.settings.build }} shell: bash @@ -130,8 +117,9 @@ jobs: uses: actions/upload-artifact@v6 with: name: bindings-${{ matrix.settings.target }} - path: crates/node-litesvm/litesvm/litesvm.*.node + path: crates/node-litesvm-core/litesvm/litesvm.*.node if-no-files-found: error + test-macOS-binding: name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} needs: @@ -150,9 +138,6 @@ jobs: - '20' - '22' runs-on: ${{ matrix.settings.host }} - defaults: - run: - working-directory: crates/node-litesvm steps: - uses: actions/checkout@v6 - name: Setup node @@ -161,19 +146,20 @@ jobs: node-version: ${{ matrix.node }} cache: yarn architecture: ${{ matrix.settings.architecture }} - cache-dependency-path: crates/node-litesvm/yarn.lock + cache-dependency-path: yarn.lock - name: Install dependencies - run: yarn install + run: corepack enable && yarn install --immutable - name: Download artifacts uses: actions/download-artifact@v4 with: name: bindings-${{ matrix.settings.target }} - path: crates/node-litesvm/litesvm - - name: List packages - run: ls -R . + path: crates/node-litesvm-core/litesvm + - name: List bindings + run: ls -R crates/node-litesvm-core/litesvm shell: bash - - name: Test bindings - run: yarn test + - name: Test core bindings + run: yarn workspace litesvm-core test + test-linux-binding: name: Test ${{ matrix.target }} - node@${{ matrix.node }} needs: @@ -186,14 +172,10 @@ jobs: - x86_64-unknown-linux-musl - aarch64-unknown-linux-gnu - aarch64-unknown-linux-musl - # - armv7-unknown-linux-gnueabihf node: - '20' - '22' runs-on: ${{ contains(matrix.target, 'aarch64') && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} - defaults: - run: - working-directory: crates/node-litesvm steps: - uses: actions/checkout@v6 - name: Setup node @@ -201,15 +183,13 @@ jobs: with: node-version: ${{ matrix.node }} cache: yarn - cache-dependency-path: crates/node-litesvm/yarn.lock + cache-dependency-path: yarn.lock - name: Output docker params id: docker run: | node -e " if ('${{ matrix.target }}'.startsWith('aarch64')) { console.log('PLATFORM=linux/arm64') - } else if ('${{ matrix.target }}'.startsWith('armv7')) { - console.log('PLATFORM=linux/arm/v7') } else { console.log('PLATFORM=linux/amd64') } @@ -223,27 +203,21 @@ jobs: " >> $GITHUB_OUTPUT - name: Install dependencies run: | - yarn config set --json supportedArchitectures.cpu '["current", "arm64", "x64", "arm"]' + corepack enable + yarn config set --json supportedArchitectures.cpu '["current", "arm64", "x64"]' yarn config set --json supportedArchitectures.libc '["current", "musl", "gnu"]' - yarn install + yarn install --immutable - name: Download artifacts uses: actions/download-artifact@v6 with: name: bindings-${{ matrix.target }} - path: crates/node-litesvm/litesvm - - name: List packages - run: ls -R . + path: crates/node-litesvm-core/litesvm + - name: List bindings + run: ls -R crates/node-litesvm-core/litesvm shell: bash - # - name: Set up QEMU - # uses: docker/setup-qemu-action@v3 - # if: ${{ contains(matrix.target, 'armv7') }} - # with: - # platforms: all - # - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - # if: ${{ contains(matrix.target, 'armv7') }} - - name: Test bindings + - name: Test core bindings uses: addnab/docker-run-action@v3 with: image: ${{ steps.docker.outputs.IMAGE }} - options: -v ${{ github.workspace }}:${{ github.workspace }} -w ${{ github.workspace }}/crates/node-litesvm --platform ${{ steps.docker.outputs.PLATFORM }} -e RUST_MIN_STACK=8388608 - run: yarn test + options: -v ${{ github.workspace }}:${{ github.workspace }} -w ${{ github.workspace }} --platform ${{ steps.docker.outputs.PLATFORM }} -e RUST_MIN_STACK=8388608 + run: corepack enable && yarn workspace litesvm-core test diff --git a/.github/workflows/node-docs-check.yml b/.github/workflows/node-docs-check.yml index e5264fbf9..f82fbe91d 100644 --- a/.github/workflows/node-docs-check.yml +++ b/.github/workflows/node-docs-check.yml @@ -25,10 +25,8 @@ jobs: with: node-version: 22 cache: yarn - cache-dependency-path: crates/node-litesvm/yarn.lock + cache-dependency-path: yarn.lock - name: Install dependencies - working-directory: crates/node-litesvm - run: yarn install + run: corepack enable && yarn install --immutable - name: Build with VitePress - working-directory: crates/node-litesvm - run: yarn predocs && yarn docs:build + run: yarn workspace litesvm predocs && yarn workspace litesvm docs:build diff --git a/.github/workflows/node-docs.yml b/.github/workflows/node-docs.yml index 96a100a2b..fec4e7f7e 100644 --- a/.github/workflows/node-docs.yml +++ b/.github/workflows/node-docs.yml @@ -25,15 +25,13 @@ jobs: with: node-version: 22 cache: yarn - cache-dependency-path: crates/node-litesvm/yarn.lock + cache-dependency-path: yarn.lock - name: Setup Pages uses: actions/configure-pages@v5 - name: Install dependencies - working-directory: crates/node-litesvm - run: yarn install + run: corepack enable && yarn install --immutable - name: Build with VitePress - working-directory: crates/node-litesvm - run: yarn predocs && yarn docs:build + run: yarn workspace litesvm predocs && yarn workspace litesvm docs:build - name: Upload artifact uses: actions/upload-pages-artifact@v4 with: @@ -49,4 +47,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/node-lint.yml b/.github/workflows/node-lint.yml index db132c851..7afe0e742 100644 --- a/.github/workflows/node-lint.yml +++ b/.github/workflows/node-lint.yml @@ -9,7 +9,7 @@ on: - LICENSE - '**/*.gitignore' - .editorconfig - - node-litesvm/docs/** + - crates/node-litesvm/docs/** jobs: lint: runs-on: ubuntu-latest @@ -20,13 +20,13 @@ jobs: with: node-version: 22 check-latest: true - - name: Rustup install + cache: yarn + cache-dependency-path: yarn.lock + - name: Install Rust uses: dtolnay/rust-toolchain@stable with: components: clippy - name: Install dependencies - run: cd crates/node-litesvm && corepack enable && yarn install - - name: Lint rust - run: cd crates/node-litesvm && yarn lint:rust - - name: Lint JS - run: cd crates/node-litesvm && yarn lint:js + run: corepack enable && yarn install --immutable + - name: Lint Rust core + run: yarn workspace litesvm-core lint diff --git a/.github/workflows/release-node.yml b/.github/workflows/release-node.yml index 8d38d81ab..24a36087d 100644 --- a/.github/workflows/release-node.yml +++ b/.github/workflows/release-node.yml @@ -3,8 +3,9 @@ name: release NodeJS on: push: tags: - - "node-v[0-9]+.[0-9]+.[0-9]+" - - "node-v[0-9]+.[0-9]+.[0-9]+-*" + - "node-v[0-9]+.[0-9]+.[0-9]+" + - "node-v[0-9]+.[0-9]+.[0-9]+-*" + jobs: publish: name: Publish @@ -17,28 +18,25 @@ jobs: node-version: 22 check-latest: true cache: yarn - cache-dependency-path: crates/node-litesvm/yarn.lock + cache-dependency-path: yarn.lock - name: Install dependencies - run: cd crates/node-litesvm/ && corepack enable && yarn install - - name: Create npm dirs - run: cd crates/node-litesvm/ && yarn napi create-npm-dirs - - name: Download all artifacts + run: corepack enable && yarn install --immutable + - name: Create native package directories + run: yarn workspace litesvm-core napi create-npm-dirs + - name: Download all native artifacts uses: dawidd6/action-download-artifact@v11 with: workflow: node-build.yml - path: crates/node-litesvm/artifacts - - name: Move artifacts - run: cd crates/node-litesvm/ && yarn artifacts - - name: Compile Typescript - run: cd crates/node-litesvm/ && yarn build:js - - name: List packages - run: cd crates/node-litesvm/ && ls -R ./npm - - name: Publish + path: crates/node-litesvm-core/artifacts + - name: Move native artifacts + run: yarn workspace litesvm-core artifacts + - name: List native packages + run: ls -R crates/node-litesvm-core/npm + - name: Publish core run: | - cd crates/node-litesvm/ echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc TAG=latest [[ "$GITHUB_REF_NAME" == *-rc.* ]] && TAG=next - npm publish --access public --tag "$TAG" + yarn workspace litesvm-core npm publish --access public --tag "$TAG" env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 517a5a841..5ccc1603f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ target/ tarpaulin-out/ -tarpaulin-out-up/ \ No newline at end of file +tarpaulin-out-up/ +node_modules/ +.yarn/* +!.yarn/releases +!.yarn/releases/yarn-4.10.3.cjs diff --git a/crates/node-litesvm/.yarn/releases/yarn-4.10.3.cjs b/.yarn/releases/yarn-4.10.3.cjs similarity index 100% rename from crates/node-litesvm/.yarn/releases/yarn-4.10.3.cjs rename to .yarn/releases/yarn-4.10.3.cjs diff --git a/crates/node-litesvm/.yarnrc.yml b/.yarnrc.yml similarity index 100% rename from crates/node-litesvm/.yarnrc.yml rename to .yarnrc.yml diff --git a/Cargo.toml b/Cargo.toml index f2451353b..f3ff066f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,11 @@ [workspace] resolver = "2" members = ["crates/*"] -exclude = ["crates/node-litesvm/tests/clock-example"] +exclude = [ + "crates/node-litesvm", + "crates/node-litesvm-web3js", + "crates/node-litesvm/tests/clock-example", +] [workspace.package] version = "0.15.2" diff --git a/crates/node-litesvm-core/.cargo/config.toml b/crates/node-litesvm-core/.cargo/config.toml new file mode 100644 index 000000000..ac2b23f81 --- /dev/null +++ b/crates/node-litesvm-core/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.x86_64-pc-windows-msvc] +rustflags = ["-C", "target-feature=+crt-static"] diff --git a/crates/node-litesvm-core/.gitignore b/crates/node-litesvm-core/.gitignore new file mode 100644 index 000000000..5e0f041cd --- /dev/null +++ b/crates/node-litesvm-core/.gitignore @@ -0,0 +1,202 @@ +# Created by https://www.toptal.com/developers/gitignore/api/node +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# End of https://www.toptal.com/developers/gitignore/api/node + +# Created by https://www.toptal.com/developers/gitignore/api/macos +# Edit at https://www.toptal.com/developers/gitignore?templates=macos + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +# End of https://www.toptal.com/developers/gitignore/api/macos + +# Created by https://www.toptal.com/developers/gitignore/api/windows +# Edit at https://www.toptal.com/developers/gitignore?templates=windows + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/windows + +#Added by cargo + +/target +Cargo.lock + +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +*.node + +cache + +# NAPI-RS generated platform packages (build output) +npm/ diff --git a/crates/node-litesvm-core/.npmignore b/crates/node-litesvm-core/.npmignore new file mode 100644 index 000000000..ec144db2a --- /dev/null +++ b/crates/node-litesvm-core/.npmignore @@ -0,0 +1,13 @@ +target +Cargo.lock +.cargo +.github +npm +.eslintrc +.prettierignore +rustfmt.toml +yarn.lock +*.node +.yarn +__test__ +renovate.json diff --git a/crates/node-litesvm-core/.npmrc b/crates/node-litesvm-core/.npmrc new file mode 100644 index 000000000..589e81379 --- /dev/null +++ b/crates/node-litesvm-core/.npmrc @@ -0,0 +1,2 @@ +engine-strict=false +ignore-engines=true diff --git a/crates/node-litesvm-core/Cargo.toml b/crates/node-litesvm-core/Cargo.toml new file mode 100644 index 000000000..6f6bc26ab --- /dev/null +++ b/crates/node-litesvm-core/Cargo.toml @@ -0,0 +1,45 @@ +[package] +edition = "2021" +name = "litesvm-node" +version = "1.3.0" +include = ["/src", "build.rs"] +repository = "https://github.com/LiteSVM/litesvm" +license = "MIT" + +[features] +default = [] +sbpf-debugger = ["litesvm/sbpf-debugger"] +register-tracing = ["litesvm/register-tracing"] + +[lib] +crate-type = ["cdylib"] +test = false + +[dependencies] +agave-feature-set.workspace = true +bv.workspace = true +litesvm = { workspace = true, features = ["nodejs-internal", "precompiles"] } +napi = { workspace = true, features = ["napi4", "napi6"] } +napi-derive.workspace = true +solana-account.workspace = true +solana-address.workspace = true +solana-clock.workspace = true +solana-compute-budget.workspace = true +solana-epoch-rewards.workspace = true +solana-epoch-schedule.workspace = true +solana-hash.workspace = true +solana-instruction.workspace = true +solana-last-restart-slot.workspace = true +solana-message.workspace = true +solana-rent.workspace = true +solana-signature.workspace = true +solana-slot-hashes.workspace = true +solana-slot-history.workspace = true +solana-stake-history.workspace = true +solana-transaction.workspace = true +solana-transaction-context.workspace = true +solana-transaction-error.workspace = true +wincode.workspace = true + +[build-dependencies] +napi-build.workspace = true diff --git a/crates/node-litesvm-core/README.md b/crates/node-litesvm-core/README.md new file mode 100644 index 000000000..151529795 --- /dev/null +++ b/crates/node-litesvm-core/README.md @@ -0,0 +1,26 @@ +# LiteSVM Core + +`litesvm-core` contains the raw Node.js NAPI bindings for +[LiteSVM](https://github.com/LiteSVM/litesvm). It is the shared native runtime +used by the SDK-specific `litesvm` and `litesvm-web3js` packages. + +Most applications should install one of the SDK wrappers instead: + +- `litesvm` for `@solana/kit` +- `litesvm-web3js` for `@solana/web3.js` v3 + +Install this package directly only when you need the byte-oriented generated +NAPI API: + +```sh +yarn add litesvm-core +``` + +```ts +import { LiteSvm } from "litesvm-core"; + +const svm = new LiteSvm(); +``` + +The core package intentionally has no dependency on either Solana JavaScript +SDK. diff --git a/crates/node-litesvm-core/build.rs b/crates/node-litesvm-core/build.rs new file mode 100644 index 000000000..9fc236788 --- /dev/null +++ b/crates/node-litesvm-core/build.rs @@ -0,0 +1,5 @@ +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/crates/node-litesvm-core/litesvm/index.d.ts b/crates/node-litesvm-core/litesvm/index.d.ts new file mode 100644 index 000000000..d18fb2902 --- /dev/null +++ b/crates/node-litesvm-core/litesvm/index.d.ts @@ -0,0 +1,636 @@ +/* auto-generated by NAPI-RS */ +/* eslint-disable */ +export declare class Account { + constructor(lamports: bigint, data: Uint8Array, owner: Uint8Array, executable: boolean, rentEpoch: bigint) + lamports(): bigint + data(): Uint8Array + owner(): Uint8Array + executable(): boolean + rentEpoch(): bigint + toString(): string +} + +export declare class AddressAndAccount { + address: Uint8Array + account(): Account +} + +/** + * A representation of network time. + * + * All members of `Clock` start from 0 upon network boot. + */ +export declare class Clock { + /** + * @param slot - The current Slot. + * @param epochStartTimestamp - The timestamp of the first `Slot` in this `Epoch`. + * @param epoch - The current epoch. + * @param leaderScheduleEpoch - The future Epoch for which the leader schedule has most recently been calculated. + * @param unixTimestamp - The approximate real world time of the current slot. + */ + constructor(slot: bigint, epochStartTimestamp: bigint, epoch: bigint, leaderScheduleEpoch: bigint, unixTimestamp: bigint) + /** The current Slot. */ + get slot(): bigint + set slot(val: bigint) + /** The current epoch. */ + get epoch(): bigint + set epoch(val: bigint) + /** The timestamp of the first `Slot` in this `Epoch`. */ + get epochStartTimestamp(): bigint + set epochStartTimestamp(val: bigint) + /** The future Epoch for which the leader schedule has most recently been calculated. */ + get leaderScheduleEpoch(): bigint + set leaderScheduleEpoch(val: bigint) + /** The approximate real world time of the current slot. */ + get unixTimestamp(): bigint + set unixTimestamp(val: bigint) + toString(): string +} + +export declare class CompiledInstruction { + constructor(programIdIndex: number, accounts: Uint8Array, data: Uint8Array) + programIdIndex(): number + accounts(): Uint8Array + data(): Uint8Array + toString(): string +} + +export declare class ComputeBudget { + constructor() + get computeUnitLimit(): bigint + set computeUnitLimit(limit: bigint) + set log64Units(val: bigint) + get log64Units(): bigint + set createProgramAddressUnits(val: bigint) + get createProgramAddressUnits(): bigint + set invokeUnits(val: bigint) + get invokeUnits(): bigint + set maxInstructionStackDepth(val: bigint) + get maxInstructionStackDepth(): bigint + set maxInstructionTraceLength(val: bigint) + get maxInstructionTraceLength(): bigint + set sha256BaseCost(val: bigint) + get sha256BaseCost(): bigint + set sha256ByteCost(val: bigint) + get sha256ByteCost(): bigint + set sha256MaxSlices(val: bigint) + get sha256MaxSlices(): bigint + set maxCallDepth(val: bigint) + get maxCallDepth(): bigint + set stackFrameSize(val: bigint) + get stackFrameSize(): bigint + set logPubkeyUnits(val: bigint) + get logPubkeyUnits(): bigint + set cpiBytesPerUnit(val: bigint) + get cpiBytesPerUnit(): bigint + set sysvarBaseCost(val: bigint) + get sysvarBaseCost(): bigint + set secp256K1RecoverCost(val: bigint) + get secp256K1RecoverCost(): bigint + set syscallBaseCost(val: bigint) + get syscallBaseCost(): bigint + set curve25519EdwardsValidatePointCost(val: bigint) + get curve25519EdwardsValidatePointCost(): bigint + set curve25519EdwardsAddCost(val: bigint) + get curve25519EdwardsAddCost(): bigint + set curve25519EdwardsSubtractCost(val: bigint) + get curve25519EdwardsSubtractCost(): bigint + set curve25519EdwardsMultiplyCost(val: bigint) + get curve25519EdwardsMultiplyCost(): bigint + set curve25519EdwardsMsmBaseCost(val: bigint) + get curve25519EdwardsMsmBaseCost(): bigint + set curve25519EdwardsMsmIncrementalCost(val: bigint) + get curve25519EdwardsMsmIncrementalCost(): bigint + set curve25519RistrettoValidatePointCost(val: bigint) + get curve25519RistrettoValidatePointCost(): bigint + set curve25519RistrettoAddCost(val: bigint) + get curve25519RistrettoAddCost(): bigint + set curve25519RistrettoSubtractCost(val: bigint) + get curve25519RistrettoSubtractCost(): bigint + set curve25519RistrettoMultiplyCost(val: bigint) + get curve25519RistrettoMultiplyCost(): bigint + set curve25519RistrettoMsmBaseCost(val: bigint) + get curve25519RistrettoMsmBaseCost(): bigint + set curve25519RistrettoMsmIncrementalCost(val: bigint) + get curve25519RistrettoMsmIncrementalCost(): bigint + set heapSize(val: number) + get heapSize(): number + set heapCost(val: bigint) + get heapCost(): bigint + set memOpBaseCost(val: bigint) + get memOpBaseCost(): bigint + set altBn128AdditionCost(val: bigint) + get altBn128AdditionCost(): bigint + set altBn128MultiplicationCost(val: bigint) + get altBn128MultiplicationCost(): bigint + set altBn128PairingOnePairCostFirst(val: bigint) + get altBn128PairingOnePairCostFirst(): bigint + set altBn128PairingOnePairCostOther(val: bigint) + get altBn128PairingOnePairCostOther(): bigint + set bigModularExponentiationBaseCost(val: bigint) + get bigModularExponentiationBaseCost(): bigint + set bigModularExponentiationCostDivisor(val: bigint) + get bigModularExponentiationCostDivisor(): bigint + set poseidonCostCoefficientA(val: bigint) + get poseidonCostCoefficientA(): bigint + set poseidonCostCoefficientC(val: bigint) + get poseidonCostCoefficientC(): bigint + set getRemainingComputeUnitsCost(val: bigint) + get getRemainingComputeUnitsCost(): bigint + set altBn128G1Compress(val: bigint) + get altBn128G1Compress(): bigint + set altBn128G1Decompress(val: bigint) + get altBn128G1Decompress(): bigint + set altBn128G2Compress(val: bigint) + get altBn128G2Compress(): bigint + set altBn128G2Decompress(val: bigint) + get altBn128G2Decompress(): bigint +} + +/** A type to hold data for the EpochRewards sysvar. */ +export declare class EpochRewards { + /** + * @param distribution_starting_block_height - The starting block height of the rewards distribution in the current epoch + * @param num_partitions - Number of partitions in the rewards distribution in the current epoch + * @param parent_blockhash - The blockhash of the parent block of the first block in the epoch + * @param total_points - The total rewards points calculated for the current epoch + * @param total_rewards - The total rewards calculated for the current epoch + * @param distributed_rewards - The rewards currently distributed for the current epoch, in lamports + * @param active - Whether the rewards period (including calculation and distribution) is active + */ + constructor(distributionStartingBlockHeight: bigint, numPartitions: bigint, parentBlockhash: string, totalPoints: bigint, totalRewards: bigint, distributedRewards: bigint, active: boolean) + /** + * The starting block height of the rewards distribution in the current + * epoch + */ + get distributionStartingBlockHeight(): bigint + set distributionStartingBlockHeight(val: bigint) + /** + * Number of partitions in the rewards distribution in the current epoch, + * used to generate an EpochRewardsHasher + */ + get numPartitions(): bigint + set numPartitions(val: bigint) + /** + * The blockhash of the parent block of the first block in the epoch, used + * to seed an EpochRewardsHasher + */ + get parentBlockhash(): string + set parentBlockhash(val: string) + /** + * The total rewards points calculated for the current epoch, where points + * equals the sum of (delegated stake * credits observed) for all + * delegations + */ + get totalPoints(): bigint + set totalPoints(val: bigint) + /** + * The total rewards calculated for the current epoch. This may be greater + * than the total `distributed_rewards` at the end of the rewards period, + * due to rounding and inability to deliver rewards smaller than 1 lamport. + */ + get totalRewards(): bigint + set totalRewards(val: bigint) + /** The rewards currently distributed for the current epoch, in lamports */ + get distributedRewards(): bigint + set distributedRewards(val: bigint) + /** + * Whether the rewards period (including calculation and distribution) is + * active + */ + get active(): boolean + set active(val: boolean) +} + +/** Configuration for epochs and slots. */ +export declare class EpochSchedule { + /** + * @param slots_per_epoch - The maximum number of slots in each epoch. + * @param leader_schedule_slot_offset - A number of slots before beginning of an epoch to calculate a leader schedule for that epoch. + * @param warmup - Whether epochs start short and grow. + * @param first_normal_epoch - The first epoch after the warmup period. + * @param first_normal_slot - The first slot after the warmup period. + */ + constructor(slotsPerEpoch: bigint, leaderScheduleSlotOffset: bigint, warmup: boolean, firstNormalEpoch: bigint, firstNormalSlot: bigint) + /** The maximum number of slots in each epoch. */ + get slotsPerEpoch(): bigint + set slotsPerEpoch(val: bigint) + /** + * A number of slots before beginning of an epoch to calculate + * a leader schedule for that epoch. + */ + get leaderScheduleSlotOffset(): bigint + set leaderScheduleSlotOffset(val: bigint) + /** Whether epochs start short and grow. */ + get warmup(): boolean + set warmup(val: boolean) + /** + * The first epoch after the warmup period. + * + * Basically: `log2(slots_per_epoch) - log2(MINIMUM_SLOTS_PER_EPOCH)`. + */ + get firstNormalEpoch(): bigint + set firstNormalEpoch(val: bigint) + /** + * The first slot after the warmup period. + * + * Basically: `MINIMUM_SLOTS_PER_EPOCH * (2.pow(first_normal_epoch) - 1)`. + */ + get firstNormalSlot(): bigint + set firstNormalSlot(val: bigint) +} + +export declare class FailedTransactionMetadata { + err(): TransactionErrorFieldless | TransactionErrorInstructionError | TransactionErrorDuplicateInstruction | TransactionErrorInsufficientFundsForRent | TransactionErrorProgramExecutionTemporarilyRestricted + meta(): TransactionMetadata + toString(): string +} + +export declare class FeatureSet { + constructor() + static allEnabled(): FeatureSet + isActive(featureId: Uint8Array): boolean + activatedSlot(featureId: Uint8Array): bigint | null + activate(featureId: Uint8Array, slot: bigint): void + deactivate(featureId: Uint8Array): void + getActiveFeatures(): Array + getInactiveFeatures(): Array + getActiveFeaturesCount(): number + getInactiveFeaturesCount(): number + toString(): string +} + +export declare class InnerInstruction { + instruction(): CompiledInstruction + stackHeight(): number + toString(): string +} + +export declare class InstructionErrorBorshIo { + msg: string + toString(): string +} +export type InstructionErrorBorshIO = InstructionErrorBorshIo + +export declare class InstructionErrorCustom { + code: number + toString(): string +} + +export declare class LiteSvm { + /** Creates the basic test environment. */ + constructor() + static default(): LiteSvm + setComputeBudget(budget: ComputeBudget): void + /** Enables or disables sigverify */ + setSigverify(sigverify: boolean): void + /** Enables or disables the blockhash check */ + setBlockhashCheck(check: boolean): void + /** Includes the default sysvars */ + setSysvars(): void + /** Changes the default builtins */ + setFeatureSet(featureSet: FeatureSet): void + /** Changes the default builtins */ + setBuiltins(): void + /** Changes the initial lamports in LiteSVM's airdrop account */ + setLamports(lamports: bigint): void + /** Includes the standard SPL programs */ + setDefaultPrograms(): void + /** + * Changes the capacity of the transaction history. + * Set this to 0 to disable transaction history and allow duplicate transactions. + */ + setTransactionHistory(capacity: bigint): void + setLogBytesLimit(limit?: bigint | undefined | null): void + setPrecompiles(): void + /** Returns minimum balance required to make an account with specified data length rent exempt. */ + minimumBalanceForRentExemption(dataLen: bigint): bigint + /** Returns all information associated with the account of the provided pubkey. */ + getAccount(pubkey: Uint8Array): Account | null + /** Returns all accounts owned by the given program, together with their addresses. */ + getProgramAccounts(programId: Uint8Array): Array + /** Sets all information associated with the account of the provided pubkey. */ + setAccount(pubkey: Uint8Array, data: Account): void + /** Gets the balance of the provided account pubkey. */ + getBalance(pubkey: Uint8Array): bigint | null + /** Gets the latest blockhash. */ + latestBlockhash(): string + /** Gets a transaction from the transaction history. */ + getTransaction(signature: Uint8Array): TransactionMetadata | FailedTransactionMetadata | null + /** Airdrops the account with the lamports specified. */ + airdrop(pubkey: Uint8Array, lamports: bigint): TransactionMetadata | FailedTransactionMetadata | null + /** Adds am SBF program to the test environment from the file specified. */ + addProgramFromFile(programId: Uint8Array, path: string): void + /** Adds am SBF program to the test environment. */ + addProgram(programId: Uint8Array, programBytes: Uint8Array): void + /** Adds an SBF program with a specific loader. */ + addProgramWithLoader(programId: Uint8Array, programBytes: Uint8Array, loaderId: Uint8Array): void + sendLegacyTransaction(txBytes: Uint8Array): TransactionMetadata | FailedTransactionMetadata + sendVersionedTransaction(txBytes: Uint8Array): TransactionMetadata | FailedTransactionMetadata + simulateLegacyTransaction(txBytes: Uint8Array): SimulatedTransactionInfo | FailedTransactionMetadata + simulateVersionedTransaction(txBytes: Uint8Array): SimulatedTransactionInfo | FailedTransactionMetadata + /** Expires the current blockhash */ + expireBlockhash(): void + /** Warps the clock to the specified slot */ + warpToSlot(slot: bigint): void + getComputeBudget(): ComputeBudget | null + getSigverify(): boolean + getClock(): Clock + setClock(clock: Clock): void + getRent(): Rent + setRent(rent: Rent): void + getEpochRewards(): EpochRewards + setEpochRewards(rewards: EpochRewards): void + getEpochSchedule(): EpochSchedule + setEpochSchedule(schedule: EpochSchedule): void + getLastRestartSlot(): bigint + setLastRestartSlot(slot: bigint): void + getSlotHashes(): Array + setSlotHashes(hashes: Array): void + getSlotHistory(): SlotHistory + setSlotHistory(history: SlotHistory): void + getStakeHistory(): StakeHistory + setStakeHistory(history: StakeHistory): void + withNativeMints(): void +} + +/** Configuration of network rent. */ +export declare class Rent { + /** + * @param lamportsPerByteYear - Rental rate in lamports/byte-year. + * @param exemptionThreshold - Amount of time (in years) a balance must include rent for the account to be rent exempt. + * @param burnPercent - The percentage of collected rent that is burned. + */ + constructor(lamportsPerByteYear: bigint, exemptionThreshold: number, burnPercent: number) + /** Initialize rent with the default Solana settings. */ + static default(): Rent + /** + * Rental rate in lamports/byte-year. + * + * Note: since SIMD-0194 the underlying sysvar stores lamports/byte with + * the exemption threshold folded in (default 6960, threshold 1.0), so on + * current clusters this returns double the historical 3480 value. + */ + get lamportsPerByteYear(): bigint + set lamportsPerByteYear(val: bigint) + /** Amount of time (in years) a balance must include rent for the account to be rent exempt. */ + get exemptionThreshold(): number + set exemptionThreshold(val: number) + /** The percentage of collected rent that is burned. */ + get burnPercent(): number + set burnPercent(val: number) + /** + * Calculate how much rent to burn from the collected rent. + * + * The first value returned is the amount burned. The second is the amount + * to distribute to validators. + * + * @param rentCollected: The amount of rent collected. + * @returns The amount burned and the amount to distribute to validators. + */ + calculateBurn(rentCollected: bigint): [bigint, bigint] + /** + * Minimum balance due for rent-exemption of a given account data size. + * + * Note: a stripped-down version of this calculation is used in + * ``calculate_split_rent_exempt_reserve`` in the stake program. When this + * function is updated, eg. when making rent variable, the stake program + * will need to be refactored. + * + * @param dataLen - The account data size. + * @returns The minimum balance due. + */ + minimumBalance(dataLen: bigint): bigint + /** Whether a given balance and data length would be exempt. */ + isExempt(balance: bigint, dataLen: bigint): boolean + /** + * Rent due on account's data length with balance. + * + * @param balance - The account balance. + * @param dataLen - The account data length. + * @param yearsElapsed - Time elapsed in years. + * @returns The rent due. + */ + due(balance: bigint, dataLen: bigint, yearsElapsed: number): bigint | null + /** + * Rent due for account that is known to be not exempt. + * + * Note: since SIMD-0194 the underlying rate is lamports/byte with the + * exemption threshold folded in, so on current clusters this returns + * double the pre-SIMD-0194 amount for the same inputs. + * + * @param dataLen - The account data length. + * @param yearsElapsed - Time elapsed in years. + * @returns The amount due. + */ + dueAmount(dataLen: bigint, yearsElapsed: number): bigint + /** + * Creates a `Rent` that charges no lamports. + * + * This is used for testing. + */ + static free(): Rent + /** + * Creates a `Rent` that is scaled based on the number of slots in an epoch. + * + * This is used for testing. + * + * @deprecated Epoch-based rent scaling was removed upstream (SIMD-0194); + * the argument is ignored and the default rent is returned. The old + * scaling never affected the rent-exempt minimum balance. + */ + static withSlotsPerEpoch(slotsPerEpoch: bigint): Rent + toString(): string +} + +export declare class SimulatedTransactionInfo { + meta(): TransactionMetadata + postAccounts(): Array + toString(): string +} + +export declare class SlotHash { + get slot(): bigint + get hash(): string + set slot(slot: bigint) + set hash(hash: string) +} + +/** A bitvector indicating which slots are present in the past epoch. */ +export declare class SlotHistory { + constructor(bits: BigUint64Array, nextSlot: bigint) + static default(): SlotHistory + get bits(): BigUint64Array + set bits(bits: BigUint64Array) + get nextSlot(): bigint + set nextSlot(slot: bigint) + add(slot: bigint): void + check(slot: bigint): SlotHistoryCheck + oldest(): bigint + newest(): bigint + toString(): string +} + +/** A type to hold data for the StakeHistory sysvar. */ +export declare class StakeHistory { + constructor() + get(epoch: bigint): StakeHistoryEntry | null + add(epoch: bigint, entry: StakeHistoryEntry): void +} + +export declare class StakeHistoryEntry { + /** + * @param effective - effective stake at this epoch + * @param activating - sum of portion of stakes not fully warmed up + * @param effective - requested to be cooled down, not fully deactivated yet + */ + constructor(effective: bigint, activating: bigint, deactivating: bigint) + /** effective stake at this epoch */ + get effective(): bigint + set effective(val: bigint) + /** sum of portion of stakes not fully warmed up */ + get activating(): bigint + set activating(val: bigint) + /** requested to be cooled down, not fully deactivated yet */ + get deactivating(): bigint + set deactivating(val: bigint) + toString(): string +} + +export declare class TransactionErrorDuplicateInstruction { + index: number + toString(): string +} + +export declare class TransactionErrorInstructionError { + index: number + err(): InstructionErrorFieldless | InstructionErrorCustom | InstructionErrorBorshIO + toString(): string +} + +export declare class TransactionErrorInsufficientFundsForRent { + accountIndex: number + toString(): string +} + +export declare class TransactionErrorProgramExecutionTemporarilyRestricted { + accountIndex: number + toString(): string +} + +export declare class TransactionMetadata { + signature(): Uint8Array + logs(): Array + innerInstructions(): Array> + computeUnitsConsumed(): bigint + returnData(): TransactionReturnData + prettyLogs(): string + toString(): string +} + +export declare class TransactionReturnData { + programId(): Uint8Array + data(): Uint8Array + toString(): string +} + +export declare const enum InstructionErrorFieldless { + GenericError = 0, + InvalidArgument = 1, + InvalidInstructionData = 2, + InvalidAccountData = 3, + AccountDataTooSmall = 4, + InsufficientFunds = 5, + IncorrectProgramId = 6, + MissingRequiredSignature = 7, + AccountAlreadyInitialized = 8, + UninitializedAccount = 9, + UnbalancedInstruction = 10, + ModifiedProgramId = 11, + ExternalAccountLamportSpend = 12, + ExternalAccountDataModified = 13, + ReadonlyLamportChange = 14, + ReadonlyDataModified = 15, + DuplicateAccountIndex = 16, + ExecutableModified = 17, + RentEpochModified = 18, + NotEnoughAccountKeys = 19, + AccountDataSizeChanged = 20, + AccountNotExecutable = 21, + AccountBorrowFailed = 22, + AccountBorrowOutstanding = 23, + DuplicateAccountOutOfSync = 24, + InvalidError = 25, + ExecutableDataModified = 26, + ExecutableLamportChange = 27, + ExecutableAccountNotRentExempt = 28, + UnsupportedProgramId = 29, + CallDepth = 30, + MissingAccount = 31, + ReentrancyNotAllowed = 32, + MaxSeedLengthExceeded = 33, + InvalidSeeds = 34, + InvalidRealloc = 35, + ComputationalBudgetExceeded = 36, + PrivilegeEscalation = 37, + ProgramEnvironmentSetupFailure = 38, + ProgramFailedToComplete = 39, + ProgramFailedToCompile = 40, + Immutable = 41, + IncorrectAuthority = 42, + AccountNotRentExempt = 43, + InvalidAccountOwner = 44, + ArithmeticOverflow = 45, + UnsupportedSysvar = 46, + IllegalOwner = 47, + MaxAccountsDataAllocationsExceeded = 48, + MaxAccountsExceeded = 49, + MaxInstructionTraceLengthExceeded = 50, + BuiltinProgramsMustConsumeComputeUnits = 51, + BorshIoError = 52 +} + +export declare const enum SlotHistoryCheck { + Future = 0, + TooOld = 1, + Found = 2, + NotFound = 3 +} + +export declare const enum TransactionErrorFieldless { + AccountInUse = 0, + AccountLoadedTwice = 1, + AccountNotFound = 2, + ProgramAccountNotFound = 3, + InsufficientFundsForFee = 4, + InvalidAccountForFee = 5, + AlreadyProcessed = 6, + BlockhashNotFound = 7, + CallChainTooDeep = 8, + MissingSignatureForFee = 9, + InvalidAccountIndex = 10, + SignatureFailure = 11, + InvalidProgramForExecution = 12, + SanitizeFailure = 13, + ClusterMaintenance = 14, + AccountBorrowOutstanding = 15, + WouldExceedMaxBlockCostLimit = 16, + UnsupportedVersion = 17, + InvalidWritableAccount = 18, + WouldExceedMaxAccountCostLimit = 19, + WouldExceedAccountDataBlockLimit = 20, + TooManyAccountLocks = 21, + AddressLookupTableNotFound = 22, + InvalidAddressLookupTableOwner = 23, + InvalidAddressLookupTableData = 24, + InvalidAddressLookupTableIndex = 25, + InvalidRentPayingAccount = 26, + WouldExceedMaxVoteCostLimit = 27, + WouldExceedAccountDataTotalLimit = 28, + MaxLoadedAccountsDataSizeExceeded = 29, + ResanitizationNeeded = 30, + InvalidLoadedAccountsDataSizeLimit = 31, + UnbalancedTransaction = 32, + ProgramCacheHitMaxLimit = 33, + CommitCancelled = 34 +} diff --git a/crates/node-litesvm-core/litesvm/index.js b/crates/node-litesvm-core/litesvm/index.js new file mode 100644 index 000000000..e288bd4c4 --- /dev/null +++ b/crates/node-litesvm-core/litesvm/index.js @@ -0,0 +1,618 @@ +// prettier-ignore +/* eslint-disable */ +// @ts-nocheck +/* auto-generated by NAPI-RS */ + +const { readFileSync } = require('fs') +let nativeBinding = null +const loadErrors = [] + +const isMusl = () => { + let musl = false + if (process.platform === 'linux') { + musl = isMuslFromFilesystem() + if (musl === null) { + musl = isMuslFromReport() + } + if (musl === null) { + musl = isMuslFromChildProcess() + } + } + return musl +} + +const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-') + +const isMuslFromFilesystem = () => { + try { + return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl') + } catch { + return null + } +} + +const isMuslFromReport = () => { + let report = null + if (process.report && typeof process.report.getReport === 'function') { + process.report.excludeNetwork = true + report = process.report.getReport() + } + if (!report) { + return null + } + if (report.header && report.header.glibcVersionRuntime) { + return false + } + if (Array.isArray(report.sharedObjects)) { + if (report.sharedObjects.some(isFileMusl)) { + return true + } + } + return false +} + +const isMuslFromChildProcess = () => { + try { + return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl') + } catch (e) { + // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false + return false + } +} + +function requireNative() { + if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) { + try { + return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); + } catch (err) { + loadErrors.push(err) + } + } else if (process.platform === 'android') { + if (process.arch === 'arm64') { + try { + return require('./litesvm.android-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-android-arm64') + const bindingPackageVersion = require('litesvm-core-android-arm64/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'arm') { + try { + return require('./litesvm.android-arm-eabi.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-android-arm-eabi') + const bindingPackageVersion = require('litesvm-core-android-arm-eabi/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)) + } + } else if (process.platform === 'win32') { + if (process.arch === 'x64') { + if ((process.config && process.config.variables && process.config.variables.shlib_suffix === 'dll.a') || (process.config && process.config.variables && process.config.variables.node_target_type === 'shared_library')) { + try { + return require('./litesvm.win32-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-win32-x64-gnu') + const bindingPackageVersion = require('litesvm-core-win32-x64-gnu/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./litesvm.win32-x64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-win32-x64-msvc') + const bindingPackageVersion = require('litesvm-core-win32-x64-msvc/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'ia32') { + try { + return require('./litesvm.win32-ia32-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-win32-ia32-msvc') + const bindingPackageVersion = require('litesvm-core-win32-ia32-msvc/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'arm64') { + try { + return require('./litesvm.win32-arm64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-win32-arm64-msvc') + const bindingPackageVersion = require('litesvm-core-win32-arm64-msvc/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)) + } + } else if (process.platform === 'darwin') { + try { + return require('./litesvm.darwin-universal.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-darwin-universal') + const bindingPackageVersion = require('litesvm-core-darwin-universal/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + if (process.arch === 'x64') { + try { + return require('./litesvm.darwin-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-darwin-x64') + const bindingPackageVersion = require('litesvm-core-darwin-x64/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'arm64') { + try { + return require('./litesvm.darwin-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-darwin-arm64') + const bindingPackageVersion = require('litesvm-core-darwin-arm64/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)) + } + } else if (process.platform === 'freebsd') { + if (process.arch === 'x64') { + try { + return require('./litesvm.freebsd-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-freebsd-x64') + const bindingPackageVersion = require('litesvm-core-freebsd-x64/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'arm64') { + try { + return require('./litesvm.freebsd-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-freebsd-arm64') + const bindingPackageVersion = require('litesvm-core-freebsd-arm64/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)) + } + } else if (process.platform === 'linux') { + if (process.arch === 'x64') { + if (isMusl()) { + try { + return require('./litesvm.linux-x64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-x64-musl') + const bindingPackageVersion = require('litesvm-core-linux-x64-musl/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./litesvm.linux-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-x64-gnu') + const bindingPackageVersion = require('litesvm-core-linux-x64-gnu/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'arm64') { + if (isMusl()) { + try { + return require('./litesvm.linux-arm64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-arm64-musl') + const bindingPackageVersion = require('litesvm-core-linux-arm64-musl/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./litesvm.linux-arm64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-arm64-gnu') + const bindingPackageVersion = require('litesvm-core-linux-arm64-gnu/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'arm') { + if (isMusl()) { + try { + return require('./litesvm.linux-arm-musleabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-arm-musleabihf') + const bindingPackageVersion = require('litesvm-core-linux-arm-musleabihf/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./litesvm.linux-arm-gnueabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-arm-gnueabihf') + const bindingPackageVersion = require('litesvm-core-linux-arm-gnueabihf/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'loong64') { + if (isMusl()) { + try { + return require('./litesvm.linux-loong64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-loong64-musl') + const bindingPackageVersion = require('litesvm-core-linux-loong64-musl/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./litesvm.linux-loong64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-loong64-gnu') + const bindingPackageVersion = require('litesvm-core-linux-loong64-gnu/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'riscv64') { + if (isMusl()) { + try { + return require('./litesvm.linux-riscv64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-riscv64-musl') + const bindingPackageVersion = require('litesvm-core-linux-riscv64-musl/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + try { + return require('./litesvm.linux-riscv64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-riscv64-gnu') + const bindingPackageVersion = require('litesvm-core-linux-riscv64-gnu/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } + } else if (process.arch === 'ppc64') { + try { + return require('./litesvm.linux-ppc64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-ppc64-gnu') + const bindingPackageVersion = require('litesvm-core-linux-ppc64-gnu/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 's390x') { + try { + return require('./litesvm.linux-s390x-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-linux-s390x-gnu') + const bindingPackageVersion = require('litesvm-core-linux-s390x-gnu/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)) + } + } else if (process.platform === 'openharmony') { + if (process.arch === 'arm64') { + try { + return require('./litesvm.openharmony-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-openharmony-arm64') + const bindingPackageVersion = require('litesvm-core-openharmony-arm64/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'x64') { + try { + return require('./litesvm.openharmony-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-openharmony-x64') + const bindingPackageVersion = require('litesvm-core-openharmony-x64/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else if (process.arch === 'arm') { + try { + return require('./litesvm.openharmony-arm.node') + } catch (e) { + loadErrors.push(e) + } + try { + const binding = require('litesvm-core-openharmony-arm') + const bindingPackageVersion = require('litesvm-core-openharmony-arm/package.json').version + if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { + throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + } + return binding + } catch (e) { + loadErrors.push(e) + } + } else { + loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`)) + } + } else { + loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)) + } +} + +nativeBinding = requireNative() + +// NAPI_RS_FORCE_WASI is a tri-state flag: +// unset / any other value → native binding preferred, WASI is only a fallback +// 'true' → force WASI fallback even if native loaded +// 'error' → force WASI and throw if no WASI binding is found +// Treating any non-empty string as truthy (the historical behavior) meant +// NAPI_RS_FORCE_WASI=false, NAPI_RS_FORCE_WASI=0, etc. inadvertently triggered +// the WASI path, causing ENOENT for packages shipped without a .wasi.cjs file. +const forceWasi = + process.env.NAPI_RS_FORCE_WASI === 'true' || process.env.NAPI_RS_FORCE_WASI === 'error' + +if (!nativeBinding || forceWasi) { + let wasiBinding = null + let wasiBindingError = null + try { + wasiBinding = require('./litesvm.wasi.cjs') + nativeBinding = wasiBinding + } catch (err) { + if (forceWasi) { + wasiBindingError = err + } + } + if (!nativeBinding || forceWasi) { + try { + wasiBinding = require('litesvm-core-wasm32-wasi') + nativeBinding = wasiBinding + } catch (err) { + if (forceWasi) { + if (!wasiBindingError) { + wasiBindingError = err + } else { + wasiBindingError.cause = err + } + loadErrors.push(err) + } + } + } + if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) { + const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error') + error.cause = wasiBindingError + throw error + } +} + +if (!nativeBinding) { + if (loadErrors.length > 0) { + const error = new Error( + `Cannot find native binding. ` + + `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` + + 'Please try `npm i` again after removing both package-lock.json and node_modules directory.', + ) + // assign instead of the `new Error(message, { cause })` options form, + // which Node < 16.9 silently ignores + error.cause = loadErrors.reduce((err, cur) => { + cur.cause = err + return cur + }) + throw error + } + throw new Error(`Failed to load native binding`) +} + +module.exports = nativeBinding +module.exports.Account = nativeBinding.Account +module.exports.AddressAndAccount = nativeBinding.AddressAndAccount +module.exports.Clock = nativeBinding.Clock +module.exports.CompiledInstruction = nativeBinding.CompiledInstruction +module.exports.ComputeBudget = nativeBinding.ComputeBudget +module.exports.EpochRewards = nativeBinding.EpochRewards +module.exports.EpochSchedule = nativeBinding.EpochSchedule +module.exports.FailedTransactionMetadata = nativeBinding.FailedTransactionMetadata +module.exports.FeatureSet = nativeBinding.FeatureSet +module.exports.InnerInstruction = nativeBinding.InnerInstruction +module.exports.InstructionErrorBorshIo = nativeBinding.InstructionErrorBorshIo +module.exports.InstructionErrorBorshIO = nativeBinding.InstructionErrorBorshIO +module.exports.InstructionErrorCustom = nativeBinding.InstructionErrorCustom +module.exports.LiteSvm = nativeBinding.LiteSvm +module.exports.Rent = nativeBinding.Rent +module.exports.SimulatedTransactionInfo = nativeBinding.SimulatedTransactionInfo +module.exports.SlotHash = nativeBinding.SlotHash +module.exports.SlotHistory = nativeBinding.SlotHistory +module.exports.StakeHistory = nativeBinding.StakeHistory +module.exports.StakeHistoryEntry = nativeBinding.StakeHistoryEntry +module.exports.TransactionErrorDuplicateInstruction = nativeBinding.TransactionErrorDuplicateInstruction +module.exports.TransactionErrorInstructionError = nativeBinding.TransactionErrorInstructionError +module.exports.TransactionErrorInsufficientFundsForRent = nativeBinding.TransactionErrorInsufficientFundsForRent +module.exports.TransactionErrorProgramExecutionTemporarilyRestricted = nativeBinding.TransactionErrorProgramExecutionTemporarilyRestricted +module.exports.TransactionMetadata = nativeBinding.TransactionMetadata +module.exports.TransactionReturnData = nativeBinding.TransactionReturnData +module.exports.InstructionErrorFieldless = nativeBinding.InstructionErrorFieldless +module.exports.SlotHistoryCheck = nativeBinding.SlotHistoryCheck +module.exports.TransactionErrorFieldless = nativeBinding.TransactionErrorFieldless diff --git a/crates/node-litesvm-core/package.json b/crates/node-litesvm-core/package.json new file mode 100644 index 000000000..22452038c --- /dev/null +++ b/crates/node-litesvm-core/package.json @@ -0,0 +1,43 @@ +{ + "name": "litesvm-core", + "version": "1.3.0", + "description": "Raw Node.js NAPI bindings for LiteSVM", + "main": "litesvm/index.js", + "types": "litesvm/index.d.ts", + "files": [ + "litesvm/" + ], + "napi": { + "binaryName": "litesvm", + "targets": [ + "x86_64-apple-darwin", + "x86_64-unknown-linux-gnu", + "x86_64-unknown-linux-musl", + "aarch64-apple-darwin", + "aarch64-unknown-linux-gnu", + "aarch64-unknown-linux-musl" + ] + }, + "license": "MIT", + "repository": "https://github.com/LiteSVM/litesvm", + "devDependencies": { + "@napi-rs/cli": "^3.7.2" + }, + "engines": { + "node": ">= 20" + }, + "scripts": { + "artifacts": "napi artifacts", + "build": "napi build --platform --release --output-dir litesvm --dts index.d.ts --js index.js", + "build:debug": "napi build --platform --output-dir litesvm --dts index.d.ts --js index.js", + "prepublishOnly": "napi prepublish -t npm", + "fmt": "cargo fmt", + "lint": "cargo clippy", + "test": "node --test tests/*.test.js", + "universal": "napi universal", + "cargoBump": "cargo set-version --package litesvm-node --bump", + "bumpPatch": "yarn version patch && napi version && cargo tree", + "bumpMinor": "yarn version minor && napi version && cargo tree", + "bumpMajor": "yarn version major && napi version && cargo tree" + } +} diff --git a/crates/node-litesvm-core/src/account.rs b/crates/node-litesvm-core/src/account.rs new file mode 100644 index 000000000..0c0672eab --- /dev/null +++ b/crates/node-litesvm-core/src/account.rs @@ -0,0 +1,64 @@ +use { + crate::{to_string_js, util::bigint_to_u64}, + napi::bindgen_prelude::*, + solana_account::Account as AccountOriginal, + solana_address::Address, +}; + +#[derive(Debug, Clone)] +#[napi] +pub struct Account(pub(crate) AccountOriginal); + +impl AsRef for Account { + fn as_ref(&self) -> &AccountOriginal { + &self.0 + } +} + +#[napi] +impl Account { + #[napi(constructor)] + pub fn new( + lamports: BigInt, + data: Uint8Array, + owner: Uint8Array, + executable: bool, + rent_epoch: BigInt, + ) -> Result { + Ok(Self(AccountOriginal { + lamports: bigint_to_u64(&lamports)?, + data: data.to_vec(), + owner: Address::try_from(owner.as_ref()) + .map_err(|e| Error::from_reason(e.to_string()))?, + executable, + rent_epoch: bigint_to_u64(&rent_epoch)?, + })) + } + + #[napi] + pub fn lamports(&self) -> u64 { + self.0.lamports + } + + #[napi] + pub fn data(&self) -> Uint8Array { + Uint8Array::new(self.0.data.clone()) + } + + #[napi] + pub fn owner(&self) -> Uint8Array { + Uint8Array::new(self.0.owner.to_bytes().to_vec()) + } + + #[napi] + pub fn executable(&self) -> bool { + self.0.executable + } + + #[napi] + pub fn rent_epoch(&self) -> u64 { + self.0.rent_epoch + } +} + +to_string_js!(Account); diff --git a/crates/node-litesvm-core/src/compute_budget.rs b/crates/node-litesvm-core/src/compute_budget.rs new file mode 100644 index 000000000..daaa4a5ed --- /dev/null +++ b/crates/node-litesvm-core/src/compute_budget.rs @@ -0,0 +1,371 @@ +use { + crate::util::{bigint_to_u64, bigint_to_usize}, + napi::bindgen_prelude::*, + solana_compute_budget::compute_budget::ComputeBudget as ComputeBudgetOriginal, +}; + +#[napi] +pub struct ComputeBudget(pub(crate) ComputeBudgetOriginal); + +#[napi] +impl ComputeBudget { + #[napi(constructor)] + pub fn new() -> Self { + Self(ComputeBudgetOriginal::new_with_defaults(false)) + } + + #[napi(getter)] + pub fn compute_unit_limit(&self) -> u64 { + self.0.compute_unit_limit + } + + #[napi(setter)] + pub fn set_compute_unit_limit(&mut self, limit: BigInt) -> Result<()> { + Ok(self.0.compute_unit_limit = bigint_to_u64(&limit)?) + } + + #[napi(setter)] + pub fn set_log_64_units(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.log_64_units = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn log_64_units(&self) -> u64 { + self.0.log_64_units + } + #[napi(setter)] + pub fn set_create_program_address_units(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.create_program_address_units = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn create_program_address_units(&self) -> u64 { + self.0.create_program_address_units + } + #[napi(setter)] + pub fn set_invoke_units(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.invoke_units = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn invoke_units(&self) -> u64 { + self.0.invoke_units + } + #[napi(setter)] + pub fn set_max_instruction_stack_depth(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.max_instruction_stack_depth = bigint_to_usize(&val)?) + } + #[napi(getter)] + pub fn max_instruction_stack_depth(&self) -> usize { + self.0.max_instruction_stack_depth + } + #[napi(setter)] + pub fn set_max_instruction_trace_length(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.max_instruction_trace_length = bigint_to_usize(&val)?) + } + #[napi(getter)] + pub fn max_instruction_trace_length(&self) -> usize { + self.0.max_instruction_trace_length + } + #[napi(setter)] + pub fn set_sha256_base_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.sha256_base_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn sha256_base_cost(&self) -> u64 { + self.0.sha256_base_cost + } + #[napi(setter)] + pub fn set_sha256_byte_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.sha256_byte_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn sha256_byte_cost(&self) -> u64 { + self.0.sha256_byte_cost + } + #[napi(setter)] + pub fn set_sha256_max_slices(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.sha256_max_slices = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn sha256_max_slices(&self) -> u64 { + self.0.sha256_max_slices + } + #[napi(setter)] + pub fn set_max_call_depth(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.max_call_depth = bigint_to_usize(&val)?) + } + #[napi(getter)] + pub fn max_call_depth(&self) -> usize { + self.0.max_call_depth + } + #[napi(setter)] + pub fn set_stack_frame_size(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.stack_frame_size = bigint_to_usize(&val)?) + } + #[napi(getter)] + pub fn stack_frame_size(&self) -> usize { + self.0.stack_frame_size + } + #[napi(setter)] + pub fn set_log_pubkey_units(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.log_pubkey_units = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn log_pubkey_units(&self) -> u64 { + self.0.log_pubkey_units + } + #[napi(setter)] + pub fn set_cpi_bytes_per_unit(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.cpi_bytes_per_unit = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn cpi_bytes_per_unit(&self) -> u64 { + self.0.cpi_bytes_per_unit + } + #[napi(setter)] + pub fn set_sysvar_base_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.sysvar_base_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn sysvar_base_cost(&self) -> u64 { + self.0.sysvar_base_cost + } + #[napi(setter)] + pub fn set_secp256k1_recover_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.secp256k1_recover_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn secp256k1_recover_cost(&self) -> u64 { + self.0.secp256k1_recover_cost + } + #[napi(setter)] + pub fn set_syscall_base_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.syscall_base_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn syscall_base_cost(&self) -> u64 { + self.0.syscall_base_cost + } + #[napi(setter)] + pub fn set_curve25519_edwards_validate_point_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_edwards_validate_point_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_edwards_validate_point_cost(&self) -> u64 { + self.0.curve25519_edwards_validate_point_cost + } + #[napi(setter)] + pub fn set_curve25519_edwards_add_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_edwards_add_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_edwards_add_cost(&self) -> u64 { + self.0.curve25519_edwards_add_cost + } + #[napi(setter)] + pub fn set_curve25519_edwards_subtract_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_edwards_subtract_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_edwards_subtract_cost(&self) -> u64 { + self.0.curve25519_edwards_subtract_cost + } + #[napi(setter)] + pub fn set_curve25519_edwards_multiply_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_edwards_multiply_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_edwards_multiply_cost(&self) -> u64 { + self.0.curve25519_edwards_multiply_cost + } + #[napi(setter)] + pub fn set_curve25519_edwards_msm_base_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_edwards_msm_base_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_edwards_msm_base_cost(&self) -> u64 { + self.0.curve25519_edwards_msm_base_cost + } + #[napi(setter)] + pub fn set_curve25519_edwards_msm_incremental_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_edwards_msm_incremental_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_edwards_msm_incremental_cost(&self) -> u64 { + self.0.curve25519_edwards_msm_incremental_cost + } + #[napi(setter)] + pub fn set_curve25519_ristretto_validate_point_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_ristretto_validate_point_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_ristretto_validate_point_cost(&self) -> u64 { + self.0.curve25519_ristretto_validate_point_cost + } + #[napi(setter)] + pub fn set_curve25519_ristretto_add_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_ristretto_add_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_ristretto_add_cost(&self) -> u64 { + self.0.curve25519_ristretto_add_cost + } + #[napi(setter)] + pub fn set_curve25519_ristretto_subtract_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_ristretto_subtract_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_ristretto_subtract_cost(&self) -> u64 { + self.0.curve25519_ristretto_subtract_cost + } + #[napi(setter)] + pub fn set_curve25519_ristretto_multiply_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_ristretto_multiply_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_ristretto_multiply_cost(&self) -> u64 { + self.0.curve25519_ristretto_multiply_cost + } + #[napi(setter)] + pub fn set_curve25519_ristretto_msm_base_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_ristretto_msm_base_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_ristretto_msm_base_cost(&self) -> u64 { + self.0.curve25519_ristretto_msm_base_cost + } + #[napi(setter)] + pub fn set_curve25519_ristretto_msm_incremental_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.curve25519_ristretto_msm_incremental_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn curve25519_ristretto_msm_incremental_cost(&self) -> u64 { + self.0.curve25519_ristretto_msm_incremental_cost + } + #[napi(setter)] + pub fn set_heap_size(&mut self, val: u32) { + self.0.heap_size = val; + } + #[napi(getter)] + pub fn heap_size(&self) -> u32 { + self.0.heap_size + } + #[napi(setter)] + pub fn set_heap_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.heap_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn heap_cost(&self) -> u64 { + self.0.heap_cost + } + #[napi(setter)] + pub fn set_mem_op_base_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.mem_op_base_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn mem_op_base_cost(&self) -> u64 { + self.0.mem_op_base_cost + } + #[napi(setter)] + pub fn set_alt_bn128_addition_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.alt_bn128_g1_addition_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn alt_bn128_addition_cost(&self) -> u64 { + self.0.alt_bn128_g1_addition_cost + } + #[napi(setter)] + pub fn set_alt_bn128_multiplication_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.alt_bn128_g1_multiplication_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn alt_bn128_multiplication_cost(&self) -> u64 { + self.0.alt_bn128_g1_multiplication_cost + } + #[napi(setter)] + pub fn set_alt_bn128_pairing_one_pair_cost_first(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.alt_bn128_pairing_one_pair_cost_first = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn alt_bn128_pairing_one_pair_cost_first(&self) -> u64 { + self.0.alt_bn128_pairing_one_pair_cost_first + } + #[napi(setter)] + pub fn set_alt_bn128_pairing_one_pair_cost_other(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.alt_bn128_pairing_one_pair_cost_other = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn alt_bn128_pairing_one_pair_cost_other(&self) -> u64 { + self.0.alt_bn128_pairing_one_pair_cost_other + } + #[napi(setter)] + pub fn set_big_modular_exponentiation_base_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.big_modular_exponentiation_base_cost = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn big_modular_exponentiation_base_cost(&self) -> u64 { + self.0.big_modular_exponentiation_base_cost + } + #[napi(setter)] + pub fn set_big_modular_exponentiation_cost_divisor(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.big_modular_exponentiation_cost_divisor = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn big_modular_exponentiation_cost_divisor(&self) -> u64 { + self.0.big_modular_exponentiation_cost_divisor + } + #[napi(setter)] + pub fn set_poseidon_cost_coefficient_a(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.poseidon_cost_coefficient_a = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn poseidon_cost_coefficient_a(&self) -> u64 { + self.0.poseidon_cost_coefficient_a + } + #[napi(setter)] + pub fn set_poseidon_cost_coefficient_c(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.poseidon_cost_coefficient_c = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn poseidon_cost_coefficient_c(&self) -> u64 { + self.0.poseidon_cost_coefficient_c + } + #[napi(setter)] + pub fn set_get_remaining_compute_units_cost(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.get_remaining_compute_units_cost = bigint_to_u64(&val)?) + } + #[napi(getter, js_name = "getRemainingComputeUnitsCost")] + pub fn remaining_compute_units_cost(&self) -> u64 { + self.0.get_remaining_compute_units_cost + } + #[napi(setter)] + pub fn set_alt_bn128_g1_compress(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.alt_bn128_g1_compress = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn alt_bn128_g1_compress(&self) -> u64 { + self.0.alt_bn128_g1_compress + } + #[napi(setter)] + pub fn set_alt_bn128_g1_decompress(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.alt_bn128_g1_decompress = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn alt_bn128_g1_decompress(&self) -> u64 { + self.0.alt_bn128_g1_decompress + } + #[napi(setter)] + pub fn set_alt_bn128_g2_compress(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.alt_bn128_g2_compress = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn alt_bn128_g2_compress(&self) -> u64 { + self.0.alt_bn128_g2_compress + } + #[napi(setter)] + pub fn set_alt_bn128_g2_decompress(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.alt_bn128_g2_decompress = bigint_to_u64(&val)?) + } + #[napi(getter)] + pub fn alt_bn128_g2_decompress(&self) -> u64 { + self.0.alt_bn128_g2_decompress + } +} diff --git a/crates/node-litesvm-core/src/feature_set.rs b/crates/node-litesvm-core/src/feature_set.rs new file mode 100644 index 000000000..25a2ba742 --- /dev/null +++ b/crates/node-litesvm-core/src/feature_set.rs @@ -0,0 +1,76 @@ +use { + crate::{ + to_string_js, + util::{bigint_to_u64, convert_pubkey}, + }, + agave_feature_set::FeatureSet as FeatureSetOriginal, + napi::bindgen_prelude::*, +}; + +#[derive(Debug, Clone)] +#[napi] +pub struct FeatureSet(pub(crate) FeatureSetOriginal); + +#[napi] +impl FeatureSet { + #[napi(constructor)] + pub fn new() -> Self { + Self(FeatureSetOriginal::default()) + } + + #[napi(factory)] + pub fn all_enabled() -> Self { + Self(FeatureSetOriginal::all_enabled()) + } + + #[napi] + pub fn is_active(&self, feature_id: &[u8]) -> bool { + self.0.is_active(&convert_pubkey(feature_id)) + } + + #[napi] + pub fn activated_slot(&self, feature_id: &[u8]) -> Option { + self.0.activated_slot(&convert_pubkey(feature_id)) + } + + #[napi] + pub fn activate(&mut self, feature_id: &[u8], slot: BigInt) { + let slot_u64 = bigint_to_u64(&slot).unwrap_or(0); + self.0.activate(&convert_pubkey(feature_id), slot_u64); + } + + #[napi] + pub fn deactivate(&mut self, feature_id: &[u8]) { + self.0.deactivate(&convert_pubkey(feature_id)); + } + + #[napi] + pub fn get_active_features(&self) -> Vec { + self.0 + .active() + .keys() + .map(|pubkey| Buffer::from(pubkey.to_bytes().to_vec())) + .collect() + } + + #[napi] + pub fn get_inactive_features(&self) -> Vec { + self.0 + .inactive() + .iter() + .map(|pubkey| Buffer::from(pubkey.to_bytes().to_vec())) + .collect() + } + + #[napi] + pub fn get_active_features_count(&self) -> u32 { + self.0.active().len() as u32 + } + + #[napi] + pub fn get_inactive_features_count(&self) -> u32 { + self.0.inactive().len() as u32 + } +} + +to_string_js!(FeatureSet); diff --git a/crates/node-litesvm-core/src/lib.rs b/crates/node-litesvm-core/src/lib.rs new file mode 100644 index 000000000..8e0e9c425 --- /dev/null +++ b/crates/node-litesvm-core/src/lib.rs @@ -0,0 +1,454 @@ +#![deny(clippy::all)] +#![allow(clippy::new_without_default, clippy::unit_arg)] +use { + crate::{ + account::Account, + compute_budget::ComputeBudget, + feature_set::FeatureSet, + sysvar::{ + clock::Clock, epoch_rewards::EpochRewards, epoch_schedule::EpochSchedule, rent::Rent, + slot_hashes::SlotHash, slot_history::SlotHistory, stake_history::StakeHistory, + }, + transaction_metadata::{ + AddressAndAccount, FailedTransactionMetadata, SimulatedTransactionInfo, + TransactionMetadata, + }, + util::{convert_pubkey, try_parse_hash}, + }, + litesvm::{ + error::LiteSVMError, + types::{ + FailedTransactionMetadata as FailedTransactionMetadataOriginal, + SimulatedTransactionInfo as SimulatedTransactionInfoOriginal, + TransactionResult as TransactionResultOriginal, + }, + LiteSVM as LiteSVMOriginal, + }, + napi::bindgen_prelude::*, + solana_clock::Clock as ClockOriginal, + solana_epoch_rewards::EpochRewards as EpochRewardsOriginal, + solana_epoch_schedule::EpochSchedule as EpochScheduleOriginal, + solana_last_restart_slot::LastRestartSlot, + solana_rent::Rent as RentOriginal, + solana_signature::Signature, + solana_slot_hashes::SlotHashes, + solana_slot_history::SlotHistory as SlotHistoryOriginal, + solana_stake_history::StakeHistory as StakeHistoryOriginal, + solana_transaction::{versioned::VersionedTransaction, Transaction}, + util::{bigint_to_u64, bigint_to_usize}, + wincode::deserialize, +}; +mod account; +mod compute_budget; +mod feature_set; +mod native_mint; +mod sysvar; +mod transaction_error; +mod transaction_metadata; +mod util; + +#[macro_use] +extern crate napi_derive; + +fn to_js_error(e: LiteSVMError, msg: &str) -> Error { + Error::new(Status::GenericFailure, format!("{msg}: {e}")) +} + +#[macro_export] +macro_rules! to_string_js { + ($name:ident) => { + #[napi] + impl $name { + #[napi(js_name = "toString")] + pub fn js_to_string(&self) -> String { + format!("{self:?}") + } + } + }; +} + +pub type TransactionResult = Either; +pub type SimulateResult = Either; + +fn convert_transaction_result(inner: TransactionResultOriginal) -> TransactionResult { + match inner { + Ok(x) => TransactionResult::A(TransactionMetadata(x)), + Err(e) => TransactionResult::B(FailedTransactionMetadata(e)), + } +} + +fn convert_sim_result( + inner: std::result::Result, +) -> SimulateResult { + match inner { + Ok(x) => SimulateResult::A(SimulatedTransactionInfo(x)), + Err(e) => SimulateResult::B(FailedTransactionMetadata(e)), + } +} + +#[napi] +pub struct LiteSvm(LiteSVMOriginal); + +#[napi] +impl LiteSvm { + /// Creates the basic test environment. + #[napi(constructor)] + pub fn new() -> Self { + Self(LiteSVMOriginal::new()) + } + + #[napi(factory, js_name = "default")] + pub fn new_default() -> Self { + Self(LiteSVMOriginal::default()) + } + + #[napi] + pub fn set_compute_budget(&mut self, budget: &ComputeBudget) { + self.0.set_compute_budget(budget.0); + } + + #[napi] + /// Enables or disables sigverify + pub fn set_sigverify(&mut self, sigverify: bool) { + self.0.set_sigverify(sigverify); + } + + #[napi] + /// Enables or disables the blockhash check + pub fn set_blockhash_check(&mut self, check: bool) { + self.0.set_blockhash_check(check); + } + + #[napi] + /// Includes the default sysvars + pub fn set_sysvars(&mut self) { + self.0.set_sysvars() + } + + #[napi] + /// Changes the default builtins + pub fn set_feature_set(&mut self, feature_set: &FeatureSet) { + self.0.set_feature_set(feature_set.0.clone()); + } + + #[napi] + /// Changes the default builtins + pub fn set_builtins(&mut self) { + self.0.set_builtins(); + } + + #[napi] + /// Changes the initial lamports in LiteSVM's airdrop account + pub fn set_lamports(&mut self, lamports: BigInt) -> Result<()> { + Ok(self.0.set_lamports(bigint_to_u64(&lamports)?)) + } + + #[napi] + /// Includes the standard SPL programs + pub fn set_default_programs(&mut self) { + self.0.set_default_programs(); + } + + #[napi] + /// Changes the capacity of the transaction history. + /// Set this to 0 to disable transaction history and allow duplicate transactions. + pub fn set_transaction_history(&mut self, capacity: BigInt) -> Result<()> { + Ok(self.0.set_transaction_history(bigint_to_usize(&capacity)?)) + } + + #[napi] + pub fn set_log_bytes_limit(&mut self, limit: Option) -> Result<()> { + Ok(match limit { + None => self.0.set_log_bytes_limit(None), + Some(x) => { + let converted = bigint_to_usize(&x)?; + self.0.set_log_bytes_limit(Some(converted)) + } + }) + } + + #[napi] + pub fn set_precompiles(&mut self) { + self.0.set_precompiles(); + } + + #[napi] + /// Returns minimum balance required to make an account with specified data length rent exempt. + pub fn minimum_balance_for_rent_exemption(&self, data_len: BigInt) -> Result { + Ok(self + .0 + .minimum_balance_for_rent_exemption(bigint_to_usize(&data_len)?)) + } + + #[napi] + /// Returns all information associated with the account of the provided pubkey. + pub fn get_account(&self, pubkey: &[u8]) -> Option { + self.0.get_account(&convert_pubkey(pubkey)).map(Account) + } + + #[napi] + /// Returns all accounts owned by the given program, together with their addresses. + pub fn get_program_accounts(&self, program_id: &[u8]) -> Vec { + self.0 + .get_program_accounts(&convert_pubkey(program_id)) + .into_iter() + .map(|(address, account)| AddressAndAccount { + address: Uint8Array::with_data_copied(address.to_bytes()), + account: Account(account), + }) + .collect() + } + + #[napi] + /// Sets all information associated with the account of the provided pubkey. + pub fn set_account(&mut self, pubkey: &[u8], data: &Account) -> Result<()> { + self.0 + .set_account(convert_pubkey(pubkey), data.0.clone()) + .map_err(|e| to_js_error(e, "Failed to set account")) + } + + #[napi] + /// Gets the balance of the provided account pubkey. + pub fn get_balance(&self, pubkey: &[u8]) -> Option { + self.0.get_balance(&convert_pubkey(pubkey)) + } + + #[napi] + /// Gets the latest blockhash. + pub fn latest_blockhash(&self) -> String { + self.0.latest_blockhash().to_string() + } + + #[napi(ts_return_type = "TransactionMetadata | FailedTransactionMetadata | null")] + /// Gets a transaction from the transaction history. + pub fn get_transaction(&self, signature: &[u8]) -> Option { + self.0 + .get_transaction(&Signature::try_from(signature).unwrap()) + .map(|x| convert_transaction_result(x.clone())) + } + + #[napi(ts_return_type = "TransactionMetadata | FailedTransactionMetadata | null")] + /// Airdrops the account with the lamports specified. + pub fn airdrop(&mut self, pubkey: &[u8], lamports: BigInt) -> Result { + Ok(convert_transaction_result(self.0.airdrop( + &convert_pubkey(pubkey), + bigint_to_u64(&lamports)?, + ))) + } + + #[napi] + /// Adds am SBF program to the test environment from the file specified. + pub fn add_program_from_file(&mut self, program_id: &[u8], path: String) -> Result<()> { + self.0 + .add_program_from_file(convert_pubkey(program_id), path) + .map_err(|e| { + Error::new( + Status::GenericFailure, + format!("Failed to add program: {e}"), + ) + }) + } + + #[napi] + /// Adds am SBF program to the test environment. + pub fn add_program(&mut self, program_id: &[u8], program_bytes: &[u8]) -> Result<()> { + self.0 + .add_program(convert_pubkey(program_id), program_bytes) + .map_err(|e| { + Error::new( + Status::GenericFailure, + format!("Failed to add program: {e}"), + ) + }) + } + + #[napi] + /// Adds an SBF program with a specific loader. + pub fn add_program_with_loader( + &mut self, + program_id: &[u8], + program_bytes: &[u8], + loader_id: &[u8], + ) -> Result<()> { + self.0 + .add_program_with_loader( + convert_pubkey(program_id), + program_bytes, + convert_pubkey(loader_id), + ) + .map_err(|e| { + Error::new( + Status::GenericFailure, + format!("Failed to add program: {e}"), + ) + }) + } + + #[napi(ts_return_type = "TransactionMetadata | FailedTransactionMetadata")] + pub fn send_legacy_transaction(&mut self, tx_bytes: &[u8]) -> TransactionResult { + let tx: Transaction = deserialize(tx_bytes).unwrap(); + let res = self.0.send_transaction(tx); + convert_transaction_result(res) + } + + #[napi(ts_return_type = "TransactionMetadata | FailedTransactionMetadata")] + pub fn send_versioned_transaction(&mut self, tx_bytes: &[u8]) -> TransactionResult { + let tx: VersionedTransaction = deserialize(tx_bytes).unwrap(); + let res = self.0.send_transaction(tx); + convert_transaction_result(res) + } + + #[napi(ts_return_type = "SimulatedTransactionInfo | FailedTransactionMetadata")] + pub fn simulate_legacy_transaction(&mut self, tx_bytes: &[u8]) -> SimulateResult { + let tx: Transaction = deserialize(tx_bytes).unwrap(); + let res = self.0.simulate_transaction(tx); + convert_sim_result(res) + } + + #[napi(ts_return_type = "SimulatedTransactionInfo | FailedTransactionMetadata")] + pub fn simulate_versioned_transaction(&mut self, tx_bytes: &[u8]) -> SimulateResult { + let tx: VersionedTransaction = deserialize(tx_bytes).unwrap(); + let res = self.0.simulate_transaction(tx); + convert_sim_result(res) + } + + #[napi] + /// Expires the current blockhash + pub fn expire_blockhash(&mut self) { + self.0.expire_blockhash() + } + + #[napi] + /// Warps the clock to the specified slot + pub fn warp_to_slot(&mut self, slot: BigInt) -> Result<()> { + Ok(self.0.warp_to_slot(bigint_to_u64(&slot)?)) + } + + #[napi] + pub fn get_compute_budget(&self) -> Option { + self.0.get_compute_budget().map(ComputeBudget) + } + + #[napi] + pub fn get_sigverify(&self) -> bool { + self.0.get_sigverify() + } + + #[napi] + pub fn get_clock(&self) -> Clock { + Clock(self.0.get_sysvar::()) + } + + #[napi] + pub fn set_clock(&mut self, clock: &Clock) { + self.0.set_sysvar(&clock.0) + } + + #[napi] + pub fn get_rent(&self) -> Rent { + Rent(self.0.get_sysvar::()) + } + + #[napi] + pub fn set_rent(&mut self, rent: &Rent) { + self.0.set_sysvar(&rent.0) + } + + #[napi] + pub fn get_epoch_rewards(&self) -> EpochRewards { + EpochRewards(self.0.get_sysvar::()) + } + + #[napi] + pub fn set_epoch_rewards(&mut self, rewards: &EpochRewards) { + self.0.set_sysvar(&rewards.0) + } + + #[napi] + pub fn get_epoch_schedule(&self) -> EpochSchedule { + EpochSchedule(self.0.get_sysvar::()) + } + + #[napi] + pub fn set_epoch_schedule(&mut self, schedule: &EpochSchedule) { + self.0.set_sysvar(&schedule.0) + } + + #[napi] + pub fn get_last_restart_slot(&self) -> u64 { + self.0.get_sysvar::().last_restart_slot + } + + #[napi] + pub fn set_last_restart_slot(&mut self, slot: BigInt) -> Result<()> { + Ok(self.0.set_sysvar::(&LastRestartSlot { + last_restart_slot: bigint_to_u64(&slot)?, + })) + } + + #[napi] + pub fn get_slot_hashes(&self) -> Vec { + let fetched = self.0.get_sysvar::(); + fetched + .slot_hashes() + .iter() + .map(|x| SlotHash { + slot: BigInt::from(x.0), + hash: x.1.to_string(), + }) + .collect() + } + + #[napi] + pub fn set_slot_hashes(&mut self, hashes: Vec<&SlotHash>) -> Result<()> { + let mut intermediate: Vec<(u64, solana_hash::Hash)> = Vec::with_capacity(hashes.len()); + for h in hashes { + let converted_hash = try_parse_hash(&h.hash)?; + intermediate.push((bigint_to_u64(&h.slot)?, converted_hash)); + } + let converted = SlotHashes::from_iter(intermediate); + self.0.set_sysvar::(&converted); + Ok(()) + } + + #[napi] + pub fn get_slot_history(&self) -> SlotHistory { + SlotHistory(self.0.get_sysvar::()) + } + + #[napi] + pub fn set_slot_history(&mut self, history: &SlotHistory) { + self.0.set_sysvar::(&history.0) + } + + #[napi] + pub fn get_stake_history(&self) -> StakeHistory { + StakeHistory(self.0.get_sysvar::()) + } + + #[napi] + pub fn set_stake_history(&mut self, history: &StakeHistory) { + self.0.set_sysvar::(&history.0) + } + + #[napi] + pub fn with_native_mints(&mut self) { + if self + .0 + .accounts_db() + .inner + .contains_key(&native_mint::inline_spl::SPL_TOKEN_PROGRAM_ID) + { + native_mint::create_native_mint(&mut self.0); + } + + if self + .0 + .accounts_db() + .inner + .contains_key(&native_mint::inline_spl::SPL_TOKEN_2022_PROGRAM_ID) + { + native_mint::create_native_mint_2022(&mut self.0); + } + } +} diff --git a/crates/node-litesvm-core/src/native_mint.rs b/crates/node-litesvm-core/src/native_mint.rs new file mode 100644 index 000000000..d4ef757f8 --- /dev/null +++ b/crates/node-litesvm-core/src/native_mint.rs @@ -0,0 +1,43 @@ +use {litesvm::LiteSVM, solana_account::Account, solana_address::Address, solana_rent::Rent}; + +// Avoid importing external dependencies +pub(crate) mod inline_spl { + use super::*; + + pub const SPL_TOKEN_PROGRAM_ID: Address = + Address::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); + pub const SPL_TOKEN_2022_PROGRAM_ID: Address = + Address::from_str_const("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); +} + +fn create_native_mint_with_program_id(svm: &mut LiteSVM, address: Address, token_program: Address) { + let account = Account { + lamports: svm.get_sysvar::().minimum_balance(82), + data: vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + owner: token_program, + executable: false, + rent_epoch: 0, + }; + + svm.set_account(address, account).unwrap(); +} + +pub fn create_native_mint(svm: &mut LiteSVM) { + create_native_mint_with_program_id( + svm, + Address::from_str_const("So11111111111111111111111111111111111111112"), + inline_spl::SPL_TOKEN_PROGRAM_ID, + ); +} + +pub fn create_native_mint_2022(svm: &mut LiteSVM) { + create_native_mint_with_program_id( + svm, + Address::from_str_const("9pan9bMn5HatX4EJdBwg9VgCa7Uz5HL8N1m5D3NdXejP"), + inline_spl::SPL_TOKEN_2022_PROGRAM_ID, + ); +} diff --git a/crates/node-litesvm-core/src/sysvar/clock.rs b/crates/node-litesvm-core/src/sysvar/clock.rs new file mode 100644 index 000000000..59e992e6e --- /dev/null +++ b/crates/node-litesvm-core/src/sysvar/clock.rs @@ -0,0 +1,97 @@ +use { + crate::{ + to_string_js, + util::{bigint_to_i64, bigint_to_u64}, + }, + napi::bindgen_prelude::*, + solana_clock::Clock as ClockOriginal, +}; + +/// A representation of network time. +/// +/// All members of `Clock` start from 0 upon network boot. +#[derive(Debug)] +#[napi] +pub struct Clock(pub(crate) ClockOriginal); + +#[napi] +impl Clock { + /// @param slot - The current Slot. + /// @param epochStartTimestamp - The timestamp of the first `Slot` in this `Epoch`. + /// @param epoch - The current epoch. + /// @param leaderScheduleEpoch - The future Epoch for which the leader schedule has most recently been calculated. + /// @param unixTimestamp - The approximate real world time of the current slot. + #[napi(constructor)] + pub fn new( + slot: BigInt, + epoch_start_timestamp: BigInt, + epoch: BigInt, + leader_schedule_epoch: BigInt, + unix_timestamp: BigInt, + ) -> Result { + Ok(Self(ClockOriginal { + slot: bigint_to_u64(&slot)?, + epoch_start_timestamp: bigint_to_i64(&epoch_start_timestamp)?, + epoch: bigint_to_u64(&epoch)?, + leader_schedule_epoch: bigint_to_u64(&leader_schedule_epoch)?, + unix_timestamp: bigint_to_i64(&unix_timestamp)?, + })) + } + + /// The current Slot. + #[napi(getter)] + pub fn slot(&self) -> u64 { + self.0.slot + } + + #[napi(setter)] + pub fn set_slot(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.slot = bigint_to_u64(&val)?) + } + + /// The current epoch. + #[napi(getter)] + pub fn epoch(&self) -> u64 { + self.0.epoch + } + + #[napi(setter)] + pub fn set_epoch(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.epoch = bigint_to_u64(&val)?) + } + + /// The timestamp of the first `Slot` in this `Epoch`. + #[napi(getter)] + pub fn epoch_start_timestamp(&self) -> BigInt { + self.0.epoch_start_timestamp.into() + } + + #[napi(setter)] + pub fn set_epoch_start_timestamp(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.epoch_start_timestamp = bigint_to_i64(&val)?) + } + + /// The future Epoch for which the leader schedule has most recently been calculated. + #[napi(getter)] + pub fn leader_schedule_epoch(&self) -> u64 { + self.0.leader_schedule_epoch + } + + #[napi(setter)] + pub fn set_leader_schedule_epoch(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.leader_schedule_epoch = bigint_to_u64(&val)?) + } + + /// The approximate real world time of the current slot. + #[napi(getter)] + pub fn unix_timestamp(&self) -> BigInt { + self.0.unix_timestamp.into() + } + + #[napi(setter)] + pub fn set_unix_timestamp(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.unix_timestamp = bigint_to_i64(&val)?) + } +} + +to_string_js!(Clock); diff --git a/crates/node-litesvm-core/src/sysvar/epoch_rewards.rs b/crates/node-litesvm-core/src/sysvar/epoch_rewards.rs new file mode 100644 index 000000000..24ac97c14 --- /dev/null +++ b/crates/node-litesvm-core/src/sysvar/epoch_rewards.rs @@ -0,0 +1,129 @@ +use { + crate::util::{bigint_to_u128, bigint_to_u64, try_parse_hash}, + napi::bindgen_prelude::*, + solana_epoch_rewards::EpochRewards as EpochRewardsOriginal, +}; + +/// A type to hold data for the EpochRewards sysvar. +#[derive(Debug)] +#[napi] +pub struct EpochRewards(pub(crate) EpochRewardsOriginal); + +#[napi] +impl EpochRewards { + /// @param distribution_starting_block_height - The starting block height of the rewards distribution in the current epoch + /// @param num_partitions - Number of partitions in the rewards distribution in the current epoch + /// @param parent_blockhash - The blockhash of the parent block of the first block in the epoch + /// @param total_points - The total rewards points calculated for the current epoch + /// @param total_rewards - The total rewards calculated for the current epoch + /// @param distributed_rewards - The rewards currently distributed for the current epoch, in lamports + /// @param active - Whether the rewards period (including calculation and distribution) is active + #[napi(constructor)] + pub fn new( + distribution_starting_block_height: BigInt, + num_partitions: BigInt, + parent_blockhash: String, + total_points: BigInt, + total_rewards: BigInt, + distributed_rewards: BigInt, + active: bool, + ) -> Result { + let hash_parsed = try_parse_hash(&parent_blockhash)?; + Ok(Self(EpochRewardsOriginal { + distribution_starting_block_height: bigint_to_u64(&distribution_starting_block_height)?, + num_partitions: bigint_to_u64(&num_partitions)?, + parent_blockhash: hash_parsed, + total_points: bigint_to_u128(&total_points)?, + total_rewards: bigint_to_u64(&total_rewards)?, + distributed_rewards: bigint_to_u64(&distributed_rewards)?, + active, + })) + } + + /// The starting block height of the rewards distribution in the current + /// epoch + #[napi(getter)] + pub fn distribution_starting_block_height(&self) -> u64 { + self.0.distribution_starting_block_height + } + + #[napi(setter)] + pub fn set_distribution_starting_block_height(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.distribution_starting_block_height = bigint_to_u64(&val)?) + } + + /// Number of partitions in the rewards distribution in the current epoch, + /// used to generate an EpochRewardsHasher + #[napi(getter)] + pub fn num_partitions(&self) -> u64 { + self.0.num_partitions + } + + #[napi(setter)] + pub fn set_num_partitions(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.num_partitions = bigint_to_u64(&val)?) + } + + /// The blockhash of the parent block of the first block in the epoch, used + /// to seed an EpochRewardsHasher + #[napi(getter)] + pub fn parent_blockhash(&self) -> String { + self.0.parent_blockhash.to_string() + } + + #[napi(setter)] + pub fn set_parent_blockhash(&mut self, val: String) -> Result<()> { + let hash_parsed = try_parse_hash(&val)?; + self.0.parent_blockhash = hash_parsed; + Ok(()) + } + + /// The total rewards points calculated for the current epoch, where points + /// equals the sum of (delegated stake * credits observed) for all + /// delegations + #[napi(getter)] + pub fn total_points(&self) -> u128 { + self.0.total_points + } + + #[napi(setter)] + pub fn set_total_points(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.total_points = bigint_to_u128(&val)?) + } + + /// The total rewards calculated for the current epoch. This may be greater + /// than the total `distributed_rewards` at the end of the rewards period, + /// due to rounding and inability to deliver rewards smaller than 1 lamport. + #[napi(getter)] + pub fn total_rewards(&self) -> u64 { + self.0.total_rewards + } + + #[napi(setter)] + pub fn set_total_rewards(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.total_rewards = bigint_to_u64(&val)?) + } + + /// The rewards currently distributed for the current epoch, in lamports + #[napi(getter)] + pub fn distributed_rewards(&self) -> u64 { + self.0.distributed_rewards + } + + #[napi(setter)] + pub fn set_distributed_rewards(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.distributed_rewards = bigint_to_u64(&val)?) + } + + /// Whether the rewards period (including calculation and distribution) is + /// active + #[napi(getter)] + pub fn active(&self) -> bool { + self.0.active + } + + #[napi(setter)] + pub fn set_active(&mut self, val: bool) { + self.0.active = val; + } +} diff --git a/crates/node-litesvm-core/src/sysvar/epoch_schedule.rs b/crates/node-litesvm-core/src/sysvar/epoch_schedule.rs new file mode 100644 index 000000000..60fe29eb2 --- /dev/null +++ b/crates/node-litesvm-core/src/sysvar/epoch_schedule.rs @@ -0,0 +1,94 @@ +use { + crate::util::bigint_to_u64, napi::bindgen_prelude::*, + solana_epoch_schedule::EpochSchedule as EpochScheduleOriginal, +}; + +/// Configuration for epochs and slots. +#[derive(Debug)] +#[napi] +pub struct EpochSchedule(pub(crate) EpochScheduleOriginal); + +#[napi] +impl EpochSchedule { + /// @param slots_per_epoch - The maximum number of slots in each epoch. + /// @param leader_schedule_slot_offset - A number of slots before beginning of an epoch to calculate a leader schedule for that epoch. + /// @param warmup - Whether epochs start short and grow. + /// @param first_normal_epoch - The first epoch after the warmup period. + /// @param first_normal_slot - The first slot after the warmup period. + #[napi(constructor)] + pub fn new( + slots_per_epoch: BigInt, + leader_schedule_slot_offset: BigInt, + warmup: bool, + first_normal_epoch: BigInt, + first_normal_slot: BigInt, + ) -> Result { + Ok(Self(EpochScheduleOriginal { + slots_per_epoch: bigint_to_u64(&slots_per_epoch)?, + leader_schedule_slot_offset: bigint_to_u64(&leader_schedule_slot_offset)?, + warmup, + first_normal_epoch: bigint_to_u64(&first_normal_epoch)?, + first_normal_slot: bigint_to_u64(&first_normal_slot)?, + })) + } + + /// The maximum number of slots in each epoch. + #[napi(getter)] + pub fn slots_per_epoch(&self) -> u64 { + self.0.slots_per_epoch + } + + #[napi(setter)] + pub fn set_slots_per_epoch(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.slots_per_epoch = bigint_to_u64(&val)?) + } + + /// A number of slots before beginning of an epoch to calculate + /// a leader schedule for that epoch. + #[napi(getter)] + pub fn leader_schedule_slot_offset(&self) -> u64 { + self.0.leader_schedule_slot_offset + } + + #[napi(setter)] + pub fn set_leader_schedule_slot_offset(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.leader_schedule_slot_offset = bigint_to_u64(&val)?) + } + + /// Whether epochs start short and grow. + #[napi(getter)] + pub fn warmup(&self) -> bool { + self.0.warmup + } + + #[napi(setter)] + pub fn set_warmup(&mut self, val: bool) { + self.0.warmup = val; + } + + /// The first epoch after the warmup period. + /// + /// Basically: `log2(slots_per_epoch) - log2(MINIMUM_SLOTS_PER_EPOCH)`. + #[napi(getter)] + pub fn first_normal_epoch(&self) -> u64 { + self.0.first_normal_epoch + } + + #[napi(setter)] + pub fn set_first_normal_epoch(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.first_normal_epoch = bigint_to_u64(&val)?) + } + + /// The first slot after the warmup period. + /// + /// Basically: `MINIMUM_SLOTS_PER_EPOCH * (2.pow(first_normal_epoch) - 1)`. + #[napi(getter)] + pub fn first_normal_slot(&self) -> u64 { + self.0.first_normal_slot + } + + #[napi(setter)] + pub fn set_first_normal_slot(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.first_normal_slot = bigint_to_u64(&val)?) + } +} diff --git a/crates/node-litesvm-core/src/sysvar/mod.rs b/crates/node-litesvm-core/src/sysvar/mod.rs new file mode 100644 index 000000000..e354730cc --- /dev/null +++ b/crates/node-litesvm-core/src/sysvar/mod.rs @@ -0,0 +1,7 @@ +pub mod clock; +pub mod epoch_rewards; +pub mod epoch_schedule; +pub mod rent; +pub mod slot_hashes; +pub mod slot_history; +pub mod stake_history; diff --git a/crates/node-litesvm-core/src/sysvar/rent.rs b/crates/node-litesvm-core/src/sysvar/rent.rs new file mode 100644 index 000000000..6f664cc4d --- /dev/null +++ b/crates/node-litesvm-core/src/sysvar/rent.rs @@ -0,0 +1,187 @@ +#![allow(deprecated)] + +use { + crate::{ + to_string_js, + util::{bigint_to_u64, bigint_to_usize}, + }, + napi::bindgen_prelude::*, + solana_rent::{Rent as RentOriginal, ACCOUNT_STORAGE_OVERHEAD}, +}; + +/// Configuration of network rent. +#[derive(Default, Debug)] +#[napi] +pub struct Rent(pub(crate) RentOriginal); + +#[napi] +impl Rent { + /// @param lamportsPerByteYear - Rental rate in lamports/byte-year. + /// @param exemptionThreshold - Amount of time (in years) a balance must include rent for the account to be rent exempt. + /// @param burnPercent - The percentage of collected rent that is burned. + #[napi(constructor)] + pub fn new( + lamports_per_byte_year: BigInt, + exemption_threshold: f64, + burn_percent: u8, + ) -> Result { + Ok(Self(RentOriginal { + lamports_per_byte: bigint_to_u64(&lamports_per_byte_year)?, + exemption_threshold: exemption_threshold.to_le_bytes(), + burn_percent, + })) + } + + /// Initialize rent with the default Solana settings. + #[napi(factory, js_name = "default")] + pub fn new_default() -> Self { + Self::default() + } + + /// Rental rate in lamports/byte-year. + /// + /// Note: since SIMD-0194 the underlying sysvar stores lamports/byte with + /// the exemption threshold folded in (default 6960, threshold 1.0), so on + /// current clusters this returns double the historical 3480 value. + #[napi(getter)] + pub fn lamports_per_byte_year(&self) -> u64 { + self.0.lamports_per_byte + } + + #[napi(setter)] + pub fn set_lamports_per_byte_year(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.lamports_per_byte = bigint_to_u64(&val)?) + } + + /// Amount of time (in years) a balance must include rent for the account to be rent exempt. + #[napi(getter)] + pub fn exemption_threshold(&self) -> f64 { + f64::from_le_bytes(self.0.exemption_threshold) + } + + #[napi(setter)] + pub fn set_exemption_threshold(&mut self, val: f64) { + self.0.exemption_threshold = val.to_le_bytes(); + } + + /// The percentage of collected rent that is burned. + #[napi(getter)] + pub fn burn_percent(&self) -> u8 { + self.0.burn_percent + } + + #[napi(setter)] + pub fn set_burn_percent(&mut self, val: u8) { + self.0.burn_percent = val; + } + + /// Calculate how much rent to burn from the collected rent. + /// + /// The first value returned is the amount burned. The second is the amount + /// to distribute to validators. + /// + /// @param rentCollected: The amount of rent collected. + /// @returns The amount burned and the amount to distribute to validators. + #[napi(ts_return_type = "[bigint, bigint]")] + pub fn calculate_burn(&self, rent_collected: BigInt) -> Result<[u64; 2]> { + let rent_collected = bigint_to_u64(&rent_collected)?; + let burned = rent_collected.saturating_mul(u64::from(self.0.burn_percent)) / 100; + Ok([burned, rent_collected.saturating_sub(burned)]) + } + + /// Minimum balance due for rent-exemption of a given account data size. + /// + /// Note: a stripped-down version of this calculation is used in + /// ``calculate_split_rent_exempt_reserve`` in the stake program. When this + /// function is updated, eg. when making rent variable, the stake program + /// will need to be refactored. + /// + /// @param dataLen - The account data size. + /// @returns The minimum balance due. + #[napi] + pub fn minimum_balance(&self, data_len: BigInt) -> Result { + self.checked_minimum_balance(bigint_to_usize(&data_len)?) + } + + /// Whether a given balance and data length would be exempt. + #[napi] + pub fn is_exempt(&self, balance: BigInt, data_len: BigInt) -> Result { + Ok( + bigint_to_u64(&balance)? + >= self.checked_minimum_balance(bigint_to_usize(&data_len)?)?, + ) + } + + /// Rent due on account's data length with balance. + /// + /// @param balance - The account balance. + /// @param dataLen - The account data length. + /// @param yearsElapsed - Time elapsed in years. + /// @returns The rent due. + #[napi] + pub fn due( + &self, + balance: BigInt, + data_len: BigInt, + years_elapsed: f64, + ) -> Result> { + let balance = bigint_to_u64(&balance)?; + let data_len = bigint_to_usize(&data_len)?; + if balance >= self.checked_minimum_balance(data_len)? { + Ok(None) + } else { + Ok(Some(self.due_amount_inner(data_len as u64, years_elapsed))) + } + } + + /// Rent due for account that is known to be not exempt. + /// + /// Note: since SIMD-0194 the underlying rate is lamports/byte with the + /// exemption threshold folded in, so on current clusters this returns + /// double the pre-SIMD-0194 amount for the same inputs. + /// + /// @param dataLen - The account data length. + /// @param yearsElapsed - Time elapsed in years. + /// @returns The amount due. + #[napi] + pub fn due_amount(&self, data_len: BigInt, years_elapsed: f64) -> Result { + Ok(self.due_amount_inner(bigint_to_usize(&data_len)? as u64, years_elapsed)) + } + + /// Creates a `Rent` that charges no lamports. + /// + /// This is used for testing. + /// + #[napi(factory)] + pub fn free() -> Self { + Self(RentOriginal::free()) + } + + /// Creates a `Rent` that is scaled based on the number of slots in an epoch. + /// + /// This is used for testing. + /// + /// @deprecated Epoch-based rent scaling was removed upstream (SIMD-0194); + /// the argument is ignored and the default rent is returned. The old + /// scaling never affected the rent-exempt minimum balance. + #[napi(factory)] + pub fn with_slots_per_epoch(_slots_per_epoch: BigInt) -> Result { + Ok(Self::default()) + } + + fn checked_minimum_balance(&self, data_len: usize) -> Result { + self.0.try_minimum_balance(data_len).ok_or_else(|| { + Error::new( + Status::InvalidArg, + "maximum permitted data length exceeded".to_string(), + ) + }) + } + + fn due_amount_inner(&self, data_len: u64, years_elapsed: f64) -> u64 { + (((ACCOUNT_STORAGE_OVERHEAD + data_len) * self.0.lamports_per_byte) as f64 * years_elapsed) + as u64 + } +} + +to_string_js!(Rent); diff --git a/crates/node-litesvm-core/src/sysvar/slot_hashes.rs b/crates/node-litesvm-core/src/sysvar/slot_hashes.rs new file mode 100644 index 000000000..0086794fe --- /dev/null +++ b/crates/node-litesvm-core/src/sysvar/slot_hashes.rs @@ -0,0 +1,31 @@ +use napi::bindgen_prelude::*; + +#[derive(Debug, Clone)] +#[napi] +pub struct SlotHash { + pub(crate) slot: BigInt, + pub(crate) hash: String, +} + +#[napi] +impl SlotHash { + #[napi(getter)] + pub fn slot(&self) -> BigInt { + self.slot.clone() + } + + #[napi(getter)] + pub fn hash(&self) -> String { + self.hash.clone() + } + + #[napi(setter)] + pub fn set_slot(&mut self, slot: BigInt) { + self.slot = slot; + } + + #[napi(setter)] + pub fn set_hash(&mut self, hash: String) { + self.hash = hash; + } +} diff --git a/crates/node-litesvm-core/src/sysvar/slot_history.rs b/crates/node-litesvm-core/src/sysvar/slot_history.rs new file mode 100644 index 000000000..7ba24d9d7 --- /dev/null +++ b/crates/node-litesvm-core/src/sysvar/slot_history.rs @@ -0,0 +1,93 @@ +use { + crate::{to_string_js, util::bigint_to_u64}, + bv::BitVec, + napi::bindgen_prelude::*, + solana_slot_history::{Check, SlotHistory as SlotHistoryOriginal}, +}; + +#[derive(Debug)] +#[napi] +pub enum SlotHistoryCheck { + Future, + TooOld, + Found, + NotFound, +} + +to_string_js!(SlotHistoryCheck); + +impl From for SlotHistoryCheck { + fn from(value: Check) -> Self { + match value { + Check::Future => Self::Future, + Check::TooOld => Self::TooOld, + Check::Found => Self::Found, + Check::NotFound => Self::NotFound, + } + } +} + +/// A bitvector indicating which slots are present in the past epoch. +#[derive(Debug)] +#[napi] +pub struct SlotHistory(pub(crate) SlotHistoryOriginal); + +#[napi] +impl SlotHistory { + #[napi(constructor)] + pub fn new(bits: BigUint64Array, next_slot: BigInt) -> Result { + let bits_converted: BitVec = BitVec::from(bits.to_vec()); + Ok(Self(SlotHistoryOriginal { + bits: bits_converted, + next_slot: bigint_to_u64(&next_slot)?, + })) + } + + #[napi(factory, js_name = "default")] + pub fn new_default() -> Self { + Self(SlotHistoryOriginal::default()) + } + + #[napi(getter)] + pub fn bits(&self) -> BigUint64Array { + self.0.bits.clone().into_boxed_slice().to_vec().into() + } + + #[napi(setter)] + pub fn set_bits(&mut self, bits: BigUint64Array) { + let bits_converted: BitVec = BitVec::from(bits.to_vec()); + self.0.bits = bits_converted; + } + + #[napi(getter)] + pub fn next_slot(&self) -> u64 { + self.0.next_slot + } + + #[napi(setter)] + pub fn set_next_slot(&mut self, slot: BigInt) -> Result<()> { + Ok(self.0.next_slot = bigint_to_u64(&slot)?) + } + + #[napi] + pub fn add(&mut self, slot: BigInt) -> Result<()> { + Ok(self.0.add(bigint_to_u64(&slot)?)) + } + + #[napi] + pub fn check(&self, slot: BigInt) -> Result { + Ok(SlotHistoryCheck::from(self.0.check(bigint_to_u64(&slot)?))) + } + + #[napi] + pub fn oldest(&self) -> u64 { + self.0.oldest() + } + + #[napi] + pub fn newest(&self) -> u64 { + self.0.newest() + } +} + +to_string_js!(SlotHistory); diff --git a/crates/node-litesvm-core/src/sysvar/stake_history.rs b/crates/node-litesvm-core/src/sysvar/stake_history.rs new file mode 100644 index 000000000..d80f5b3f0 --- /dev/null +++ b/crates/node-litesvm-core/src/sysvar/stake_history.rs @@ -0,0 +1,87 @@ +use { + crate::{to_string_js, util::bigint_to_u64}, + napi::bindgen_prelude::*, + solana_stake_history::{ + StakeHistory as StakeHistoryOriginal, StakeHistoryEntry as StakeHistoryEntryOriginal, + }, +}; + +#[derive(Debug, Clone)] +#[napi] +pub struct StakeHistoryEntry(pub(crate) StakeHistoryEntryOriginal); + +#[napi] +impl StakeHistoryEntry { + /// @param effective - effective stake at this epoch + /// @param activating - sum of portion of stakes not fully warmed up + /// @param effective - requested to be cooled down, not fully deactivated yet + #[napi(constructor)] + pub fn new(effective: BigInt, activating: BigInt, deactivating: BigInt) -> Result { + Ok(Self(StakeHistoryEntryOriginal { + effective: bigint_to_u64(&effective)?, + activating: bigint_to_u64(&activating)?, + deactivating: bigint_to_u64(&deactivating)?, + })) + } + + /// effective stake at this epoch + #[napi(getter)] + pub fn effective(&self) -> u64 { + self.0.effective + } + + #[napi(setter)] + pub fn set_effective(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.effective = bigint_to_u64(&val)?) + } + + /// sum of portion of stakes not fully warmed up + #[napi(getter)] + pub fn activating(&self) -> u64 { + self.0.activating + } + + #[napi(setter)] + pub fn set_activating(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.effective = bigint_to_u64(&val)?) + } + + /// requested to be cooled down, not fully deactivated yet + #[napi(getter)] + pub fn deactivating(&self) -> u64 { + self.0.deactivating + } + + #[napi(setter)] + pub fn set_deactivating(&mut self, val: BigInt) -> Result<()> { + Ok(self.0.effective = bigint_to_u64(&val)?) + } +} + +to_string_js!(StakeHistoryEntry); + +/// A type to hold data for the StakeHistory sysvar. +#[derive(Debug)] +#[napi] +pub struct StakeHistory(pub(crate) StakeHistoryOriginal); + +#[napi] +impl StakeHistory { + #[napi(constructor)] + pub fn new() -> Self { + Self(StakeHistoryOriginal::default()) + } + + #[napi] + pub fn get(&self, epoch: BigInt) -> Result> { + Ok(self + .0 + .get(bigint_to_u64(&epoch)?) + .map(|x| StakeHistoryEntry(x.clone()))) + } + + #[napi] + pub fn add(&mut self, epoch: BigInt, entry: &StakeHistoryEntry) -> Result<()> { + Ok(self.0.add(bigint_to_u64(&epoch)?, entry.clone().0)) + } +} diff --git a/crates/node-litesvm-core/src/transaction_error.rs b/crates/node-litesvm-core/src/transaction_error.rs new file mode 100644 index 000000000..f21a26e28 --- /dev/null +++ b/crates/node-litesvm-core/src/transaction_error.rs @@ -0,0 +1,498 @@ +use { + crate::to_string_js, + core::fmt, + napi::bindgen_prelude::{Either3, Either5}, + solana_instruction::error::InstructionError as InstructionErrorOriginal, + solana_transaction_error::TransactionError as TransactionErrorOriginal, +}; + +#[derive(Clone, Debug)] +#[napi] +pub struct InstructionErrorCustom { + pub code: u32, +} + +to_string_js!(InstructionErrorCustom); + +#[derive(Clone, Debug)] +#[napi] +pub struct InstructionErrorBorshIO { + pub msg: String, +} + +to_string_js!(InstructionErrorBorshIO); + +#[derive(Debug, Clone)] +#[napi] +pub enum InstructionErrorFieldless { + GenericError, + InvalidArgument, + InvalidInstructionData, + InvalidAccountData, + AccountDataTooSmall, + InsufficientFunds, + IncorrectProgramId, + MissingRequiredSignature, + AccountAlreadyInitialized, + UninitializedAccount, + UnbalancedInstruction, + ModifiedProgramId, + ExternalAccountLamportSpend, + ExternalAccountDataModified, + ReadonlyLamportChange, + ReadonlyDataModified, + DuplicateAccountIndex, + ExecutableModified, + RentEpochModified, + NotEnoughAccountKeys, + AccountDataSizeChanged, + AccountNotExecutable, + AccountBorrowFailed, + AccountBorrowOutstanding, + DuplicateAccountOutOfSync, + InvalidError, + ExecutableDataModified, + ExecutableLamportChange, + ExecutableAccountNotRentExempt, + UnsupportedProgramId, + CallDepth, + MissingAccount, + ReentrancyNotAllowed, + MaxSeedLengthExceeded, + InvalidSeeds, + InvalidRealloc, + ComputationalBudgetExceeded, + PrivilegeEscalation, + ProgramEnvironmentSetupFailure, + ProgramFailedToComplete, + ProgramFailedToCompile, + Immutable, + IncorrectAuthority, + AccountNotRentExempt, + InvalidAccountOwner, + ArithmeticOverflow, + UnsupportedSysvar, + IllegalOwner, + MaxAccountsDataAllocationsExceeded, + MaxAccountsExceeded, + MaxInstructionTraceLengthExceeded, + BuiltinProgramsMustConsumeComputeUnits, + BorshIoError, +} + +to_string_js!(InstructionErrorFieldless); + +pub type InstructionError = + Either3; + +// fn debug_instruction_error(err: InstructionError, f: &mut fmt::Formatter) -> fmt::Result { +// match err { +// InstructionError::A(fieldless) => write!(f, "{fieldless:?}"), +// InstructionError::B(custom) => write!(f, "{custom:?}"), +// InstructionError::C(borsh_err) => write!(f, "{borsh_err:?}"), +// } +// } + +fn convert_instruction_error(e: InstructionErrorOriginal) -> InstructionError { + match e { + InstructionErrorOriginal::Custom(code) => { + InstructionError::B(InstructionErrorCustom { code }) + } + InstructionErrorOriginal::BorshIoError => { + InstructionError::A(InstructionErrorFieldless::BorshIoError) + } + InstructionErrorOriginal::GenericError => { + InstructionError::A(InstructionErrorFieldless::GenericError) + } + InstructionErrorOriginal::InvalidArgument => { + InstructionError::A(InstructionErrorFieldless::InvalidArgument) + } + InstructionErrorOriginal::InvalidInstructionData => { + InstructionError::A(InstructionErrorFieldless::InvalidInstructionData) + } + InstructionErrorOriginal::InvalidAccountData => { + InstructionError::A(InstructionErrorFieldless::InvalidAccountData) + } + InstructionErrorOriginal::AccountDataTooSmall => { + InstructionError::A(InstructionErrorFieldless::AccountDataTooSmall) + } + InstructionErrorOriginal::InsufficientFunds => { + InstructionError::A(InstructionErrorFieldless::InsufficientFunds) + } + InstructionErrorOriginal::IncorrectProgramId => { + InstructionError::A(InstructionErrorFieldless::IncorrectProgramId) + } + InstructionErrorOriginal::MissingRequiredSignature => { + InstructionError::A(InstructionErrorFieldless::MissingRequiredSignature) + } + InstructionErrorOriginal::AccountAlreadyInitialized => { + InstructionError::A(InstructionErrorFieldless::AccountAlreadyInitialized) + } + InstructionErrorOriginal::UninitializedAccount => { + InstructionError::A(InstructionErrorFieldless::UninitializedAccount) + } + InstructionErrorOriginal::UnbalancedInstruction => { + InstructionError::A(InstructionErrorFieldless::UnbalancedInstruction) + } + InstructionErrorOriginal::ModifiedProgramId => { + InstructionError::A(InstructionErrorFieldless::ModifiedProgramId) + } + InstructionErrorOriginal::ExternalAccountLamportSpend => { + InstructionError::A(InstructionErrorFieldless::ExternalAccountLamportSpend) + } + InstructionErrorOriginal::ExternalAccountDataModified => { + InstructionError::A(InstructionErrorFieldless::ExternalAccountDataModified) + } + InstructionErrorOriginal::ReadonlyLamportChange => { + InstructionError::A(InstructionErrorFieldless::ReadonlyLamportChange) + } + InstructionErrorOriginal::ReadonlyDataModified => { + InstructionError::A(InstructionErrorFieldless::ReadonlyDataModified) + } + InstructionErrorOriginal::DuplicateAccountIndex => { + InstructionError::A(InstructionErrorFieldless::DuplicateAccountIndex) + } + InstructionErrorOriginal::ExecutableModified => { + InstructionError::A(InstructionErrorFieldless::ExecutableModified) + } + InstructionErrorOriginal::RentEpochModified => { + InstructionError::A(InstructionErrorFieldless::RentEpochModified) + } + #[allow(deprecated)] + InstructionErrorOriginal::NotEnoughAccountKeys => { + InstructionError::A(InstructionErrorFieldless::NotEnoughAccountKeys) + } + InstructionErrorOriginal::AccountDataSizeChanged => { + InstructionError::A(InstructionErrorFieldless::AccountDataSizeChanged) + } + InstructionErrorOriginal::AccountNotExecutable => { + InstructionError::A(InstructionErrorFieldless::AccountNotExecutable) + } + InstructionErrorOriginal::AccountBorrowFailed => { + InstructionError::A(InstructionErrorFieldless::AccountBorrowFailed) + } + InstructionErrorOriginal::AccountBorrowOutstanding => { + InstructionError::A(InstructionErrorFieldless::AccountBorrowOutstanding) + } + InstructionErrorOriginal::DuplicateAccountOutOfSync => { + InstructionError::A(InstructionErrorFieldless::DuplicateAccountOutOfSync) + } + InstructionErrorOriginal::InvalidError => { + InstructionError::A(InstructionErrorFieldless::InvalidError) + } + InstructionErrorOriginal::ExecutableDataModified => { + InstructionError::A(InstructionErrorFieldless::ExecutableDataModified) + } + InstructionErrorOriginal::ExecutableLamportChange => { + InstructionError::A(InstructionErrorFieldless::ExecutableLamportChange) + } + InstructionErrorOriginal::ExecutableAccountNotRentExempt => { + InstructionError::A(InstructionErrorFieldless::ExecutableAccountNotRentExempt) + } + InstructionErrorOriginal::UnsupportedProgramId => { + InstructionError::A(InstructionErrorFieldless::UnsupportedProgramId) + } + InstructionErrorOriginal::CallDepth => { + InstructionError::A(InstructionErrorFieldless::CallDepth) + } + InstructionErrorOriginal::MissingAccount => { + InstructionError::A(InstructionErrorFieldless::MissingAccount) + } + InstructionErrorOriginal::ReentrancyNotAllowed => { + InstructionError::A(InstructionErrorFieldless::ReentrancyNotAllowed) + } + InstructionErrorOriginal::MaxSeedLengthExceeded => { + InstructionError::A(InstructionErrorFieldless::MaxSeedLengthExceeded) + } + InstructionErrorOriginal::InvalidSeeds => { + InstructionError::A(InstructionErrorFieldless::InvalidSeeds) + } + InstructionErrorOriginal::InvalidRealloc => { + InstructionError::A(InstructionErrorFieldless::InvalidRealloc) + } + InstructionErrorOriginal::ComputationalBudgetExceeded => { + InstructionError::A(InstructionErrorFieldless::ComputationalBudgetExceeded) + } + InstructionErrorOriginal::PrivilegeEscalation => { + InstructionError::A(InstructionErrorFieldless::PrivilegeEscalation) + } + InstructionErrorOriginal::ProgramEnvironmentSetupFailure => { + InstructionError::A(InstructionErrorFieldless::ProgramEnvironmentSetupFailure) + } + InstructionErrorOriginal::ProgramFailedToComplete => { + InstructionError::A(InstructionErrorFieldless::ProgramFailedToComplete) + } + InstructionErrorOriginal::ProgramFailedToCompile => { + InstructionError::A(InstructionErrorFieldless::ProgramFailedToCompile) + } + InstructionErrorOriginal::Immutable => { + InstructionError::A(InstructionErrorFieldless::Immutable) + } + InstructionErrorOriginal::IncorrectAuthority => { + InstructionError::A(InstructionErrorFieldless::IncorrectAuthority) + } + InstructionErrorOriginal::AccountNotRentExempt => { + InstructionError::A(InstructionErrorFieldless::AccountNotRentExempt) + } + InstructionErrorOriginal::InvalidAccountOwner => { + InstructionError::A(InstructionErrorFieldless::InvalidAccountOwner) + } + InstructionErrorOriginal::ArithmeticOverflow => { + InstructionError::A(InstructionErrorFieldless::ArithmeticOverflow) + } + InstructionErrorOriginal::UnsupportedSysvar => { + InstructionError::A(InstructionErrorFieldless::UnsupportedSysvar) + } + InstructionErrorOriginal::IllegalOwner => { + InstructionError::A(InstructionErrorFieldless::IllegalOwner) + } + InstructionErrorOriginal::MaxAccountsDataAllocationsExceeded => { + InstructionError::A(InstructionErrorFieldless::MaxAccountsDataAllocationsExceeded) + } + InstructionErrorOriginal::MaxAccountsExceeded => { + InstructionError::A(InstructionErrorFieldless::MaxAccountsExceeded) + } + InstructionErrorOriginal::MaxInstructionTraceLengthExceeded => { + InstructionError::A(InstructionErrorFieldless::MaxInstructionTraceLengthExceeded) + } + InstructionErrorOriginal::BuiltinProgramsMustConsumeComputeUnits => { + InstructionError::A(InstructionErrorFieldless::BuiltinProgramsMustConsumeComputeUnits) + } + } +} + +#[derive(Debug)] +#[napi] +pub enum TransactionErrorFieldless { + AccountInUse, + AccountLoadedTwice, + AccountNotFound, + ProgramAccountNotFound, + InsufficientFundsForFee, + InvalidAccountForFee, + AlreadyProcessed, + BlockhashNotFound, + CallChainTooDeep, + MissingSignatureForFee, + InvalidAccountIndex, + SignatureFailure, + InvalidProgramForExecution, + SanitizeFailure, + ClusterMaintenance, + AccountBorrowOutstanding, + WouldExceedMaxBlockCostLimit, + UnsupportedVersion, + InvalidWritableAccount, + WouldExceedMaxAccountCostLimit, + WouldExceedAccountDataBlockLimit, + TooManyAccountLocks, + AddressLookupTableNotFound, + InvalidAddressLookupTableOwner, + InvalidAddressLookupTableData, + InvalidAddressLookupTableIndex, + InvalidRentPayingAccount, + WouldExceedMaxVoteCostLimit, + WouldExceedAccountDataTotalLimit, + MaxLoadedAccountsDataSizeExceeded, + ResanitizationNeeded, + InvalidLoadedAccountsDataSizeLimit, + UnbalancedTransaction, + ProgramCacheHitMaxLimit, + CommitCancelled, +} + +to_string_js!(TransactionErrorFieldless); + +#[derive(Clone)] +#[napi] +pub struct TransactionErrorInstructionError { + pub index: u8, + error: InstructionError, +} + +impl fmt::Debug for TransactionErrorInstructionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("TransactionErrorInstructionError") + .field("index", &self.index) + .field( + "error", + match &self.error { + InstructionError::A(a) => a, + InstructionError::B(b) => b, + InstructionError::C(c) => c, + }, + ) + .finish() + } +} + +#[napi] +impl TransactionErrorInstructionError { + #[napi( + ts_return_type = "InstructionErrorFieldless | InstructionErrorCustom | InstructionErrorBorshIO" + )] + pub fn err(&self) -> InstructionError { + self.error.clone() + } +} + +to_string_js!(TransactionErrorInstructionError); + +#[derive(Debug)] +#[napi] +pub struct TransactionErrorDuplicateInstruction { + pub index: u8, +} + +to_string_js!(TransactionErrorDuplicateInstruction); + +#[derive(Debug)] +#[napi] +pub struct TransactionErrorInsufficientFundsForRent { + pub account_index: u8, +} + +to_string_js!(TransactionErrorInsufficientFundsForRent); + +#[derive(Debug)] +#[napi] +pub struct TransactionErrorProgramExecutionTemporarilyRestricted { + pub account_index: u8, +} + +to_string_js!(TransactionErrorProgramExecutionTemporarilyRestricted); + +pub type TransactionError = Either5< + TransactionErrorFieldless, + TransactionErrorInstructionError, + TransactionErrorDuplicateInstruction, + TransactionErrorInsufficientFundsForRent, + TransactionErrorProgramExecutionTemporarilyRestricted, +>; + +pub(crate) fn convert_transaction_error(w: TransactionErrorOriginal) -> TransactionError { + match w { + TransactionErrorOriginal::InstructionError(index, err) => { + TransactionError::B(TransactionErrorInstructionError { + index, + error: convert_instruction_error(err), + }) + } + TransactionErrorOriginal::DuplicateInstruction(index) => { + TransactionError::C(TransactionErrorDuplicateInstruction { index }) + } + TransactionErrorOriginal::InsufficientFundsForRent { account_index } => { + TransactionError::D(TransactionErrorInsufficientFundsForRent { account_index }) + } + TransactionErrorOriginal::ProgramExecutionTemporarilyRestricted { account_index } => { + TransactionError::E(TransactionErrorProgramExecutionTemporarilyRestricted { + account_index, + }) + } + TransactionErrorOriginal::AccountInUse => { + TransactionError::A(TransactionErrorFieldless::AccountInUse) + } + TransactionErrorOriginal::AccountLoadedTwice => { + TransactionError::A(TransactionErrorFieldless::AccountLoadedTwice) + } + TransactionErrorOriginal::AccountNotFound => { + TransactionError::A(TransactionErrorFieldless::AccountNotFound) + } + TransactionErrorOriginal::ProgramAccountNotFound => { + TransactionError::A(TransactionErrorFieldless::ProgramAccountNotFound) + } + TransactionErrorOriginal::InsufficientFundsForFee => { + TransactionError::A(TransactionErrorFieldless::InsufficientFundsForFee) + } + TransactionErrorOriginal::InvalidAccountForFee => { + TransactionError::A(TransactionErrorFieldless::InvalidAccountForFee) + } + TransactionErrorOriginal::AlreadyProcessed => { + TransactionError::A(TransactionErrorFieldless::AlreadyProcessed) + } + TransactionErrorOriginal::BlockhashNotFound => { + TransactionError::A(TransactionErrorFieldless::BlockhashNotFound) + } + TransactionErrorOriginal::CallChainTooDeep => { + TransactionError::A(TransactionErrorFieldless::CallChainTooDeep) + } + TransactionErrorOriginal::MissingSignatureForFee => { + TransactionError::A(TransactionErrorFieldless::MissingSignatureForFee) + } + TransactionErrorOriginal::InvalidAccountIndex => { + TransactionError::A(TransactionErrorFieldless::InvalidAccountIndex) + } + TransactionErrorOriginal::SignatureFailure => { + TransactionError::A(TransactionErrorFieldless::SignatureFailure) + } + TransactionErrorOriginal::InvalidProgramForExecution => { + TransactionError::A(TransactionErrorFieldless::InvalidProgramForExecution) + } + TransactionErrorOriginal::SanitizeFailure => { + TransactionError::A(TransactionErrorFieldless::SanitizeFailure) + } + TransactionErrorOriginal::ClusterMaintenance => { + TransactionError::A(TransactionErrorFieldless::ClusterMaintenance) + } + TransactionErrorOriginal::AccountBorrowOutstanding => { + TransactionError::A(TransactionErrorFieldless::AccountBorrowOutstanding) + } + TransactionErrorOriginal::WouldExceedMaxBlockCostLimit => { + TransactionError::A(TransactionErrorFieldless::WouldExceedMaxBlockCostLimit) + } + TransactionErrorOriginal::UnsupportedVersion => { + TransactionError::A(TransactionErrorFieldless::UnsupportedVersion) + } + TransactionErrorOriginal::InvalidWritableAccount => { + TransactionError::A(TransactionErrorFieldless::InvalidWritableAccount) + } + TransactionErrorOriginal::WouldExceedMaxAccountCostLimit => { + TransactionError::A(TransactionErrorFieldless::WouldExceedMaxAccountCostLimit) + } + TransactionErrorOriginal::WouldExceedAccountDataBlockLimit => { + TransactionError::A(TransactionErrorFieldless::WouldExceedAccountDataBlockLimit) + } + TransactionErrorOriginal::TooManyAccountLocks => { + TransactionError::A(TransactionErrorFieldless::TooManyAccountLocks) + } + TransactionErrorOriginal::AddressLookupTableNotFound => { + TransactionError::A(TransactionErrorFieldless::AddressLookupTableNotFound) + } + TransactionErrorOriginal::InvalidAddressLookupTableOwner => { + TransactionError::A(TransactionErrorFieldless::InvalidAddressLookupTableOwner) + } + TransactionErrorOriginal::InvalidAddressLookupTableData => { + TransactionError::A(TransactionErrorFieldless::InvalidAddressLookupTableData) + } + TransactionErrorOriginal::InvalidAddressLookupTableIndex => { + TransactionError::A(TransactionErrorFieldless::InvalidAddressLookupTableIndex) + } + TransactionErrorOriginal::InvalidRentPayingAccount => { + TransactionError::A(TransactionErrorFieldless::InvalidRentPayingAccount) + } + TransactionErrorOriginal::WouldExceedMaxVoteCostLimit => { + TransactionError::A(TransactionErrorFieldless::WouldExceedMaxVoteCostLimit) + } + TransactionErrorOriginal::WouldExceedAccountDataTotalLimit => { + TransactionError::A(TransactionErrorFieldless::WouldExceedAccountDataTotalLimit) + } + TransactionErrorOriginal::MaxLoadedAccountsDataSizeExceeded => { + TransactionError::A(TransactionErrorFieldless::MaxLoadedAccountsDataSizeExceeded) + } + TransactionErrorOriginal::ResanitizationNeeded => { + TransactionError::A(TransactionErrorFieldless::ResanitizationNeeded) + } + TransactionErrorOriginal::InvalidLoadedAccountsDataSizeLimit => { + TransactionError::A(TransactionErrorFieldless::InvalidLoadedAccountsDataSizeLimit) + } + TransactionErrorOriginal::UnbalancedTransaction => { + TransactionError::A(TransactionErrorFieldless::UnbalancedTransaction) + } + TransactionErrorOriginal::ProgramCacheHitMaxLimit => { + TransactionError::A(TransactionErrorFieldless::ProgramCacheHitMaxLimit) + } + TransactionErrorOriginal::CommitCancelled => { + TransactionError::A(TransactionErrorFieldless::CommitCancelled) + } + } +} diff --git a/crates/node-litesvm-core/src/transaction_metadata.rs b/crates/node-litesvm-core/src/transaction_metadata.rs new file mode 100644 index 000000000..d37bb81bb --- /dev/null +++ b/crates/node-litesvm-core/src/transaction_metadata.rs @@ -0,0 +1,196 @@ +use { + crate::{ + account::Account, + to_string_js, + transaction_error::{convert_transaction_error, TransactionError}, + }, + litesvm::types::{ + FailedTransactionMetadata as FailedTransactionMetadataOriginal, + SimulatedTransactionInfo as SimulatedTransactionInfoOriginal, + TransactionMetadata as TransactionMetadataOriginal, + }, + napi::bindgen_prelude::*, + solana_account::Account as AccountOriginal, + solana_message::{ + compiled_instruction::CompiledInstruction as CompiledInstructionOriginal, + inner_instruction::InnerInstruction as InnerInstructionOriginal, + }, + solana_transaction_context::transaction::TransactionReturnData as TransactionReturnDataOriginal, +}; + +#[derive(Debug, Clone)] +#[napi] +pub struct CompiledInstruction(CompiledInstructionOriginal); + +#[napi] +impl CompiledInstruction { + #[napi(constructor)] + pub fn new(program_id_index: u8, accounts: Uint8Array, data: Uint8Array) -> Self { + Self(CompiledInstructionOriginal { + program_id_index, + accounts: accounts.to_vec(), + data: data.to_vec(), + }) + } + + #[napi] + pub fn program_id_index(&self) -> u8 { + self.0.program_id_index + } + + #[napi] + pub fn accounts(&self) -> Uint8Array { + Uint8Array::new(self.0.accounts.clone()) + } + + #[napi] + pub fn data(&self) -> Uint8Array { + Uint8Array::new(self.0.data.clone()) + } +} + +to_string_js!(CompiledInstruction); + +#[derive(Debug, Clone)] +#[napi] +pub struct InnerInstruction(InnerInstructionOriginal); + +#[napi] +impl InnerInstruction { + #[napi] + pub fn instruction(&self) -> CompiledInstruction { + CompiledInstruction(self.0.instruction.clone()) + } + + #[napi] + pub fn stack_height(&self) -> u8 { + self.0.stack_height + } +} + +to_string_js!(InnerInstruction); + +#[derive(Debug, Clone)] +#[napi] +pub struct TransactionReturnData(TransactionReturnDataOriginal); + +#[napi] +impl TransactionReturnData { + #[napi] + pub fn program_id(&self) -> Uint8Array { + Uint8Array::with_data_copied(self.0.program_id) + } + + #[napi] + pub fn data(&self) -> Uint8Array { + Uint8Array::new(self.0.data.clone()) + } +} + +to_string_js!(TransactionReturnData); + +#[derive(Debug, Clone)] +#[napi] +pub struct TransactionMetadata(pub(crate) TransactionMetadataOriginal); + +#[napi] +impl TransactionMetadata { + #[napi] + pub fn signature(&self) -> Uint8Array { + Uint8Array::with_data_copied(self.0.signature) + } + + #[napi] + pub fn logs(&self) -> Vec { + self.0.logs.clone() + } + + #[napi] + pub fn inner_instructions(&self) -> Vec> { + self.0 + .inner_instructions + .clone() + .into_iter() + .map(|outer| outer.into_iter().map(InnerInstruction).collect()) + .collect() + } + + #[napi] + pub fn compute_units_consumed(&self) -> u64 { + self.0.compute_units_consumed + } + + #[napi] + pub fn return_data(&self) -> TransactionReturnData { + TransactionReturnData(self.0.return_data.clone()) + } + + #[napi] + pub fn pretty_logs(&self) -> String { + self.0.pretty_logs() + } +} + +to_string_js!(TransactionMetadata); + +#[derive(Debug, Clone)] +#[napi] +pub struct FailedTransactionMetadata(pub(crate) FailedTransactionMetadataOriginal); + +#[napi] +impl FailedTransactionMetadata { + #[napi( + ts_return_type = "TransactionErrorFieldless | TransactionErrorInstructionError | TransactionErrorDuplicateInstruction | TransactionErrorInsufficientFundsForRent | TransactionErrorProgramExecutionTemporarilyRestricted" + )] + pub fn err(&self) -> TransactionError { + convert_transaction_error(self.0.err.clone()) + } + + #[napi] + pub fn meta(&self) -> TransactionMetadata { + TransactionMetadata(self.0.meta.clone()) + } +} + +to_string_js!(FailedTransactionMetadata); + +#[napi] +pub struct AddressAndAccount { + pub address: Uint8Array, + pub(crate) account: Account, +} + +#[napi] +impl AddressAndAccount { + #[napi] + pub fn account(&self) -> Account { + self.account.clone() + } +} + +#[derive(Debug, Clone)] +#[napi] +pub struct SimulatedTransactionInfo(pub(crate) SimulatedTransactionInfoOriginal); + +#[napi] +impl SimulatedTransactionInfo { + #[napi] + pub fn meta(&self) -> TransactionMetadata { + TransactionMetadata(self.0.meta.clone()) + } + + #[napi] + pub fn post_accounts(&self) -> Vec { + self.0 + .post_accounts + .clone() + .into_iter() + .map(|x| AddressAndAccount { + address: Uint8Array::with_data_copied(x.0), + account: Account(AccountOriginal::from(x.1)), + }) + .collect() + } +} + +to_string_js!(SimulatedTransactionInfo); diff --git a/crates/node-litesvm-core/src/util.rs b/crates/node-litesvm-core/src/util.rs new file mode 100644 index 000000000..4c0f26c31 --- /dev/null +++ b/crates/node-litesvm-core/src/util.rs @@ -0,0 +1,82 @@ +use {napi::bindgen_prelude::*, solana_address::Address, solana_hash::Hash, std::str::FromStr}; + +pub(crate) fn convert_pubkey(address: &[u8]) -> Address { + Address::try_from(address).unwrap() +} + +pub(crate) fn try_parse_hash(raw: &str) -> Result { + Hash::from_str(raw).map_err(|e| { + Error::new( + Status::GenericFailure, + format!("Failed to parse blockhash: {e}"), + ) + }) +} + +pub(crate) fn bigint_to_u64(val: &BigInt) -> Result { + let res = val.get_u64(); + if res.0 { + return Err(Error::new( + Status::GenericFailure, + format!("Cannot convert negative bigint to u64: {val:?}"), + )); + } + if !res.2 { + return Err(Error::new( + Status::GenericFailure, + format!("Bigint too large for u64: {val:?}"), + )); + } + Ok(res.1) +} + +pub(crate) fn bigint_to_u128(val: &BigInt) -> Result { + let res = val.get_u128(); + if res.0 { + return Err(Error::new( + Status::GenericFailure, + format!("Cannot convert negative bigint to u128: {val:?}"), + )); + } + if !res.2 { + return Err(Error::new( + Status::GenericFailure, + format!("Bigint too large for u128: {val:?}"), + )); + } + Ok(res.1) +} + +pub(crate) fn bigint_to_i64(val: &BigInt) -> Result { + let res = val.get_i64(); + if !res.1 { + return Err(Error::new( + Status::GenericFailure, + format!("Bigint too large for i64: {val:?}"), + )); + } + Ok(res.0) +} + +pub(crate) fn bigint_to_usize(val: &BigInt) -> Result { + let res = val.get_u64(); + if res.0 { + return Err(Error::new( + Status::GenericFailure, + format!("Cannot convert negative bigint to usize: {val:?}"), + )); + } + if !res.2 { + return Err(Error::new( + Status::GenericFailure, + format!("Bigint too large for usize: {val:?}"), + )); + } + let val_u64 = res.1; + usize::try_from(val_u64).map_err(|_| { + Error::new( + Status::GenericFailure, + format!("Bigint too large for usize: {val_u64}"), + ) + }) +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..d5cfe560e --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "packageManager": "yarn@4.10.3", + "workspaces": [ + "crates/node-litesvm-core", + "crates/node-litesvm", + "crates/node-litesvm-web3js" + ], + "scripts": { + "build:core": "yarn workspace litesvm-core build", + "build:core:debug": "yarn workspace litesvm-core build:debug", + } +} diff --git a/crates/node-litesvm/yarn.lock b/yarn.lock similarity index 99% rename from crates/node-litesvm/yarn.lock rename to yarn.lock index 8d3d07429..59dec853d 100644 --- a/crates/node-litesvm/yarn.lock +++ b/yarn.lock @@ -3973,9 +3973,17 @@ __metadata: languageName: node linkType: hard -"litesvm@workspace:.": +"litesvm-core@workspace:crates/node-litesvm-core": version: 0.0.0-use.local - resolution: "litesvm@workspace:." + resolution: "litesvm-core@workspace:crates/node-litesvm-core" + dependencies: + "@napi-rs/cli": "npm:^3.7.2" + languageName: unknown + linkType: soft + +"litesvm@workspace:crates/node-litesvm": + version: 0.0.0-use.local + resolution: "litesvm@workspace:crates/node-litesvm" dependencies: "@algolia/client-search": "npm:^5.55.0" "@napi-rs/cli": "npm:^3.7.2" @@ -4633,6 +4641,12 @@ __metadata: languageName: node linkType: hard +"root-workspace-0b6124@workspace:.": + version: 0.0.0-use.local + resolution: "root-workspace-0b6124@workspace:." + languageName: unknown + linkType: soft + "safer-buffer@npm:>= 2.1.2 < 3.0.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" From c5cd09b83bccdd7d37b1134d0516a8f78a2847de Mon Sep 17 00:00:00 2001 From: Chii Yuen Date: Tue, 21 Jul 2026 14:52:36 +0800 Subject: [PATCH 2/7] refactor(node): make litesvm a Kit wrapper --- .github/workflows/node-build.yml | 13 +- .github/workflows/node-lint.yml | 2 + .github/workflows/release-node.yml | 5 +- crates/node-litesvm/.cargo/config.toml | 2 - crates/node-litesvm/Cargo.toml | 45 -- crates/node-litesvm/README.md | 4 + crates/node-litesvm/build.rs | 5 - crates/node-litesvm/litesvm/index.ts | 4 +- crates/node-litesvm/litesvm/internal.d.ts | 636 ------------------ crates/node-litesvm/litesvm/internal.js | 618 ----------------- crates/node-litesvm/package.json | 43 +- crates/node-litesvm/src/account.rs | 64 -- crates/node-litesvm/src/compute_budget.rs | 371 ---------- crates/node-litesvm/src/feature_set.rs | 76 --- crates/node-litesvm/src/lib.rs | 454 ------------- crates/node-litesvm/src/native_mint.rs | 43 -- crates/node-litesvm/src/sysvar/clock.rs | 97 --- .../node-litesvm/src/sysvar/epoch_rewards.rs | 129 ---- .../node-litesvm/src/sysvar/epoch_schedule.rs | 94 --- crates/node-litesvm/src/sysvar/mod.rs | 7 - crates/node-litesvm/src/sysvar/rent.rs | 187 ----- crates/node-litesvm/src/sysvar/slot_hashes.rs | 31 - .../node-litesvm/src/sysvar/slot_history.rs | 93 --- .../node-litesvm/src/sysvar/stake_history.rs | 87 --- crates/node-litesvm/src/transaction_error.rs | 498 -------------- .../node-litesvm/src/transaction_metadata.rs | 196 ------ crates/node-litesvm/src/util.rs | 82 --- .../node-litesvm/tests/computeLimit.test.ts | 2 +- .../node-litesvm/tests/getTransaction.test.ts | 3 +- .../tests/nonExistentProgram.test.ts | 2 +- yarn.lock | 4 +- 31 files changed, 39 insertions(+), 3858 deletions(-) delete mode 100644 crates/node-litesvm/.cargo/config.toml delete mode 100644 crates/node-litesvm/Cargo.toml delete mode 100644 crates/node-litesvm/build.rs delete mode 100644 crates/node-litesvm/litesvm/internal.d.ts delete mode 100644 crates/node-litesvm/litesvm/internal.js delete mode 100644 crates/node-litesvm/src/account.rs delete mode 100644 crates/node-litesvm/src/compute_budget.rs delete mode 100644 crates/node-litesvm/src/feature_set.rs delete mode 100644 crates/node-litesvm/src/lib.rs delete mode 100644 crates/node-litesvm/src/native_mint.rs delete mode 100644 crates/node-litesvm/src/sysvar/clock.rs delete mode 100644 crates/node-litesvm/src/sysvar/epoch_rewards.rs delete mode 100644 crates/node-litesvm/src/sysvar/epoch_schedule.rs delete mode 100644 crates/node-litesvm/src/sysvar/mod.rs delete mode 100644 crates/node-litesvm/src/sysvar/rent.rs delete mode 100644 crates/node-litesvm/src/sysvar/slot_hashes.rs delete mode 100644 crates/node-litesvm/src/sysvar/slot_history.rs delete mode 100644 crates/node-litesvm/src/sysvar/stake_history.rs delete mode 100644 crates/node-litesvm/src/transaction_error.rs delete mode 100644 crates/node-litesvm/src/transaction_metadata.rs delete mode 100644 crates/node-litesvm/src/util.rs diff --git a/.github/workflows/node-build.yml b/.github/workflows/node-build.yml index 68bdd8596..20a426f9c 100644 --- a/.github/workflows/node-build.yml +++ b/.github/workflows/node-build.yml @@ -39,6 +39,8 @@ jobs: components: clippy, rustfmt - name: Install dependencies run: corepack enable && yarn install --immutable + - name: Lint Kit wrapper + run: yarn workspace litesvm lint - name: Cargo fmt run: cargo fmt --all -- --check - name: Clippy core @@ -157,8 +159,11 @@ jobs: - name: List bindings run: ls -R crates/node-litesvm-core/litesvm shell: bash - - name: Test core bindings - run: yarn workspace litesvm-core test + - name: Test core and Kit wrapper + run: | + yarn workspace litesvm-core test + yarn workspace litesvm build + yarn workspace litesvm test test-linux-binding: name: Test ${{ matrix.target }} - node@${{ matrix.node }} @@ -215,9 +220,9 @@ jobs: - name: List bindings run: ls -R crates/node-litesvm-core/litesvm shell: bash - - name: Test core bindings + - name: Test core and Kit wrapper uses: addnab/docker-run-action@v3 with: image: ${{ steps.docker.outputs.IMAGE }} options: -v ${{ github.workspace }}:${{ github.workspace }} -w ${{ github.workspace }} --platform ${{ steps.docker.outputs.PLATFORM }} -e RUST_MIN_STACK=8388608 - run: corepack enable && yarn workspace litesvm-core test + run: corepack enable && yarn workspace litesvm-core test && yarn workspace litesvm build && yarn workspace litesvm test diff --git a/.github/workflows/node-lint.yml b/.github/workflows/node-lint.yml index 7afe0e742..78d20ffab 100644 --- a/.github/workflows/node-lint.yml +++ b/.github/workflows/node-lint.yml @@ -30,3 +30,5 @@ jobs: run: corepack enable && yarn install --immutable - name: Lint Rust core run: yarn workspace litesvm-core lint + - name: Lint Kit wrapper + run: yarn workspace litesvm lint:js diff --git a/.github/workflows/release-node.yml b/.github/workflows/release-node.yml index 24a36087d..ee7770a3e 100644 --- a/.github/workflows/release-node.yml +++ b/.github/workflows/release-node.yml @@ -30,13 +30,16 @@ jobs: path: crates/node-litesvm-core/artifacts - name: Move native artifacts run: yarn workspace litesvm-core artifacts + - name: Build Kit wrapper + run: yarn workspace litesvm build - name: List native packages run: ls -R crates/node-litesvm-core/npm - - name: Publish core + - name: Publish core and Kit wrapper run: | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc TAG=latest [[ "$GITHUB_REF_NAME" == *-rc.* ]] && TAG=next yarn workspace litesvm-core npm publish --access public --tag "$TAG" + yarn workspace litesvm npm publish --access public --tag "$TAG" env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/crates/node-litesvm/.cargo/config.toml b/crates/node-litesvm/.cargo/config.toml deleted file mode 100644 index ac2b23f81..000000000 --- a/crates/node-litesvm/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.x86_64-pc-windows-msvc] -rustflags = ["-C", "target-feature=+crt-static"] diff --git a/crates/node-litesvm/Cargo.toml b/crates/node-litesvm/Cargo.toml deleted file mode 100644 index 6f6bc26ab..000000000 --- a/crates/node-litesvm/Cargo.toml +++ /dev/null @@ -1,45 +0,0 @@ -[package] -edition = "2021" -name = "litesvm-node" -version = "1.3.0" -include = ["/src", "build.rs"] -repository = "https://github.com/LiteSVM/litesvm" -license = "MIT" - -[features] -default = [] -sbpf-debugger = ["litesvm/sbpf-debugger"] -register-tracing = ["litesvm/register-tracing"] - -[lib] -crate-type = ["cdylib"] -test = false - -[dependencies] -agave-feature-set.workspace = true -bv.workspace = true -litesvm = { workspace = true, features = ["nodejs-internal", "precompiles"] } -napi = { workspace = true, features = ["napi4", "napi6"] } -napi-derive.workspace = true -solana-account.workspace = true -solana-address.workspace = true -solana-clock.workspace = true -solana-compute-budget.workspace = true -solana-epoch-rewards.workspace = true -solana-epoch-schedule.workspace = true -solana-hash.workspace = true -solana-instruction.workspace = true -solana-last-restart-slot.workspace = true -solana-message.workspace = true -solana-rent.workspace = true -solana-signature.workspace = true -solana-slot-hashes.workspace = true -solana-slot-history.workspace = true -solana-stake-history.workspace = true -solana-transaction.workspace = true -solana-transaction-context.workspace = true -solana-transaction-error.workspace = true -wincode.workspace = true - -[build-dependencies] -napi-build.workspace = true diff --git a/crates/node-litesvm/README.md b/crates/node-litesvm/README.md index ae52d547d..fceca579e 100644 --- a/crates/node-litesvm/README.md +++ b/crates/node-litesvm/README.md @@ -9,6 +9,10 @@ This is the NodeJS wrapper for [LiteSVM](https://github.com/LiteSVM/litesvm). It brings best-in-class Solana testing to NodeJS, giving you a powerful, fast and ergonomic way to test Solana programs in TS/JS. +This package provides the `@solana/kit` API and delegates native execution to +`litesvm-core`. Install `litesvm-web3js` instead when using +`@solana/web3.js` v3. + For a standard testing workflow, LiteSVM offers an experience superior to `solana-test-validator` (slow, unwieldy) and `bankrun` (reasonably fast and powerful, but inherits a lot of warts from `solana-program-test`). diff --git a/crates/node-litesvm/build.rs b/crates/node-litesvm/build.rs deleted file mode 100644 index 9fc236788..000000000 --- a/crates/node-litesvm/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -extern crate napi_build; - -fn main() { - napi_build::setup(); -} diff --git a/crates/node-litesvm/litesvm/index.ts b/crates/node-litesvm/litesvm/index.ts index 32fc86825..296aa7742 100644 --- a/crates/node-litesvm/litesvm/index.ts +++ b/crates/node-litesvm/litesvm/index.ts @@ -34,7 +34,7 @@ import { SlotHistory, StakeHistory, TransactionMetadata, -} from "./internal"; +} from "litesvm-core"; export { Account, Clock, @@ -52,7 +52,7 @@ export { StakeHistoryEntry, TransactionMetadata, TransactionReturnData, -} from "./internal"; +} from "litesvm-core"; function toEncodedAccount(address: Address, account: Account): EncodedAccount { const data = account.data(); diff --git a/crates/node-litesvm/litesvm/internal.d.ts b/crates/node-litesvm/litesvm/internal.d.ts deleted file mode 100644 index d18fb2902..000000000 --- a/crates/node-litesvm/litesvm/internal.d.ts +++ /dev/null @@ -1,636 +0,0 @@ -/* auto-generated by NAPI-RS */ -/* eslint-disable */ -export declare class Account { - constructor(lamports: bigint, data: Uint8Array, owner: Uint8Array, executable: boolean, rentEpoch: bigint) - lamports(): bigint - data(): Uint8Array - owner(): Uint8Array - executable(): boolean - rentEpoch(): bigint - toString(): string -} - -export declare class AddressAndAccount { - address: Uint8Array - account(): Account -} - -/** - * A representation of network time. - * - * All members of `Clock` start from 0 upon network boot. - */ -export declare class Clock { - /** - * @param slot - The current Slot. - * @param epochStartTimestamp - The timestamp of the first `Slot` in this `Epoch`. - * @param epoch - The current epoch. - * @param leaderScheduleEpoch - The future Epoch for which the leader schedule has most recently been calculated. - * @param unixTimestamp - The approximate real world time of the current slot. - */ - constructor(slot: bigint, epochStartTimestamp: bigint, epoch: bigint, leaderScheduleEpoch: bigint, unixTimestamp: bigint) - /** The current Slot. */ - get slot(): bigint - set slot(val: bigint) - /** The current epoch. */ - get epoch(): bigint - set epoch(val: bigint) - /** The timestamp of the first `Slot` in this `Epoch`. */ - get epochStartTimestamp(): bigint - set epochStartTimestamp(val: bigint) - /** The future Epoch for which the leader schedule has most recently been calculated. */ - get leaderScheduleEpoch(): bigint - set leaderScheduleEpoch(val: bigint) - /** The approximate real world time of the current slot. */ - get unixTimestamp(): bigint - set unixTimestamp(val: bigint) - toString(): string -} - -export declare class CompiledInstruction { - constructor(programIdIndex: number, accounts: Uint8Array, data: Uint8Array) - programIdIndex(): number - accounts(): Uint8Array - data(): Uint8Array - toString(): string -} - -export declare class ComputeBudget { - constructor() - get computeUnitLimit(): bigint - set computeUnitLimit(limit: bigint) - set log64Units(val: bigint) - get log64Units(): bigint - set createProgramAddressUnits(val: bigint) - get createProgramAddressUnits(): bigint - set invokeUnits(val: bigint) - get invokeUnits(): bigint - set maxInstructionStackDepth(val: bigint) - get maxInstructionStackDepth(): bigint - set maxInstructionTraceLength(val: bigint) - get maxInstructionTraceLength(): bigint - set sha256BaseCost(val: bigint) - get sha256BaseCost(): bigint - set sha256ByteCost(val: bigint) - get sha256ByteCost(): bigint - set sha256MaxSlices(val: bigint) - get sha256MaxSlices(): bigint - set maxCallDepth(val: bigint) - get maxCallDepth(): bigint - set stackFrameSize(val: bigint) - get stackFrameSize(): bigint - set logPubkeyUnits(val: bigint) - get logPubkeyUnits(): bigint - set cpiBytesPerUnit(val: bigint) - get cpiBytesPerUnit(): bigint - set sysvarBaseCost(val: bigint) - get sysvarBaseCost(): bigint - set secp256K1RecoverCost(val: bigint) - get secp256K1RecoverCost(): bigint - set syscallBaseCost(val: bigint) - get syscallBaseCost(): bigint - set curve25519EdwardsValidatePointCost(val: bigint) - get curve25519EdwardsValidatePointCost(): bigint - set curve25519EdwardsAddCost(val: bigint) - get curve25519EdwardsAddCost(): bigint - set curve25519EdwardsSubtractCost(val: bigint) - get curve25519EdwardsSubtractCost(): bigint - set curve25519EdwardsMultiplyCost(val: bigint) - get curve25519EdwardsMultiplyCost(): bigint - set curve25519EdwardsMsmBaseCost(val: bigint) - get curve25519EdwardsMsmBaseCost(): bigint - set curve25519EdwardsMsmIncrementalCost(val: bigint) - get curve25519EdwardsMsmIncrementalCost(): bigint - set curve25519RistrettoValidatePointCost(val: bigint) - get curve25519RistrettoValidatePointCost(): bigint - set curve25519RistrettoAddCost(val: bigint) - get curve25519RistrettoAddCost(): bigint - set curve25519RistrettoSubtractCost(val: bigint) - get curve25519RistrettoSubtractCost(): bigint - set curve25519RistrettoMultiplyCost(val: bigint) - get curve25519RistrettoMultiplyCost(): bigint - set curve25519RistrettoMsmBaseCost(val: bigint) - get curve25519RistrettoMsmBaseCost(): bigint - set curve25519RistrettoMsmIncrementalCost(val: bigint) - get curve25519RistrettoMsmIncrementalCost(): bigint - set heapSize(val: number) - get heapSize(): number - set heapCost(val: bigint) - get heapCost(): bigint - set memOpBaseCost(val: bigint) - get memOpBaseCost(): bigint - set altBn128AdditionCost(val: bigint) - get altBn128AdditionCost(): bigint - set altBn128MultiplicationCost(val: bigint) - get altBn128MultiplicationCost(): bigint - set altBn128PairingOnePairCostFirst(val: bigint) - get altBn128PairingOnePairCostFirst(): bigint - set altBn128PairingOnePairCostOther(val: bigint) - get altBn128PairingOnePairCostOther(): bigint - set bigModularExponentiationBaseCost(val: bigint) - get bigModularExponentiationBaseCost(): bigint - set bigModularExponentiationCostDivisor(val: bigint) - get bigModularExponentiationCostDivisor(): bigint - set poseidonCostCoefficientA(val: bigint) - get poseidonCostCoefficientA(): bigint - set poseidonCostCoefficientC(val: bigint) - get poseidonCostCoefficientC(): bigint - set getRemainingComputeUnitsCost(val: bigint) - get getRemainingComputeUnitsCost(): bigint - set altBn128G1Compress(val: bigint) - get altBn128G1Compress(): bigint - set altBn128G1Decompress(val: bigint) - get altBn128G1Decompress(): bigint - set altBn128G2Compress(val: bigint) - get altBn128G2Compress(): bigint - set altBn128G2Decompress(val: bigint) - get altBn128G2Decompress(): bigint -} - -/** A type to hold data for the EpochRewards sysvar. */ -export declare class EpochRewards { - /** - * @param distribution_starting_block_height - The starting block height of the rewards distribution in the current epoch - * @param num_partitions - Number of partitions in the rewards distribution in the current epoch - * @param parent_blockhash - The blockhash of the parent block of the first block in the epoch - * @param total_points - The total rewards points calculated for the current epoch - * @param total_rewards - The total rewards calculated for the current epoch - * @param distributed_rewards - The rewards currently distributed for the current epoch, in lamports - * @param active - Whether the rewards period (including calculation and distribution) is active - */ - constructor(distributionStartingBlockHeight: bigint, numPartitions: bigint, parentBlockhash: string, totalPoints: bigint, totalRewards: bigint, distributedRewards: bigint, active: boolean) - /** - * The starting block height of the rewards distribution in the current - * epoch - */ - get distributionStartingBlockHeight(): bigint - set distributionStartingBlockHeight(val: bigint) - /** - * Number of partitions in the rewards distribution in the current epoch, - * used to generate an EpochRewardsHasher - */ - get numPartitions(): bigint - set numPartitions(val: bigint) - /** - * The blockhash of the parent block of the first block in the epoch, used - * to seed an EpochRewardsHasher - */ - get parentBlockhash(): string - set parentBlockhash(val: string) - /** - * The total rewards points calculated for the current epoch, where points - * equals the sum of (delegated stake * credits observed) for all - * delegations - */ - get totalPoints(): bigint - set totalPoints(val: bigint) - /** - * The total rewards calculated for the current epoch. This may be greater - * than the total `distributed_rewards` at the end of the rewards period, - * due to rounding and inability to deliver rewards smaller than 1 lamport. - */ - get totalRewards(): bigint - set totalRewards(val: bigint) - /** The rewards currently distributed for the current epoch, in lamports */ - get distributedRewards(): bigint - set distributedRewards(val: bigint) - /** - * Whether the rewards period (including calculation and distribution) is - * active - */ - get active(): boolean - set active(val: boolean) -} - -/** Configuration for epochs and slots. */ -export declare class EpochSchedule { - /** - * @param slots_per_epoch - The maximum number of slots in each epoch. - * @param leader_schedule_slot_offset - A number of slots before beginning of an epoch to calculate a leader schedule for that epoch. - * @param warmup - Whether epochs start short and grow. - * @param first_normal_epoch - The first epoch after the warmup period. - * @param first_normal_slot - The first slot after the warmup period. - */ - constructor(slotsPerEpoch: bigint, leaderScheduleSlotOffset: bigint, warmup: boolean, firstNormalEpoch: bigint, firstNormalSlot: bigint) - /** The maximum number of slots in each epoch. */ - get slotsPerEpoch(): bigint - set slotsPerEpoch(val: bigint) - /** - * A number of slots before beginning of an epoch to calculate - * a leader schedule for that epoch. - */ - get leaderScheduleSlotOffset(): bigint - set leaderScheduleSlotOffset(val: bigint) - /** Whether epochs start short and grow. */ - get warmup(): boolean - set warmup(val: boolean) - /** - * The first epoch after the warmup period. - * - * Basically: `log2(slots_per_epoch) - log2(MINIMUM_SLOTS_PER_EPOCH)`. - */ - get firstNormalEpoch(): bigint - set firstNormalEpoch(val: bigint) - /** - * The first slot after the warmup period. - * - * Basically: `MINIMUM_SLOTS_PER_EPOCH * (2.pow(first_normal_epoch) - 1)`. - */ - get firstNormalSlot(): bigint - set firstNormalSlot(val: bigint) -} - -export declare class FailedTransactionMetadata { - err(): TransactionErrorFieldless | TransactionErrorInstructionError | TransactionErrorDuplicateInstruction | TransactionErrorInsufficientFundsForRent | TransactionErrorProgramExecutionTemporarilyRestricted - meta(): TransactionMetadata - toString(): string -} - -export declare class FeatureSet { - constructor() - static allEnabled(): FeatureSet - isActive(featureId: Uint8Array): boolean - activatedSlot(featureId: Uint8Array): bigint | null - activate(featureId: Uint8Array, slot: bigint): void - deactivate(featureId: Uint8Array): void - getActiveFeatures(): Array - getInactiveFeatures(): Array - getActiveFeaturesCount(): number - getInactiveFeaturesCount(): number - toString(): string -} - -export declare class InnerInstruction { - instruction(): CompiledInstruction - stackHeight(): number - toString(): string -} - -export declare class InstructionErrorBorshIo { - msg: string - toString(): string -} -export type InstructionErrorBorshIO = InstructionErrorBorshIo - -export declare class InstructionErrorCustom { - code: number - toString(): string -} - -export declare class LiteSvm { - /** Creates the basic test environment. */ - constructor() - static default(): LiteSvm - setComputeBudget(budget: ComputeBudget): void - /** Enables or disables sigverify */ - setSigverify(sigverify: boolean): void - /** Enables or disables the blockhash check */ - setBlockhashCheck(check: boolean): void - /** Includes the default sysvars */ - setSysvars(): void - /** Changes the default builtins */ - setFeatureSet(featureSet: FeatureSet): void - /** Changes the default builtins */ - setBuiltins(): void - /** Changes the initial lamports in LiteSVM's airdrop account */ - setLamports(lamports: bigint): void - /** Includes the standard SPL programs */ - setDefaultPrograms(): void - /** - * Changes the capacity of the transaction history. - * Set this to 0 to disable transaction history and allow duplicate transactions. - */ - setTransactionHistory(capacity: bigint): void - setLogBytesLimit(limit?: bigint | undefined | null): void - setPrecompiles(): void - /** Returns minimum balance required to make an account with specified data length rent exempt. */ - minimumBalanceForRentExemption(dataLen: bigint): bigint - /** Returns all information associated with the account of the provided pubkey. */ - getAccount(pubkey: Uint8Array): Account | null - /** Returns all accounts owned by the given program, together with their addresses. */ - getProgramAccounts(programId: Uint8Array): Array - /** Sets all information associated with the account of the provided pubkey. */ - setAccount(pubkey: Uint8Array, data: Account): void - /** Gets the balance of the provided account pubkey. */ - getBalance(pubkey: Uint8Array): bigint | null - /** Gets the latest blockhash. */ - latestBlockhash(): string - /** Gets a transaction from the transaction history. */ - getTransaction(signature: Uint8Array): TransactionMetadata | FailedTransactionMetadata | null - /** Airdrops the account with the lamports specified. */ - airdrop(pubkey: Uint8Array, lamports: bigint): TransactionMetadata | FailedTransactionMetadata | null - /** Adds am SBF program to the test environment from the file specified. */ - addProgramFromFile(programId: Uint8Array, path: string): void - /** Adds am SBF program to the test environment. */ - addProgram(programId: Uint8Array, programBytes: Uint8Array): void - /** Adds an SBF program with a specific loader. */ - addProgramWithLoader(programId: Uint8Array, programBytes: Uint8Array, loaderId: Uint8Array): void - sendLegacyTransaction(txBytes: Uint8Array): TransactionMetadata | FailedTransactionMetadata - sendVersionedTransaction(txBytes: Uint8Array): TransactionMetadata | FailedTransactionMetadata - simulateLegacyTransaction(txBytes: Uint8Array): SimulatedTransactionInfo | FailedTransactionMetadata - simulateVersionedTransaction(txBytes: Uint8Array): SimulatedTransactionInfo | FailedTransactionMetadata - /** Expires the current blockhash */ - expireBlockhash(): void - /** Warps the clock to the specified slot */ - warpToSlot(slot: bigint): void - getComputeBudget(): ComputeBudget | null - getSigverify(): boolean - getClock(): Clock - setClock(clock: Clock): void - getRent(): Rent - setRent(rent: Rent): void - getEpochRewards(): EpochRewards - setEpochRewards(rewards: EpochRewards): void - getEpochSchedule(): EpochSchedule - setEpochSchedule(schedule: EpochSchedule): void - getLastRestartSlot(): bigint - setLastRestartSlot(slot: bigint): void - getSlotHashes(): Array - setSlotHashes(hashes: Array): void - getSlotHistory(): SlotHistory - setSlotHistory(history: SlotHistory): void - getStakeHistory(): StakeHistory - setStakeHistory(history: StakeHistory): void - withNativeMints(): void -} - -/** Configuration of network rent. */ -export declare class Rent { - /** - * @param lamportsPerByteYear - Rental rate in lamports/byte-year. - * @param exemptionThreshold - Amount of time (in years) a balance must include rent for the account to be rent exempt. - * @param burnPercent - The percentage of collected rent that is burned. - */ - constructor(lamportsPerByteYear: bigint, exemptionThreshold: number, burnPercent: number) - /** Initialize rent with the default Solana settings. */ - static default(): Rent - /** - * Rental rate in lamports/byte-year. - * - * Note: since SIMD-0194 the underlying sysvar stores lamports/byte with - * the exemption threshold folded in (default 6960, threshold 1.0), so on - * current clusters this returns double the historical 3480 value. - */ - get lamportsPerByteYear(): bigint - set lamportsPerByteYear(val: bigint) - /** Amount of time (in years) a balance must include rent for the account to be rent exempt. */ - get exemptionThreshold(): number - set exemptionThreshold(val: number) - /** The percentage of collected rent that is burned. */ - get burnPercent(): number - set burnPercent(val: number) - /** - * Calculate how much rent to burn from the collected rent. - * - * The first value returned is the amount burned. The second is the amount - * to distribute to validators. - * - * @param rentCollected: The amount of rent collected. - * @returns The amount burned and the amount to distribute to validators. - */ - calculateBurn(rentCollected: bigint): [bigint, bigint] - /** - * Minimum balance due for rent-exemption of a given account data size. - * - * Note: a stripped-down version of this calculation is used in - * ``calculate_split_rent_exempt_reserve`` in the stake program. When this - * function is updated, eg. when making rent variable, the stake program - * will need to be refactored. - * - * @param dataLen - The account data size. - * @returns The minimum balance due. - */ - minimumBalance(dataLen: bigint): bigint - /** Whether a given balance and data length would be exempt. */ - isExempt(balance: bigint, dataLen: bigint): boolean - /** - * Rent due on account's data length with balance. - * - * @param balance - The account balance. - * @param dataLen - The account data length. - * @param yearsElapsed - Time elapsed in years. - * @returns The rent due. - */ - due(balance: bigint, dataLen: bigint, yearsElapsed: number): bigint | null - /** - * Rent due for account that is known to be not exempt. - * - * Note: since SIMD-0194 the underlying rate is lamports/byte with the - * exemption threshold folded in, so on current clusters this returns - * double the pre-SIMD-0194 amount for the same inputs. - * - * @param dataLen - The account data length. - * @param yearsElapsed - Time elapsed in years. - * @returns The amount due. - */ - dueAmount(dataLen: bigint, yearsElapsed: number): bigint - /** - * Creates a `Rent` that charges no lamports. - * - * This is used for testing. - */ - static free(): Rent - /** - * Creates a `Rent` that is scaled based on the number of slots in an epoch. - * - * This is used for testing. - * - * @deprecated Epoch-based rent scaling was removed upstream (SIMD-0194); - * the argument is ignored and the default rent is returned. The old - * scaling never affected the rent-exempt minimum balance. - */ - static withSlotsPerEpoch(slotsPerEpoch: bigint): Rent - toString(): string -} - -export declare class SimulatedTransactionInfo { - meta(): TransactionMetadata - postAccounts(): Array - toString(): string -} - -export declare class SlotHash { - get slot(): bigint - get hash(): string - set slot(slot: bigint) - set hash(hash: string) -} - -/** A bitvector indicating which slots are present in the past epoch. */ -export declare class SlotHistory { - constructor(bits: BigUint64Array, nextSlot: bigint) - static default(): SlotHistory - get bits(): BigUint64Array - set bits(bits: BigUint64Array) - get nextSlot(): bigint - set nextSlot(slot: bigint) - add(slot: bigint): void - check(slot: bigint): SlotHistoryCheck - oldest(): bigint - newest(): bigint - toString(): string -} - -/** A type to hold data for the StakeHistory sysvar. */ -export declare class StakeHistory { - constructor() - get(epoch: bigint): StakeHistoryEntry | null - add(epoch: bigint, entry: StakeHistoryEntry): void -} - -export declare class StakeHistoryEntry { - /** - * @param effective - effective stake at this epoch - * @param activating - sum of portion of stakes not fully warmed up - * @param effective - requested to be cooled down, not fully deactivated yet - */ - constructor(effective: bigint, activating: bigint, deactivating: bigint) - /** effective stake at this epoch */ - get effective(): bigint - set effective(val: bigint) - /** sum of portion of stakes not fully warmed up */ - get activating(): bigint - set activating(val: bigint) - /** requested to be cooled down, not fully deactivated yet */ - get deactivating(): bigint - set deactivating(val: bigint) - toString(): string -} - -export declare class TransactionErrorDuplicateInstruction { - index: number - toString(): string -} - -export declare class TransactionErrorInstructionError { - index: number - err(): InstructionErrorFieldless | InstructionErrorCustom | InstructionErrorBorshIO - toString(): string -} - -export declare class TransactionErrorInsufficientFundsForRent { - accountIndex: number - toString(): string -} - -export declare class TransactionErrorProgramExecutionTemporarilyRestricted { - accountIndex: number - toString(): string -} - -export declare class TransactionMetadata { - signature(): Uint8Array - logs(): Array - innerInstructions(): Array> - computeUnitsConsumed(): bigint - returnData(): TransactionReturnData - prettyLogs(): string - toString(): string -} - -export declare class TransactionReturnData { - programId(): Uint8Array - data(): Uint8Array - toString(): string -} - -export declare const enum InstructionErrorFieldless { - GenericError = 0, - InvalidArgument = 1, - InvalidInstructionData = 2, - InvalidAccountData = 3, - AccountDataTooSmall = 4, - InsufficientFunds = 5, - IncorrectProgramId = 6, - MissingRequiredSignature = 7, - AccountAlreadyInitialized = 8, - UninitializedAccount = 9, - UnbalancedInstruction = 10, - ModifiedProgramId = 11, - ExternalAccountLamportSpend = 12, - ExternalAccountDataModified = 13, - ReadonlyLamportChange = 14, - ReadonlyDataModified = 15, - DuplicateAccountIndex = 16, - ExecutableModified = 17, - RentEpochModified = 18, - NotEnoughAccountKeys = 19, - AccountDataSizeChanged = 20, - AccountNotExecutable = 21, - AccountBorrowFailed = 22, - AccountBorrowOutstanding = 23, - DuplicateAccountOutOfSync = 24, - InvalidError = 25, - ExecutableDataModified = 26, - ExecutableLamportChange = 27, - ExecutableAccountNotRentExempt = 28, - UnsupportedProgramId = 29, - CallDepth = 30, - MissingAccount = 31, - ReentrancyNotAllowed = 32, - MaxSeedLengthExceeded = 33, - InvalidSeeds = 34, - InvalidRealloc = 35, - ComputationalBudgetExceeded = 36, - PrivilegeEscalation = 37, - ProgramEnvironmentSetupFailure = 38, - ProgramFailedToComplete = 39, - ProgramFailedToCompile = 40, - Immutable = 41, - IncorrectAuthority = 42, - AccountNotRentExempt = 43, - InvalidAccountOwner = 44, - ArithmeticOverflow = 45, - UnsupportedSysvar = 46, - IllegalOwner = 47, - MaxAccountsDataAllocationsExceeded = 48, - MaxAccountsExceeded = 49, - MaxInstructionTraceLengthExceeded = 50, - BuiltinProgramsMustConsumeComputeUnits = 51, - BorshIoError = 52 -} - -export declare const enum SlotHistoryCheck { - Future = 0, - TooOld = 1, - Found = 2, - NotFound = 3 -} - -export declare const enum TransactionErrorFieldless { - AccountInUse = 0, - AccountLoadedTwice = 1, - AccountNotFound = 2, - ProgramAccountNotFound = 3, - InsufficientFundsForFee = 4, - InvalidAccountForFee = 5, - AlreadyProcessed = 6, - BlockhashNotFound = 7, - CallChainTooDeep = 8, - MissingSignatureForFee = 9, - InvalidAccountIndex = 10, - SignatureFailure = 11, - InvalidProgramForExecution = 12, - SanitizeFailure = 13, - ClusterMaintenance = 14, - AccountBorrowOutstanding = 15, - WouldExceedMaxBlockCostLimit = 16, - UnsupportedVersion = 17, - InvalidWritableAccount = 18, - WouldExceedMaxAccountCostLimit = 19, - WouldExceedAccountDataBlockLimit = 20, - TooManyAccountLocks = 21, - AddressLookupTableNotFound = 22, - InvalidAddressLookupTableOwner = 23, - InvalidAddressLookupTableData = 24, - InvalidAddressLookupTableIndex = 25, - InvalidRentPayingAccount = 26, - WouldExceedMaxVoteCostLimit = 27, - WouldExceedAccountDataTotalLimit = 28, - MaxLoadedAccountsDataSizeExceeded = 29, - ResanitizationNeeded = 30, - InvalidLoadedAccountsDataSizeLimit = 31, - UnbalancedTransaction = 32, - ProgramCacheHitMaxLimit = 33, - CommitCancelled = 34 -} diff --git a/crates/node-litesvm/litesvm/internal.js b/crates/node-litesvm/litesvm/internal.js deleted file mode 100644 index b27ecce77..000000000 --- a/crates/node-litesvm/litesvm/internal.js +++ /dev/null @@ -1,618 +0,0 @@ -// prettier-ignore -/* eslint-disable */ -// @ts-nocheck -/* auto-generated by NAPI-RS */ - -const { readFileSync } = require('fs') -let nativeBinding = null -const loadErrors = [] - -const isMusl = () => { - let musl = false - if (process.platform === 'linux') { - musl = isMuslFromFilesystem() - if (musl === null) { - musl = isMuslFromReport() - } - if (musl === null) { - musl = isMuslFromChildProcess() - } - } - return musl -} - -const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-') - -const isMuslFromFilesystem = () => { - try { - return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl') - } catch { - return null - } -} - -const isMuslFromReport = () => { - let report = null - if (process.report && typeof process.report.getReport === 'function') { - process.report.excludeNetwork = true - report = process.report.getReport() - } - if (!report) { - return null - } - if (report.header && report.header.glibcVersionRuntime) { - return false - } - if (Array.isArray(report.sharedObjects)) { - if (report.sharedObjects.some(isFileMusl)) { - return true - } - } - return false -} - -const isMuslFromChildProcess = () => { - try { - return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl') - } catch (e) { - // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false - return false - } -} - -function requireNative() { - if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) { - try { - return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); - } catch (err) { - loadErrors.push(err) - } - } else if (process.platform === 'android') { - if (process.arch === 'arm64') { - try { - return require('./litesvm.android-arm64.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-android-arm64') - const bindingPackageVersion = require('litesvm-android-arm64/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else if (process.arch === 'arm') { - try { - return require('./litesvm.android-arm-eabi.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-android-arm-eabi') - const bindingPackageVersion = require('litesvm-android-arm-eabi/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)) - } - } else if (process.platform === 'win32') { - if (process.arch === 'x64') { - if ((process.config && process.config.variables && process.config.variables.shlib_suffix === 'dll.a') || (process.config && process.config.variables && process.config.variables.node_target_type === 'shared_library')) { - try { - return require('./litesvm.win32-x64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-win32-x64-gnu') - const bindingPackageVersion = require('litesvm-win32-x64-gnu/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - try { - return require('./litesvm.win32-x64-msvc.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-win32-x64-msvc') - const bindingPackageVersion = require('litesvm-win32-x64-msvc/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } - } else if (process.arch === 'ia32') { - try { - return require('./litesvm.win32-ia32-msvc.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-win32-ia32-msvc') - const bindingPackageVersion = require('litesvm-win32-ia32-msvc/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else if (process.arch === 'arm64') { - try { - return require('./litesvm.win32-arm64-msvc.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-win32-arm64-msvc') - const bindingPackageVersion = require('litesvm-win32-arm64-msvc/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)) - } - } else if (process.platform === 'darwin') { - try { - return require('./litesvm.darwin-universal.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-darwin-universal') - const bindingPackageVersion = require('litesvm-darwin-universal/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - if (process.arch === 'x64') { - try { - return require('./litesvm.darwin-x64.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-darwin-x64') - const bindingPackageVersion = require('litesvm-darwin-x64/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else if (process.arch === 'arm64') { - try { - return require('./litesvm.darwin-arm64.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-darwin-arm64') - const bindingPackageVersion = require('litesvm-darwin-arm64/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)) - } - } else if (process.platform === 'freebsd') { - if (process.arch === 'x64') { - try { - return require('./litesvm.freebsd-x64.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-freebsd-x64') - const bindingPackageVersion = require('litesvm-freebsd-x64/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else if (process.arch === 'arm64') { - try { - return require('./litesvm.freebsd-arm64.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-freebsd-arm64') - const bindingPackageVersion = require('litesvm-freebsd-arm64/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)) - } - } else if (process.platform === 'linux') { - if (process.arch === 'x64') { - if (isMusl()) { - try { - return require('./litesvm.linux-x64-musl.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-x64-musl') - const bindingPackageVersion = require('litesvm-linux-x64-musl/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - try { - return require('./litesvm.linux-x64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-x64-gnu') - const bindingPackageVersion = require('litesvm-linux-x64-gnu/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } - } else if (process.arch === 'arm64') { - if (isMusl()) { - try { - return require('./litesvm.linux-arm64-musl.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-arm64-musl') - const bindingPackageVersion = require('litesvm-linux-arm64-musl/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - try { - return require('./litesvm.linux-arm64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-arm64-gnu') - const bindingPackageVersion = require('litesvm-linux-arm64-gnu/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } - } else if (process.arch === 'arm') { - if (isMusl()) { - try { - return require('./litesvm.linux-arm-musleabihf.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-arm-musleabihf') - const bindingPackageVersion = require('litesvm-linux-arm-musleabihf/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - try { - return require('./litesvm.linux-arm-gnueabihf.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-arm-gnueabihf') - const bindingPackageVersion = require('litesvm-linux-arm-gnueabihf/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } - } else if (process.arch === 'loong64') { - if (isMusl()) { - try { - return require('./litesvm.linux-loong64-musl.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-loong64-musl') - const bindingPackageVersion = require('litesvm-linux-loong64-musl/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - try { - return require('./litesvm.linux-loong64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-loong64-gnu') - const bindingPackageVersion = require('litesvm-linux-loong64-gnu/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } - } else if (process.arch === 'riscv64') { - if (isMusl()) { - try { - return require('./litesvm.linux-riscv64-musl.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-riscv64-musl') - const bindingPackageVersion = require('litesvm-linux-riscv64-musl/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - try { - return require('./litesvm.linux-riscv64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-riscv64-gnu') - const bindingPackageVersion = require('litesvm-linux-riscv64-gnu/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } - } else if (process.arch === 'ppc64') { - try { - return require('./litesvm.linux-ppc64-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-ppc64-gnu') - const bindingPackageVersion = require('litesvm-linux-ppc64-gnu/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else if (process.arch === 's390x') { - try { - return require('./litesvm.linux-s390x-gnu.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-linux-s390x-gnu') - const bindingPackageVersion = require('litesvm-linux-s390x-gnu/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)) - } - } else if (process.platform === 'openharmony') { - if (process.arch === 'arm64') { - try { - return require('./litesvm.openharmony-arm64.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-openharmony-arm64') - const bindingPackageVersion = require('litesvm-openharmony-arm64/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else if (process.arch === 'x64') { - try { - return require('./litesvm.openharmony-x64.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-openharmony-x64') - const bindingPackageVersion = require('litesvm-openharmony-x64/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else if (process.arch === 'arm') { - try { - return require('./litesvm.openharmony-arm.node') - } catch (e) { - loadErrors.push(e) - } - try { - const binding = require('litesvm-openharmony-arm') - const bindingPackageVersion = require('litesvm-openharmony-arm/package.json').version - if (bindingPackageVersion !== '1.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') { - throw new Error(`Native binding package version mismatch, expected 1.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) - } - return binding - } catch (e) { - loadErrors.push(e) - } - } else { - loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`)) - } - } else { - loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)) - } -} - -nativeBinding = requireNative() - -// NAPI_RS_FORCE_WASI is a tri-state flag: -// unset / any other value → native binding preferred, WASI is only a fallback -// 'true' → force WASI fallback even if native loaded -// 'error' → force WASI and throw if no WASI binding is found -// Treating any non-empty string as truthy (the historical behavior) meant -// NAPI_RS_FORCE_WASI=false, NAPI_RS_FORCE_WASI=0, etc. inadvertently triggered -// the WASI path, causing ENOENT for packages shipped without a .wasi.cjs file. -const forceWasi = - process.env.NAPI_RS_FORCE_WASI === 'true' || process.env.NAPI_RS_FORCE_WASI === 'error' - -if (!nativeBinding || forceWasi) { - let wasiBinding = null - let wasiBindingError = null - try { - wasiBinding = require('./litesvm.wasi.cjs') - nativeBinding = wasiBinding - } catch (err) { - if (forceWasi) { - wasiBindingError = err - } - } - if (!nativeBinding || forceWasi) { - try { - wasiBinding = require('litesvm-wasm32-wasi') - nativeBinding = wasiBinding - } catch (err) { - if (forceWasi) { - if (!wasiBindingError) { - wasiBindingError = err - } else { - wasiBindingError.cause = err - } - loadErrors.push(err) - } - } - } - if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) { - const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error') - error.cause = wasiBindingError - throw error - } -} - -if (!nativeBinding) { - if (loadErrors.length > 0) { - const error = new Error( - `Cannot find native binding. ` + - `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` + - 'Please try `npm i` again after removing both package-lock.json and node_modules directory.', - ) - // assign instead of the `new Error(message, { cause })` options form, - // which Node < 16.9 silently ignores - error.cause = loadErrors.reduce((err, cur) => { - cur.cause = err - return cur - }) - throw error - } - throw new Error(`Failed to load native binding`) -} - -module.exports = nativeBinding -module.exports.Account = nativeBinding.Account -module.exports.AddressAndAccount = nativeBinding.AddressAndAccount -module.exports.Clock = nativeBinding.Clock -module.exports.CompiledInstruction = nativeBinding.CompiledInstruction -module.exports.ComputeBudget = nativeBinding.ComputeBudget -module.exports.EpochRewards = nativeBinding.EpochRewards -module.exports.EpochSchedule = nativeBinding.EpochSchedule -module.exports.FailedTransactionMetadata = nativeBinding.FailedTransactionMetadata -module.exports.FeatureSet = nativeBinding.FeatureSet -module.exports.InnerInstruction = nativeBinding.InnerInstruction -module.exports.InstructionErrorBorshIo = nativeBinding.InstructionErrorBorshIo -module.exports.InstructionErrorBorshIO = nativeBinding.InstructionErrorBorshIO -module.exports.InstructionErrorCustom = nativeBinding.InstructionErrorCustom -module.exports.LiteSvm = nativeBinding.LiteSvm -module.exports.Rent = nativeBinding.Rent -module.exports.SimulatedTransactionInfo = nativeBinding.SimulatedTransactionInfo -module.exports.SlotHash = nativeBinding.SlotHash -module.exports.SlotHistory = nativeBinding.SlotHistory -module.exports.StakeHistory = nativeBinding.StakeHistory -module.exports.StakeHistoryEntry = nativeBinding.StakeHistoryEntry -module.exports.TransactionErrorDuplicateInstruction = nativeBinding.TransactionErrorDuplicateInstruction -module.exports.TransactionErrorInstructionError = nativeBinding.TransactionErrorInstructionError -module.exports.TransactionErrorInsufficientFundsForRent = nativeBinding.TransactionErrorInsufficientFundsForRent -module.exports.TransactionErrorProgramExecutionTemporarilyRestricted = nativeBinding.TransactionErrorProgramExecutionTemporarilyRestricted -module.exports.TransactionMetadata = nativeBinding.TransactionMetadata -module.exports.TransactionReturnData = nativeBinding.TransactionReturnData -module.exports.InstructionErrorFieldless = nativeBinding.InstructionErrorFieldless -module.exports.SlotHistoryCheck = nativeBinding.SlotHistoryCheck -module.exports.TransactionErrorFieldless = nativeBinding.TransactionErrorFieldless diff --git a/crates/node-litesvm/package.json b/crates/node-litesvm/package.json index 9493f58f5..870c44fd8 100644 --- a/crates/node-litesvm/package.json +++ b/crates/node-litesvm/package.json @@ -6,26 +6,15 @@ "files": [ "dist/" ], - "napi": { - "binaryName": "litesvm", - "targets": [ - "x86_64-apple-darwin", - "x86_64-unknown-linux-gnu", - "x86_64-unknown-linux-musl", - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "aarch64-unknown-linux-musl" - ] - }, "license": "MIT", "dependencies": { "@solana-program/system": "^0.12.2", "@solana-program/token": "^0.14.0", - "@solana/kit": "^6.10.0" + "@solana/kit": "^6.10.0", + "litesvm-core": "workspace:^" }, "devDependencies": { "@algolia/client-search": "^5.55.0", - "@napi-rs/cli": "^3.7.2", "@types/markdown-it": "^14.1.2", "@types/node": "22.19.21", "@types/node-fetch": "^2.6.13", @@ -46,27 +35,21 @@ "node": ">= 20" }, "scripts": { - "artifacts": "napi artifacts", - "build": "napi build --platform --release --output-dir litesvm --dts internal.d.ts --js internal.js", - "build:debug": "napi build --platform litesvm --dts internal.d.ts --js internal.js", - "prepublishOnly": "napi prepublish -t npm", - "fmt": "yarn fmt:rust && yarn fmt:js", - "fmt:rust": "cargo fmt", + "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"", + "build": "yarn clean && tsc --build", + "prepublishOnly": "yarn build", + "fmt": "yarn fmt:js", "fmt:js": "rome format --write litesvm/index.ts tests/ tsconfig.json package.json", - "lint": "yarn lint:rust && yarn lint:js", - "lint:rust": "cargo clippy", + "lint": "yarn lint:js", "lint:js": "rome check litesvm/index.ts tests/ tsconfig.json package.json", "test": "node -r ts-node/register -r tsconfig-paths/register --max-old-space-size=4096 --test --test-concurrency=1 tests/*.test.ts", - "build:js": "tsc --build && cp litesvm/internal.d.ts dist/internal.d.ts", - "universal": "napi universal", - "cargoBump": "cargo set-version --package litesvm-node --bump", - "bumpPatch": "yarn version patch && napi version && cargo tree", - "bumpMinor": "yarn version minor && napi version && cargo tree", - "bumpMajor": "yarn version major && napi version && cargo tree", + "build:js": "yarn build", + "bumpPatch": "yarn version patch", + "bumpMinor": "yarn version minor", + "bumpMajor": "yarn version major", "predocs": "typedoc", "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs", "docs:preview": "vitepress preview docs" - }, - "packageManager": "yarn@4.10.3" -} \ No newline at end of file + } +} diff --git a/crates/node-litesvm/src/account.rs b/crates/node-litesvm/src/account.rs deleted file mode 100644 index 0c0672eab..000000000 --- a/crates/node-litesvm/src/account.rs +++ /dev/null @@ -1,64 +0,0 @@ -use { - crate::{to_string_js, util::bigint_to_u64}, - napi::bindgen_prelude::*, - solana_account::Account as AccountOriginal, - solana_address::Address, -}; - -#[derive(Debug, Clone)] -#[napi] -pub struct Account(pub(crate) AccountOriginal); - -impl AsRef for Account { - fn as_ref(&self) -> &AccountOriginal { - &self.0 - } -} - -#[napi] -impl Account { - #[napi(constructor)] - pub fn new( - lamports: BigInt, - data: Uint8Array, - owner: Uint8Array, - executable: bool, - rent_epoch: BigInt, - ) -> Result { - Ok(Self(AccountOriginal { - lamports: bigint_to_u64(&lamports)?, - data: data.to_vec(), - owner: Address::try_from(owner.as_ref()) - .map_err(|e| Error::from_reason(e.to_string()))?, - executable, - rent_epoch: bigint_to_u64(&rent_epoch)?, - })) - } - - #[napi] - pub fn lamports(&self) -> u64 { - self.0.lamports - } - - #[napi] - pub fn data(&self) -> Uint8Array { - Uint8Array::new(self.0.data.clone()) - } - - #[napi] - pub fn owner(&self) -> Uint8Array { - Uint8Array::new(self.0.owner.to_bytes().to_vec()) - } - - #[napi] - pub fn executable(&self) -> bool { - self.0.executable - } - - #[napi] - pub fn rent_epoch(&self) -> u64 { - self.0.rent_epoch - } -} - -to_string_js!(Account); diff --git a/crates/node-litesvm/src/compute_budget.rs b/crates/node-litesvm/src/compute_budget.rs deleted file mode 100644 index daaa4a5ed..000000000 --- a/crates/node-litesvm/src/compute_budget.rs +++ /dev/null @@ -1,371 +0,0 @@ -use { - crate::util::{bigint_to_u64, bigint_to_usize}, - napi::bindgen_prelude::*, - solana_compute_budget::compute_budget::ComputeBudget as ComputeBudgetOriginal, -}; - -#[napi] -pub struct ComputeBudget(pub(crate) ComputeBudgetOriginal); - -#[napi] -impl ComputeBudget { - #[napi(constructor)] - pub fn new() -> Self { - Self(ComputeBudgetOriginal::new_with_defaults(false)) - } - - #[napi(getter)] - pub fn compute_unit_limit(&self) -> u64 { - self.0.compute_unit_limit - } - - #[napi(setter)] - pub fn set_compute_unit_limit(&mut self, limit: BigInt) -> Result<()> { - Ok(self.0.compute_unit_limit = bigint_to_u64(&limit)?) - } - - #[napi(setter)] - pub fn set_log_64_units(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.log_64_units = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn log_64_units(&self) -> u64 { - self.0.log_64_units - } - #[napi(setter)] - pub fn set_create_program_address_units(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.create_program_address_units = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn create_program_address_units(&self) -> u64 { - self.0.create_program_address_units - } - #[napi(setter)] - pub fn set_invoke_units(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.invoke_units = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn invoke_units(&self) -> u64 { - self.0.invoke_units - } - #[napi(setter)] - pub fn set_max_instruction_stack_depth(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.max_instruction_stack_depth = bigint_to_usize(&val)?) - } - #[napi(getter)] - pub fn max_instruction_stack_depth(&self) -> usize { - self.0.max_instruction_stack_depth - } - #[napi(setter)] - pub fn set_max_instruction_trace_length(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.max_instruction_trace_length = bigint_to_usize(&val)?) - } - #[napi(getter)] - pub fn max_instruction_trace_length(&self) -> usize { - self.0.max_instruction_trace_length - } - #[napi(setter)] - pub fn set_sha256_base_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.sha256_base_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn sha256_base_cost(&self) -> u64 { - self.0.sha256_base_cost - } - #[napi(setter)] - pub fn set_sha256_byte_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.sha256_byte_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn sha256_byte_cost(&self) -> u64 { - self.0.sha256_byte_cost - } - #[napi(setter)] - pub fn set_sha256_max_slices(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.sha256_max_slices = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn sha256_max_slices(&self) -> u64 { - self.0.sha256_max_slices - } - #[napi(setter)] - pub fn set_max_call_depth(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.max_call_depth = bigint_to_usize(&val)?) - } - #[napi(getter)] - pub fn max_call_depth(&self) -> usize { - self.0.max_call_depth - } - #[napi(setter)] - pub fn set_stack_frame_size(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.stack_frame_size = bigint_to_usize(&val)?) - } - #[napi(getter)] - pub fn stack_frame_size(&self) -> usize { - self.0.stack_frame_size - } - #[napi(setter)] - pub fn set_log_pubkey_units(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.log_pubkey_units = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn log_pubkey_units(&self) -> u64 { - self.0.log_pubkey_units - } - #[napi(setter)] - pub fn set_cpi_bytes_per_unit(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.cpi_bytes_per_unit = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn cpi_bytes_per_unit(&self) -> u64 { - self.0.cpi_bytes_per_unit - } - #[napi(setter)] - pub fn set_sysvar_base_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.sysvar_base_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn sysvar_base_cost(&self) -> u64 { - self.0.sysvar_base_cost - } - #[napi(setter)] - pub fn set_secp256k1_recover_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.secp256k1_recover_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn secp256k1_recover_cost(&self) -> u64 { - self.0.secp256k1_recover_cost - } - #[napi(setter)] - pub fn set_syscall_base_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.syscall_base_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn syscall_base_cost(&self) -> u64 { - self.0.syscall_base_cost - } - #[napi(setter)] - pub fn set_curve25519_edwards_validate_point_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_edwards_validate_point_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_edwards_validate_point_cost(&self) -> u64 { - self.0.curve25519_edwards_validate_point_cost - } - #[napi(setter)] - pub fn set_curve25519_edwards_add_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_edwards_add_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_edwards_add_cost(&self) -> u64 { - self.0.curve25519_edwards_add_cost - } - #[napi(setter)] - pub fn set_curve25519_edwards_subtract_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_edwards_subtract_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_edwards_subtract_cost(&self) -> u64 { - self.0.curve25519_edwards_subtract_cost - } - #[napi(setter)] - pub fn set_curve25519_edwards_multiply_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_edwards_multiply_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_edwards_multiply_cost(&self) -> u64 { - self.0.curve25519_edwards_multiply_cost - } - #[napi(setter)] - pub fn set_curve25519_edwards_msm_base_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_edwards_msm_base_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_edwards_msm_base_cost(&self) -> u64 { - self.0.curve25519_edwards_msm_base_cost - } - #[napi(setter)] - pub fn set_curve25519_edwards_msm_incremental_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_edwards_msm_incremental_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_edwards_msm_incremental_cost(&self) -> u64 { - self.0.curve25519_edwards_msm_incremental_cost - } - #[napi(setter)] - pub fn set_curve25519_ristretto_validate_point_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_ristretto_validate_point_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_ristretto_validate_point_cost(&self) -> u64 { - self.0.curve25519_ristretto_validate_point_cost - } - #[napi(setter)] - pub fn set_curve25519_ristretto_add_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_ristretto_add_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_ristretto_add_cost(&self) -> u64 { - self.0.curve25519_ristretto_add_cost - } - #[napi(setter)] - pub fn set_curve25519_ristretto_subtract_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_ristretto_subtract_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_ristretto_subtract_cost(&self) -> u64 { - self.0.curve25519_ristretto_subtract_cost - } - #[napi(setter)] - pub fn set_curve25519_ristretto_multiply_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_ristretto_multiply_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_ristretto_multiply_cost(&self) -> u64 { - self.0.curve25519_ristretto_multiply_cost - } - #[napi(setter)] - pub fn set_curve25519_ristretto_msm_base_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_ristretto_msm_base_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_ristretto_msm_base_cost(&self) -> u64 { - self.0.curve25519_ristretto_msm_base_cost - } - #[napi(setter)] - pub fn set_curve25519_ristretto_msm_incremental_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.curve25519_ristretto_msm_incremental_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn curve25519_ristretto_msm_incremental_cost(&self) -> u64 { - self.0.curve25519_ristretto_msm_incremental_cost - } - #[napi(setter)] - pub fn set_heap_size(&mut self, val: u32) { - self.0.heap_size = val; - } - #[napi(getter)] - pub fn heap_size(&self) -> u32 { - self.0.heap_size - } - #[napi(setter)] - pub fn set_heap_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.heap_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn heap_cost(&self) -> u64 { - self.0.heap_cost - } - #[napi(setter)] - pub fn set_mem_op_base_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.mem_op_base_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn mem_op_base_cost(&self) -> u64 { - self.0.mem_op_base_cost - } - #[napi(setter)] - pub fn set_alt_bn128_addition_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.alt_bn128_g1_addition_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn alt_bn128_addition_cost(&self) -> u64 { - self.0.alt_bn128_g1_addition_cost - } - #[napi(setter)] - pub fn set_alt_bn128_multiplication_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.alt_bn128_g1_multiplication_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn alt_bn128_multiplication_cost(&self) -> u64 { - self.0.alt_bn128_g1_multiplication_cost - } - #[napi(setter)] - pub fn set_alt_bn128_pairing_one_pair_cost_first(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.alt_bn128_pairing_one_pair_cost_first = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn alt_bn128_pairing_one_pair_cost_first(&self) -> u64 { - self.0.alt_bn128_pairing_one_pair_cost_first - } - #[napi(setter)] - pub fn set_alt_bn128_pairing_one_pair_cost_other(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.alt_bn128_pairing_one_pair_cost_other = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn alt_bn128_pairing_one_pair_cost_other(&self) -> u64 { - self.0.alt_bn128_pairing_one_pair_cost_other - } - #[napi(setter)] - pub fn set_big_modular_exponentiation_base_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.big_modular_exponentiation_base_cost = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn big_modular_exponentiation_base_cost(&self) -> u64 { - self.0.big_modular_exponentiation_base_cost - } - #[napi(setter)] - pub fn set_big_modular_exponentiation_cost_divisor(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.big_modular_exponentiation_cost_divisor = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn big_modular_exponentiation_cost_divisor(&self) -> u64 { - self.0.big_modular_exponentiation_cost_divisor - } - #[napi(setter)] - pub fn set_poseidon_cost_coefficient_a(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.poseidon_cost_coefficient_a = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn poseidon_cost_coefficient_a(&self) -> u64 { - self.0.poseidon_cost_coefficient_a - } - #[napi(setter)] - pub fn set_poseidon_cost_coefficient_c(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.poseidon_cost_coefficient_c = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn poseidon_cost_coefficient_c(&self) -> u64 { - self.0.poseidon_cost_coefficient_c - } - #[napi(setter)] - pub fn set_get_remaining_compute_units_cost(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.get_remaining_compute_units_cost = bigint_to_u64(&val)?) - } - #[napi(getter, js_name = "getRemainingComputeUnitsCost")] - pub fn remaining_compute_units_cost(&self) -> u64 { - self.0.get_remaining_compute_units_cost - } - #[napi(setter)] - pub fn set_alt_bn128_g1_compress(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.alt_bn128_g1_compress = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn alt_bn128_g1_compress(&self) -> u64 { - self.0.alt_bn128_g1_compress - } - #[napi(setter)] - pub fn set_alt_bn128_g1_decompress(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.alt_bn128_g1_decompress = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn alt_bn128_g1_decompress(&self) -> u64 { - self.0.alt_bn128_g1_decompress - } - #[napi(setter)] - pub fn set_alt_bn128_g2_compress(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.alt_bn128_g2_compress = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn alt_bn128_g2_compress(&self) -> u64 { - self.0.alt_bn128_g2_compress - } - #[napi(setter)] - pub fn set_alt_bn128_g2_decompress(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.alt_bn128_g2_decompress = bigint_to_u64(&val)?) - } - #[napi(getter)] - pub fn alt_bn128_g2_decompress(&self) -> u64 { - self.0.alt_bn128_g2_decompress - } -} diff --git a/crates/node-litesvm/src/feature_set.rs b/crates/node-litesvm/src/feature_set.rs deleted file mode 100644 index 25a2ba742..000000000 --- a/crates/node-litesvm/src/feature_set.rs +++ /dev/null @@ -1,76 +0,0 @@ -use { - crate::{ - to_string_js, - util::{bigint_to_u64, convert_pubkey}, - }, - agave_feature_set::FeatureSet as FeatureSetOriginal, - napi::bindgen_prelude::*, -}; - -#[derive(Debug, Clone)] -#[napi] -pub struct FeatureSet(pub(crate) FeatureSetOriginal); - -#[napi] -impl FeatureSet { - #[napi(constructor)] - pub fn new() -> Self { - Self(FeatureSetOriginal::default()) - } - - #[napi(factory)] - pub fn all_enabled() -> Self { - Self(FeatureSetOriginal::all_enabled()) - } - - #[napi] - pub fn is_active(&self, feature_id: &[u8]) -> bool { - self.0.is_active(&convert_pubkey(feature_id)) - } - - #[napi] - pub fn activated_slot(&self, feature_id: &[u8]) -> Option { - self.0.activated_slot(&convert_pubkey(feature_id)) - } - - #[napi] - pub fn activate(&mut self, feature_id: &[u8], slot: BigInt) { - let slot_u64 = bigint_to_u64(&slot).unwrap_or(0); - self.0.activate(&convert_pubkey(feature_id), slot_u64); - } - - #[napi] - pub fn deactivate(&mut self, feature_id: &[u8]) { - self.0.deactivate(&convert_pubkey(feature_id)); - } - - #[napi] - pub fn get_active_features(&self) -> Vec { - self.0 - .active() - .keys() - .map(|pubkey| Buffer::from(pubkey.to_bytes().to_vec())) - .collect() - } - - #[napi] - pub fn get_inactive_features(&self) -> Vec { - self.0 - .inactive() - .iter() - .map(|pubkey| Buffer::from(pubkey.to_bytes().to_vec())) - .collect() - } - - #[napi] - pub fn get_active_features_count(&self) -> u32 { - self.0.active().len() as u32 - } - - #[napi] - pub fn get_inactive_features_count(&self) -> u32 { - self.0.inactive().len() as u32 - } -} - -to_string_js!(FeatureSet); diff --git a/crates/node-litesvm/src/lib.rs b/crates/node-litesvm/src/lib.rs deleted file mode 100644 index 8e0e9c425..000000000 --- a/crates/node-litesvm/src/lib.rs +++ /dev/null @@ -1,454 +0,0 @@ -#![deny(clippy::all)] -#![allow(clippy::new_without_default, clippy::unit_arg)] -use { - crate::{ - account::Account, - compute_budget::ComputeBudget, - feature_set::FeatureSet, - sysvar::{ - clock::Clock, epoch_rewards::EpochRewards, epoch_schedule::EpochSchedule, rent::Rent, - slot_hashes::SlotHash, slot_history::SlotHistory, stake_history::StakeHistory, - }, - transaction_metadata::{ - AddressAndAccount, FailedTransactionMetadata, SimulatedTransactionInfo, - TransactionMetadata, - }, - util::{convert_pubkey, try_parse_hash}, - }, - litesvm::{ - error::LiteSVMError, - types::{ - FailedTransactionMetadata as FailedTransactionMetadataOriginal, - SimulatedTransactionInfo as SimulatedTransactionInfoOriginal, - TransactionResult as TransactionResultOriginal, - }, - LiteSVM as LiteSVMOriginal, - }, - napi::bindgen_prelude::*, - solana_clock::Clock as ClockOriginal, - solana_epoch_rewards::EpochRewards as EpochRewardsOriginal, - solana_epoch_schedule::EpochSchedule as EpochScheduleOriginal, - solana_last_restart_slot::LastRestartSlot, - solana_rent::Rent as RentOriginal, - solana_signature::Signature, - solana_slot_hashes::SlotHashes, - solana_slot_history::SlotHistory as SlotHistoryOriginal, - solana_stake_history::StakeHistory as StakeHistoryOriginal, - solana_transaction::{versioned::VersionedTransaction, Transaction}, - util::{bigint_to_u64, bigint_to_usize}, - wincode::deserialize, -}; -mod account; -mod compute_budget; -mod feature_set; -mod native_mint; -mod sysvar; -mod transaction_error; -mod transaction_metadata; -mod util; - -#[macro_use] -extern crate napi_derive; - -fn to_js_error(e: LiteSVMError, msg: &str) -> Error { - Error::new(Status::GenericFailure, format!("{msg}: {e}")) -} - -#[macro_export] -macro_rules! to_string_js { - ($name:ident) => { - #[napi] - impl $name { - #[napi(js_name = "toString")] - pub fn js_to_string(&self) -> String { - format!("{self:?}") - } - } - }; -} - -pub type TransactionResult = Either; -pub type SimulateResult = Either; - -fn convert_transaction_result(inner: TransactionResultOriginal) -> TransactionResult { - match inner { - Ok(x) => TransactionResult::A(TransactionMetadata(x)), - Err(e) => TransactionResult::B(FailedTransactionMetadata(e)), - } -} - -fn convert_sim_result( - inner: std::result::Result, -) -> SimulateResult { - match inner { - Ok(x) => SimulateResult::A(SimulatedTransactionInfo(x)), - Err(e) => SimulateResult::B(FailedTransactionMetadata(e)), - } -} - -#[napi] -pub struct LiteSvm(LiteSVMOriginal); - -#[napi] -impl LiteSvm { - /// Creates the basic test environment. - #[napi(constructor)] - pub fn new() -> Self { - Self(LiteSVMOriginal::new()) - } - - #[napi(factory, js_name = "default")] - pub fn new_default() -> Self { - Self(LiteSVMOriginal::default()) - } - - #[napi] - pub fn set_compute_budget(&mut self, budget: &ComputeBudget) { - self.0.set_compute_budget(budget.0); - } - - #[napi] - /// Enables or disables sigverify - pub fn set_sigverify(&mut self, sigverify: bool) { - self.0.set_sigverify(sigverify); - } - - #[napi] - /// Enables or disables the blockhash check - pub fn set_blockhash_check(&mut self, check: bool) { - self.0.set_blockhash_check(check); - } - - #[napi] - /// Includes the default sysvars - pub fn set_sysvars(&mut self) { - self.0.set_sysvars() - } - - #[napi] - /// Changes the default builtins - pub fn set_feature_set(&mut self, feature_set: &FeatureSet) { - self.0.set_feature_set(feature_set.0.clone()); - } - - #[napi] - /// Changes the default builtins - pub fn set_builtins(&mut self) { - self.0.set_builtins(); - } - - #[napi] - /// Changes the initial lamports in LiteSVM's airdrop account - pub fn set_lamports(&mut self, lamports: BigInt) -> Result<()> { - Ok(self.0.set_lamports(bigint_to_u64(&lamports)?)) - } - - #[napi] - /// Includes the standard SPL programs - pub fn set_default_programs(&mut self) { - self.0.set_default_programs(); - } - - #[napi] - /// Changes the capacity of the transaction history. - /// Set this to 0 to disable transaction history and allow duplicate transactions. - pub fn set_transaction_history(&mut self, capacity: BigInt) -> Result<()> { - Ok(self.0.set_transaction_history(bigint_to_usize(&capacity)?)) - } - - #[napi] - pub fn set_log_bytes_limit(&mut self, limit: Option) -> Result<()> { - Ok(match limit { - None => self.0.set_log_bytes_limit(None), - Some(x) => { - let converted = bigint_to_usize(&x)?; - self.0.set_log_bytes_limit(Some(converted)) - } - }) - } - - #[napi] - pub fn set_precompiles(&mut self) { - self.0.set_precompiles(); - } - - #[napi] - /// Returns minimum balance required to make an account with specified data length rent exempt. - pub fn minimum_balance_for_rent_exemption(&self, data_len: BigInt) -> Result { - Ok(self - .0 - .minimum_balance_for_rent_exemption(bigint_to_usize(&data_len)?)) - } - - #[napi] - /// Returns all information associated with the account of the provided pubkey. - pub fn get_account(&self, pubkey: &[u8]) -> Option { - self.0.get_account(&convert_pubkey(pubkey)).map(Account) - } - - #[napi] - /// Returns all accounts owned by the given program, together with their addresses. - pub fn get_program_accounts(&self, program_id: &[u8]) -> Vec { - self.0 - .get_program_accounts(&convert_pubkey(program_id)) - .into_iter() - .map(|(address, account)| AddressAndAccount { - address: Uint8Array::with_data_copied(address.to_bytes()), - account: Account(account), - }) - .collect() - } - - #[napi] - /// Sets all information associated with the account of the provided pubkey. - pub fn set_account(&mut self, pubkey: &[u8], data: &Account) -> Result<()> { - self.0 - .set_account(convert_pubkey(pubkey), data.0.clone()) - .map_err(|e| to_js_error(e, "Failed to set account")) - } - - #[napi] - /// Gets the balance of the provided account pubkey. - pub fn get_balance(&self, pubkey: &[u8]) -> Option { - self.0.get_balance(&convert_pubkey(pubkey)) - } - - #[napi] - /// Gets the latest blockhash. - pub fn latest_blockhash(&self) -> String { - self.0.latest_blockhash().to_string() - } - - #[napi(ts_return_type = "TransactionMetadata | FailedTransactionMetadata | null")] - /// Gets a transaction from the transaction history. - pub fn get_transaction(&self, signature: &[u8]) -> Option { - self.0 - .get_transaction(&Signature::try_from(signature).unwrap()) - .map(|x| convert_transaction_result(x.clone())) - } - - #[napi(ts_return_type = "TransactionMetadata | FailedTransactionMetadata | null")] - /// Airdrops the account with the lamports specified. - pub fn airdrop(&mut self, pubkey: &[u8], lamports: BigInt) -> Result { - Ok(convert_transaction_result(self.0.airdrop( - &convert_pubkey(pubkey), - bigint_to_u64(&lamports)?, - ))) - } - - #[napi] - /// Adds am SBF program to the test environment from the file specified. - pub fn add_program_from_file(&mut self, program_id: &[u8], path: String) -> Result<()> { - self.0 - .add_program_from_file(convert_pubkey(program_id), path) - .map_err(|e| { - Error::new( - Status::GenericFailure, - format!("Failed to add program: {e}"), - ) - }) - } - - #[napi] - /// Adds am SBF program to the test environment. - pub fn add_program(&mut self, program_id: &[u8], program_bytes: &[u8]) -> Result<()> { - self.0 - .add_program(convert_pubkey(program_id), program_bytes) - .map_err(|e| { - Error::new( - Status::GenericFailure, - format!("Failed to add program: {e}"), - ) - }) - } - - #[napi] - /// Adds an SBF program with a specific loader. - pub fn add_program_with_loader( - &mut self, - program_id: &[u8], - program_bytes: &[u8], - loader_id: &[u8], - ) -> Result<()> { - self.0 - .add_program_with_loader( - convert_pubkey(program_id), - program_bytes, - convert_pubkey(loader_id), - ) - .map_err(|e| { - Error::new( - Status::GenericFailure, - format!("Failed to add program: {e}"), - ) - }) - } - - #[napi(ts_return_type = "TransactionMetadata | FailedTransactionMetadata")] - pub fn send_legacy_transaction(&mut self, tx_bytes: &[u8]) -> TransactionResult { - let tx: Transaction = deserialize(tx_bytes).unwrap(); - let res = self.0.send_transaction(tx); - convert_transaction_result(res) - } - - #[napi(ts_return_type = "TransactionMetadata | FailedTransactionMetadata")] - pub fn send_versioned_transaction(&mut self, tx_bytes: &[u8]) -> TransactionResult { - let tx: VersionedTransaction = deserialize(tx_bytes).unwrap(); - let res = self.0.send_transaction(tx); - convert_transaction_result(res) - } - - #[napi(ts_return_type = "SimulatedTransactionInfo | FailedTransactionMetadata")] - pub fn simulate_legacy_transaction(&mut self, tx_bytes: &[u8]) -> SimulateResult { - let tx: Transaction = deserialize(tx_bytes).unwrap(); - let res = self.0.simulate_transaction(tx); - convert_sim_result(res) - } - - #[napi(ts_return_type = "SimulatedTransactionInfo | FailedTransactionMetadata")] - pub fn simulate_versioned_transaction(&mut self, tx_bytes: &[u8]) -> SimulateResult { - let tx: VersionedTransaction = deserialize(tx_bytes).unwrap(); - let res = self.0.simulate_transaction(tx); - convert_sim_result(res) - } - - #[napi] - /// Expires the current blockhash - pub fn expire_blockhash(&mut self) { - self.0.expire_blockhash() - } - - #[napi] - /// Warps the clock to the specified slot - pub fn warp_to_slot(&mut self, slot: BigInt) -> Result<()> { - Ok(self.0.warp_to_slot(bigint_to_u64(&slot)?)) - } - - #[napi] - pub fn get_compute_budget(&self) -> Option { - self.0.get_compute_budget().map(ComputeBudget) - } - - #[napi] - pub fn get_sigverify(&self) -> bool { - self.0.get_sigverify() - } - - #[napi] - pub fn get_clock(&self) -> Clock { - Clock(self.0.get_sysvar::()) - } - - #[napi] - pub fn set_clock(&mut self, clock: &Clock) { - self.0.set_sysvar(&clock.0) - } - - #[napi] - pub fn get_rent(&self) -> Rent { - Rent(self.0.get_sysvar::()) - } - - #[napi] - pub fn set_rent(&mut self, rent: &Rent) { - self.0.set_sysvar(&rent.0) - } - - #[napi] - pub fn get_epoch_rewards(&self) -> EpochRewards { - EpochRewards(self.0.get_sysvar::()) - } - - #[napi] - pub fn set_epoch_rewards(&mut self, rewards: &EpochRewards) { - self.0.set_sysvar(&rewards.0) - } - - #[napi] - pub fn get_epoch_schedule(&self) -> EpochSchedule { - EpochSchedule(self.0.get_sysvar::()) - } - - #[napi] - pub fn set_epoch_schedule(&mut self, schedule: &EpochSchedule) { - self.0.set_sysvar(&schedule.0) - } - - #[napi] - pub fn get_last_restart_slot(&self) -> u64 { - self.0.get_sysvar::().last_restart_slot - } - - #[napi] - pub fn set_last_restart_slot(&mut self, slot: BigInt) -> Result<()> { - Ok(self.0.set_sysvar::(&LastRestartSlot { - last_restart_slot: bigint_to_u64(&slot)?, - })) - } - - #[napi] - pub fn get_slot_hashes(&self) -> Vec { - let fetched = self.0.get_sysvar::(); - fetched - .slot_hashes() - .iter() - .map(|x| SlotHash { - slot: BigInt::from(x.0), - hash: x.1.to_string(), - }) - .collect() - } - - #[napi] - pub fn set_slot_hashes(&mut self, hashes: Vec<&SlotHash>) -> Result<()> { - let mut intermediate: Vec<(u64, solana_hash::Hash)> = Vec::with_capacity(hashes.len()); - for h in hashes { - let converted_hash = try_parse_hash(&h.hash)?; - intermediate.push((bigint_to_u64(&h.slot)?, converted_hash)); - } - let converted = SlotHashes::from_iter(intermediate); - self.0.set_sysvar::(&converted); - Ok(()) - } - - #[napi] - pub fn get_slot_history(&self) -> SlotHistory { - SlotHistory(self.0.get_sysvar::()) - } - - #[napi] - pub fn set_slot_history(&mut self, history: &SlotHistory) { - self.0.set_sysvar::(&history.0) - } - - #[napi] - pub fn get_stake_history(&self) -> StakeHistory { - StakeHistory(self.0.get_sysvar::()) - } - - #[napi] - pub fn set_stake_history(&mut self, history: &StakeHistory) { - self.0.set_sysvar::(&history.0) - } - - #[napi] - pub fn with_native_mints(&mut self) { - if self - .0 - .accounts_db() - .inner - .contains_key(&native_mint::inline_spl::SPL_TOKEN_PROGRAM_ID) - { - native_mint::create_native_mint(&mut self.0); - } - - if self - .0 - .accounts_db() - .inner - .contains_key(&native_mint::inline_spl::SPL_TOKEN_2022_PROGRAM_ID) - { - native_mint::create_native_mint_2022(&mut self.0); - } - } -} diff --git a/crates/node-litesvm/src/native_mint.rs b/crates/node-litesvm/src/native_mint.rs deleted file mode 100644 index d4ef757f8..000000000 --- a/crates/node-litesvm/src/native_mint.rs +++ /dev/null @@ -1,43 +0,0 @@ -use {litesvm::LiteSVM, solana_account::Account, solana_address::Address, solana_rent::Rent}; - -// Avoid importing external dependencies -pub(crate) mod inline_spl { - use super::*; - - pub const SPL_TOKEN_PROGRAM_ID: Address = - Address::from_str_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); - pub const SPL_TOKEN_2022_PROGRAM_ID: Address = - Address::from_str_const("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); -} - -fn create_native_mint_with_program_id(svm: &mut LiteSVM, address: Address, token_program: Address) { - let account = Account { - lamports: svm.get_sysvar::().minimum_balance(82), - data: vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ], - owner: token_program, - executable: false, - rent_epoch: 0, - }; - - svm.set_account(address, account).unwrap(); -} - -pub fn create_native_mint(svm: &mut LiteSVM) { - create_native_mint_with_program_id( - svm, - Address::from_str_const("So11111111111111111111111111111111111111112"), - inline_spl::SPL_TOKEN_PROGRAM_ID, - ); -} - -pub fn create_native_mint_2022(svm: &mut LiteSVM) { - create_native_mint_with_program_id( - svm, - Address::from_str_const("9pan9bMn5HatX4EJdBwg9VgCa7Uz5HL8N1m5D3NdXejP"), - inline_spl::SPL_TOKEN_2022_PROGRAM_ID, - ); -} diff --git a/crates/node-litesvm/src/sysvar/clock.rs b/crates/node-litesvm/src/sysvar/clock.rs deleted file mode 100644 index 59e992e6e..000000000 --- a/crates/node-litesvm/src/sysvar/clock.rs +++ /dev/null @@ -1,97 +0,0 @@ -use { - crate::{ - to_string_js, - util::{bigint_to_i64, bigint_to_u64}, - }, - napi::bindgen_prelude::*, - solana_clock::Clock as ClockOriginal, -}; - -/// A representation of network time. -/// -/// All members of `Clock` start from 0 upon network boot. -#[derive(Debug)] -#[napi] -pub struct Clock(pub(crate) ClockOriginal); - -#[napi] -impl Clock { - /// @param slot - The current Slot. - /// @param epochStartTimestamp - The timestamp of the first `Slot` in this `Epoch`. - /// @param epoch - The current epoch. - /// @param leaderScheduleEpoch - The future Epoch for which the leader schedule has most recently been calculated. - /// @param unixTimestamp - The approximate real world time of the current slot. - #[napi(constructor)] - pub fn new( - slot: BigInt, - epoch_start_timestamp: BigInt, - epoch: BigInt, - leader_schedule_epoch: BigInt, - unix_timestamp: BigInt, - ) -> Result { - Ok(Self(ClockOriginal { - slot: bigint_to_u64(&slot)?, - epoch_start_timestamp: bigint_to_i64(&epoch_start_timestamp)?, - epoch: bigint_to_u64(&epoch)?, - leader_schedule_epoch: bigint_to_u64(&leader_schedule_epoch)?, - unix_timestamp: bigint_to_i64(&unix_timestamp)?, - })) - } - - /// The current Slot. - #[napi(getter)] - pub fn slot(&self) -> u64 { - self.0.slot - } - - #[napi(setter)] - pub fn set_slot(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.slot = bigint_to_u64(&val)?) - } - - /// The current epoch. - #[napi(getter)] - pub fn epoch(&self) -> u64 { - self.0.epoch - } - - #[napi(setter)] - pub fn set_epoch(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.epoch = bigint_to_u64(&val)?) - } - - /// The timestamp of the first `Slot` in this `Epoch`. - #[napi(getter)] - pub fn epoch_start_timestamp(&self) -> BigInt { - self.0.epoch_start_timestamp.into() - } - - #[napi(setter)] - pub fn set_epoch_start_timestamp(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.epoch_start_timestamp = bigint_to_i64(&val)?) - } - - /// The future Epoch for which the leader schedule has most recently been calculated. - #[napi(getter)] - pub fn leader_schedule_epoch(&self) -> u64 { - self.0.leader_schedule_epoch - } - - #[napi(setter)] - pub fn set_leader_schedule_epoch(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.leader_schedule_epoch = bigint_to_u64(&val)?) - } - - /// The approximate real world time of the current slot. - #[napi(getter)] - pub fn unix_timestamp(&self) -> BigInt { - self.0.unix_timestamp.into() - } - - #[napi(setter)] - pub fn set_unix_timestamp(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.unix_timestamp = bigint_to_i64(&val)?) - } -} - -to_string_js!(Clock); diff --git a/crates/node-litesvm/src/sysvar/epoch_rewards.rs b/crates/node-litesvm/src/sysvar/epoch_rewards.rs deleted file mode 100644 index 24ac97c14..000000000 --- a/crates/node-litesvm/src/sysvar/epoch_rewards.rs +++ /dev/null @@ -1,129 +0,0 @@ -use { - crate::util::{bigint_to_u128, bigint_to_u64, try_parse_hash}, - napi::bindgen_prelude::*, - solana_epoch_rewards::EpochRewards as EpochRewardsOriginal, -}; - -/// A type to hold data for the EpochRewards sysvar. -#[derive(Debug)] -#[napi] -pub struct EpochRewards(pub(crate) EpochRewardsOriginal); - -#[napi] -impl EpochRewards { - /// @param distribution_starting_block_height - The starting block height of the rewards distribution in the current epoch - /// @param num_partitions - Number of partitions in the rewards distribution in the current epoch - /// @param parent_blockhash - The blockhash of the parent block of the first block in the epoch - /// @param total_points - The total rewards points calculated for the current epoch - /// @param total_rewards - The total rewards calculated for the current epoch - /// @param distributed_rewards - The rewards currently distributed for the current epoch, in lamports - /// @param active - Whether the rewards period (including calculation and distribution) is active - #[napi(constructor)] - pub fn new( - distribution_starting_block_height: BigInt, - num_partitions: BigInt, - parent_blockhash: String, - total_points: BigInt, - total_rewards: BigInt, - distributed_rewards: BigInt, - active: bool, - ) -> Result { - let hash_parsed = try_parse_hash(&parent_blockhash)?; - Ok(Self(EpochRewardsOriginal { - distribution_starting_block_height: bigint_to_u64(&distribution_starting_block_height)?, - num_partitions: bigint_to_u64(&num_partitions)?, - parent_blockhash: hash_parsed, - total_points: bigint_to_u128(&total_points)?, - total_rewards: bigint_to_u64(&total_rewards)?, - distributed_rewards: bigint_to_u64(&distributed_rewards)?, - active, - })) - } - - /// The starting block height of the rewards distribution in the current - /// epoch - #[napi(getter)] - pub fn distribution_starting_block_height(&self) -> u64 { - self.0.distribution_starting_block_height - } - - #[napi(setter)] - pub fn set_distribution_starting_block_height(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.distribution_starting_block_height = bigint_to_u64(&val)?) - } - - /// Number of partitions in the rewards distribution in the current epoch, - /// used to generate an EpochRewardsHasher - #[napi(getter)] - pub fn num_partitions(&self) -> u64 { - self.0.num_partitions - } - - #[napi(setter)] - pub fn set_num_partitions(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.num_partitions = bigint_to_u64(&val)?) - } - - /// The blockhash of the parent block of the first block in the epoch, used - /// to seed an EpochRewardsHasher - #[napi(getter)] - pub fn parent_blockhash(&self) -> String { - self.0.parent_blockhash.to_string() - } - - #[napi(setter)] - pub fn set_parent_blockhash(&mut self, val: String) -> Result<()> { - let hash_parsed = try_parse_hash(&val)?; - self.0.parent_blockhash = hash_parsed; - Ok(()) - } - - /// The total rewards points calculated for the current epoch, where points - /// equals the sum of (delegated stake * credits observed) for all - /// delegations - #[napi(getter)] - pub fn total_points(&self) -> u128 { - self.0.total_points - } - - #[napi(setter)] - pub fn set_total_points(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.total_points = bigint_to_u128(&val)?) - } - - /// The total rewards calculated for the current epoch. This may be greater - /// than the total `distributed_rewards` at the end of the rewards period, - /// due to rounding and inability to deliver rewards smaller than 1 lamport. - #[napi(getter)] - pub fn total_rewards(&self) -> u64 { - self.0.total_rewards - } - - #[napi(setter)] - pub fn set_total_rewards(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.total_rewards = bigint_to_u64(&val)?) - } - - /// The rewards currently distributed for the current epoch, in lamports - #[napi(getter)] - pub fn distributed_rewards(&self) -> u64 { - self.0.distributed_rewards - } - - #[napi(setter)] - pub fn set_distributed_rewards(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.distributed_rewards = bigint_to_u64(&val)?) - } - - /// Whether the rewards period (including calculation and distribution) is - /// active - #[napi(getter)] - pub fn active(&self) -> bool { - self.0.active - } - - #[napi(setter)] - pub fn set_active(&mut self, val: bool) { - self.0.active = val; - } -} diff --git a/crates/node-litesvm/src/sysvar/epoch_schedule.rs b/crates/node-litesvm/src/sysvar/epoch_schedule.rs deleted file mode 100644 index 60fe29eb2..000000000 --- a/crates/node-litesvm/src/sysvar/epoch_schedule.rs +++ /dev/null @@ -1,94 +0,0 @@ -use { - crate::util::bigint_to_u64, napi::bindgen_prelude::*, - solana_epoch_schedule::EpochSchedule as EpochScheduleOriginal, -}; - -/// Configuration for epochs and slots. -#[derive(Debug)] -#[napi] -pub struct EpochSchedule(pub(crate) EpochScheduleOriginal); - -#[napi] -impl EpochSchedule { - /// @param slots_per_epoch - The maximum number of slots in each epoch. - /// @param leader_schedule_slot_offset - A number of slots before beginning of an epoch to calculate a leader schedule for that epoch. - /// @param warmup - Whether epochs start short and grow. - /// @param first_normal_epoch - The first epoch after the warmup period. - /// @param first_normal_slot - The first slot after the warmup period. - #[napi(constructor)] - pub fn new( - slots_per_epoch: BigInt, - leader_schedule_slot_offset: BigInt, - warmup: bool, - first_normal_epoch: BigInt, - first_normal_slot: BigInt, - ) -> Result { - Ok(Self(EpochScheduleOriginal { - slots_per_epoch: bigint_to_u64(&slots_per_epoch)?, - leader_schedule_slot_offset: bigint_to_u64(&leader_schedule_slot_offset)?, - warmup, - first_normal_epoch: bigint_to_u64(&first_normal_epoch)?, - first_normal_slot: bigint_to_u64(&first_normal_slot)?, - })) - } - - /// The maximum number of slots in each epoch. - #[napi(getter)] - pub fn slots_per_epoch(&self) -> u64 { - self.0.slots_per_epoch - } - - #[napi(setter)] - pub fn set_slots_per_epoch(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.slots_per_epoch = bigint_to_u64(&val)?) - } - - /// A number of slots before beginning of an epoch to calculate - /// a leader schedule for that epoch. - #[napi(getter)] - pub fn leader_schedule_slot_offset(&self) -> u64 { - self.0.leader_schedule_slot_offset - } - - #[napi(setter)] - pub fn set_leader_schedule_slot_offset(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.leader_schedule_slot_offset = bigint_to_u64(&val)?) - } - - /// Whether epochs start short and grow. - #[napi(getter)] - pub fn warmup(&self) -> bool { - self.0.warmup - } - - #[napi(setter)] - pub fn set_warmup(&mut self, val: bool) { - self.0.warmup = val; - } - - /// The first epoch after the warmup period. - /// - /// Basically: `log2(slots_per_epoch) - log2(MINIMUM_SLOTS_PER_EPOCH)`. - #[napi(getter)] - pub fn first_normal_epoch(&self) -> u64 { - self.0.first_normal_epoch - } - - #[napi(setter)] - pub fn set_first_normal_epoch(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.first_normal_epoch = bigint_to_u64(&val)?) - } - - /// The first slot after the warmup period. - /// - /// Basically: `MINIMUM_SLOTS_PER_EPOCH * (2.pow(first_normal_epoch) - 1)`. - #[napi(getter)] - pub fn first_normal_slot(&self) -> u64 { - self.0.first_normal_slot - } - - #[napi(setter)] - pub fn set_first_normal_slot(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.first_normal_slot = bigint_to_u64(&val)?) - } -} diff --git a/crates/node-litesvm/src/sysvar/mod.rs b/crates/node-litesvm/src/sysvar/mod.rs deleted file mode 100644 index e354730cc..000000000 --- a/crates/node-litesvm/src/sysvar/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod clock; -pub mod epoch_rewards; -pub mod epoch_schedule; -pub mod rent; -pub mod slot_hashes; -pub mod slot_history; -pub mod stake_history; diff --git a/crates/node-litesvm/src/sysvar/rent.rs b/crates/node-litesvm/src/sysvar/rent.rs deleted file mode 100644 index 6f664cc4d..000000000 --- a/crates/node-litesvm/src/sysvar/rent.rs +++ /dev/null @@ -1,187 +0,0 @@ -#![allow(deprecated)] - -use { - crate::{ - to_string_js, - util::{bigint_to_u64, bigint_to_usize}, - }, - napi::bindgen_prelude::*, - solana_rent::{Rent as RentOriginal, ACCOUNT_STORAGE_OVERHEAD}, -}; - -/// Configuration of network rent. -#[derive(Default, Debug)] -#[napi] -pub struct Rent(pub(crate) RentOriginal); - -#[napi] -impl Rent { - /// @param lamportsPerByteYear - Rental rate in lamports/byte-year. - /// @param exemptionThreshold - Amount of time (in years) a balance must include rent for the account to be rent exempt. - /// @param burnPercent - The percentage of collected rent that is burned. - #[napi(constructor)] - pub fn new( - lamports_per_byte_year: BigInt, - exemption_threshold: f64, - burn_percent: u8, - ) -> Result { - Ok(Self(RentOriginal { - lamports_per_byte: bigint_to_u64(&lamports_per_byte_year)?, - exemption_threshold: exemption_threshold.to_le_bytes(), - burn_percent, - })) - } - - /// Initialize rent with the default Solana settings. - #[napi(factory, js_name = "default")] - pub fn new_default() -> Self { - Self::default() - } - - /// Rental rate in lamports/byte-year. - /// - /// Note: since SIMD-0194 the underlying sysvar stores lamports/byte with - /// the exemption threshold folded in (default 6960, threshold 1.0), so on - /// current clusters this returns double the historical 3480 value. - #[napi(getter)] - pub fn lamports_per_byte_year(&self) -> u64 { - self.0.lamports_per_byte - } - - #[napi(setter)] - pub fn set_lamports_per_byte_year(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.lamports_per_byte = bigint_to_u64(&val)?) - } - - /// Amount of time (in years) a balance must include rent for the account to be rent exempt. - #[napi(getter)] - pub fn exemption_threshold(&self) -> f64 { - f64::from_le_bytes(self.0.exemption_threshold) - } - - #[napi(setter)] - pub fn set_exemption_threshold(&mut self, val: f64) { - self.0.exemption_threshold = val.to_le_bytes(); - } - - /// The percentage of collected rent that is burned. - #[napi(getter)] - pub fn burn_percent(&self) -> u8 { - self.0.burn_percent - } - - #[napi(setter)] - pub fn set_burn_percent(&mut self, val: u8) { - self.0.burn_percent = val; - } - - /// Calculate how much rent to burn from the collected rent. - /// - /// The first value returned is the amount burned. The second is the amount - /// to distribute to validators. - /// - /// @param rentCollected: The amount of rent collected. - /// @returns The amount burned and the amount to distribute to validators. - #[napi(ts_return_type = "[bigint, bigint]")] - pub fn calculate_burn(&self, rent_collected: BigInt) -> Result<[u64; 2]> { - let rent_collected = bigint_to_u64(&rent_collected)?; - let burned = rent_collected.saturating_mul(u64::from(self.0.burn_percent)) / 100; - Ok([burned, rent_collected.saturating_sub(burned)]) - } - - /// Minimum balance due for rent-exemption of a given account data size. - /// - /// Note: a stripped-down version of this calculation is used in - /// ``calculate_split_rent_exempt_reserve`` in the stake program. When this - /// function is updated, eg. when making rent variable, the stake program - /// will need to be refactored. - /// - /// @param dataLen - The account data size. - /// @returns The minimum balance due. - #[napi] - pub fn minimum_balance(&self, data_len: BigInt) -> Result { - self.checked_minimum_balance(bigint_to_usize(&data_len)?) - } - - /// Whether a given balance and data length would be exempt. - #[napi] - pub fn is_exempt(&self, balance: BigInt, data_len: BigInt) -> Result { - Ok( - bigint_to_u64(&balance)? - >= self.checked_minimum_balance(bigint_to_usize(&data_len)?)?, - ) - } - - /// Rent due on account's data length with balance. - /// - /// @param balance - The account balance. - /// @param dataLen - The account data length. - /// @param yearsElapsed - Time elapsed in years. - /// @returns The rent due. - #[napi] - pub fn due( - &self, - balance: BigInt, - data_len: BigInt, - years_elapsed: f64, - ) -> Result> { - let balance = bigint_to_u64(&balance)?; - let data_len = bigint_to_usize(&data_len)?; - if balance >= self.checked_minimum_balance(data_len)? { - Ok(None) - } else { - Ok(Some(self.due_amount_inner(data_len as u64, years_elapsed))) - } - } - - /// Rent due for account that is known to be not exempt. - /// - /// Note: since SIMD-0194 the underlying rate is lamports/byte with the - /// exemption threshold folded in, so on current clusters this returns - /// double the pre-SIMD-0194 amount for the same inputs. - /// - /// @param dataLen - The account data length. - /// @param yearsElapsed - Time elapsed in years. - /// @returns The amount due. - #[napi] - pub fn due_amount(&self, data_len: BigInt, years_elapsed: f64) -> Result { - Ok(self.due_amount_inner(bigint_to_usize(&data_len)? as u64, years_elapsed)) - } - - /// Creates a `Rent` that charges no lamports. - /// - /// This is used for testing. - /// - #[napi(factory)] - pub fn free() -> Self { - Self(RentOriginal::free()) - } - - /// Creates a `Rent` that is scaled based on the number of slots in an epoch. - /// - /// This is used for testing. - /// - /// @deprecated Epoch-based rent scaling was removed upstream (SIMD-0194); - /// the argument is ignored and the default rent is returned. The old - /// scaling never affected the rent-exempt minimum balance. - #[napi(factory)] - pub fn with_slots_per_epoch(_slots_per_epoch: BigInt) -> Result { - Ok(Self::default()) - } - - fn checked_minimum_balance(&self, data_len: usize) -> Result { - self.0.try_minimum_balance(data_len).ok_or_else(|| { - Error::new( - Status::InvalidArg, - "maximum permitted data length exceeded".to_string(), - ) - }) - } - - fn due_amount_inner(&self, data_len: u64, years_elapsed: f64) -> u64 { - (((ACCOUNT_STORAGE_OVERHEAD + data_len) * self.0.lamports_per_byte) as f64 * years_elapsed) - as u64 - } -} - -to_string_js!(Rent); diff --git a/crates/node-litesvm/src/sysvar/slot_hashes.rs b/crates/node-litesvm/src/sysvar/slot_hashes.rs deleted file mode 100644 index 0086794fe..000000000 --- a/crates/node-litesvm/src/sysvar/slot_hashes.rs +++ /dev/null @@ -1,31 +0,0 @@ -use napi::bindgen_prelude::*; - -#[derive(Debug, Clone)] -#[napi] -pub struct SlotHash { - pub(crate) slot: BigInt, - pub(crate) hash: String, -} - -#[napi] -impl SlotHash { - #[napi(getter)] - pub fn slot(&self) -> BigInt { - self.slot.clone() - } - - #[napi(getter)] - pub fn hash(&self) -> String { - self.hash.clone() - } - - #[napi(setter)] - pub fn set_slot(&mut self, slot: BigInt) { - self.slot = slot; - } - - #[napi(setter)] - pub fn set_hash(&mut self, hash: String) { - self.hash = hash; - } -} diff --git a/crates/node-litesvm/src/sysvar/slot_history.rs b/crates/node-litesvm/src/sysvar/slot_history.rs deleted file mode 100644 index 7ba24d9d7..000000000 --- a/crates/node-litesvm/src/sysvar/slot_history.rs +++ /dev/null @@ -1,93 +0,0 @@ -use { - crate::{to_string_js, util::bigint_to_u64}, - bv::BitVec, - napi::bindgen_prelude::*, - solana_slot_history::{Check, SlotHistory as SlotHistoryOriginal}, -}; - -#[derive(Debug)] -#[napi] -pub enum SlotHistoryCheck { - Future, - TooOld, - Found, - NotFound, -} - -to_string_js!(SlotHistoryCheck); - -impl From for SlotHistoryCheck { - fn from(value: Check) -> Self { - match value { - Check::Future => Self::Future, - Check::TooOld => Self::TooOld, - Check::Found => Self::Found, - Check::NotFound => Self::NotFound, - } - } -} - -/// A bitvector indicating which slots are present in the past epoch. -#[derive(Debug)] -#[napi] -pub struct SlotHistory(pub(crate) SlotHistoryOriginal); - -#[napi] -impl SlotHistory { - #[napi(constructor)] - pub fn new(bits: BigUint64Array, next_slot: BigInt) -> Result { - let bits_converted: BitVec = BitVec::from(bits.to_vec()); - Ok(Self(SlotHistoryOriginal { - bits: bits_converted, - next_slot: bigint_to_u64(&next_slot)?, - })) - } - - #[napi(factory, js_name = "default")] - pub fn new_default() -> Self { - Self(SlotHistoryOriginal::default()) - } - - #[napi(getter)] - pub fn bits(&self) -> BigUint64Array { - self.0.bits.clone().into_boxed_slice().to_vec().into() - } - - #[napi(setter)] - pub fn set_bits(&mut self, bits: BigUint64Array) { - let bits_converted: BitVec = BitVec::from(bits.to_vec()); - self.0.bits = bits_converted; - } - - #[napi(getter)] - pub fn next_slot(&self) -> u64 { - self.0.next_slot - } - - #[napi(setter)] - pub fn set_next_slot(&mut self, slot: BigInt) -> Result<()> { - Ok(self.0.next_slot = bigint_to_u64(&slot)?) - } - - #[napi] - pub fn add(&mut self, slot: BigInt) -> Result<()> { - Ok(self.0.add(bigint_to_u64(&slot)?)) - } - - #[napi] - pub fn check(&self, slot: BigInt) -> Result { - Ok(SlotHistoryCheck::from(self.0.check(bigint_to_u64(&slot)?))) - } - - #[napi] - pub fn oldest(&self) -> u64 { - self.0.oldest() - } - - #[napi] - pub fn newest(&self) -> u64 { - self.0.newest() - } -} - -to_string_js!(SlotHistory); diff --git a/crates/node-litesvm/src/sysvar/stake_history.rs b/crates/node-litesvm/src/sysvar/stake_history.rs deleted file mode 100644 index d80f5b3f0..000000000 --- a/crates/node-litesvm/src/sysvar/stake_history.rs +++ /dev/null @@ -1,87 +0,0 @@ -use { - crate::{to_string_js, util::bigint_to_u64}, - napi::bindgen_prelude::*, - solana_stake_history::{ - StakeHistory as StakeHistoryOriginal, StakeHistoryEntry as StakeHistoryEntryOriginal, - }, -}; - -#[derive(Debug, Clone)] -#[napi] -pub struct StakeHistoryEntry(pub(crate) StakeHistoryEntryOriginal); - -#[napi] -impl StakeHistoryEntry { - /// @param effective - effective stake at this epoch - /// @param activating - sum of portion of stakes not fully warmed up - /// @param effective - requested to be cooled down, not fully deactivated yet - #[napi(constructor)] - pub fn new(effective: BigInt, activating: BigInt, deactivating: BigInt) -> Result { - Ok(Self(StakeHistoryEntryOriginal { - effective: bigint_to_u64(&effective)?, - activating: bigint_to_u64(&activating)?, - deactivating: bigint_to_u64(&deactivating)?, - })) - } - - /// effective stake at this epoch - #[napi(getter)] - pub fn effective(&self) -> u64 { - self.0.effective - } - - #[napi(setter)] - pub fn set_effective(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.effective = bigint_to_u64(&val)?) - } - - /// sum of portion of stakes not fully warmed up - #[napi(getter)] - pub fn activating(&self) -> u64 { - self.0.activating - } - - #[napi(setter)] - pub fn set_activating(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.effective = bigint_to_u64(&val)?) - } - - /// requested to be cooled down, not fully deactivated yet - #[napi(getter)] - pub fn deactivating(&self) -> u64 { - self.0.deactivating - } - - #[napi(setter)] - pub fn set_deactivating(&mut self, val: BigInt) -> Result<()> { - Ok(self.0.effective = bigint_to_u64(&val)?) - } -} - -to_string_js!(StakeHistoryEntry); - -/// A type to hold data for the StakeHistory sysvar. -#[derive(Debug)] -#[napi] -pub struct StakeHistory(pub(crate) StakeHistoryOriginal); - -#[napi] -impl StakeHistory { - #[napi(constructor)] - pub fn new() -> Self { - Self(StakeHistoryOriginal::default()) - } - - #[napi] - pub fn get(&self, epoch: BigInt) -> Result> { - Ok(self - .0 - .get(bigint_to_u64(&epoch)?) - .map(|x| StakeHistoryEntry(x.clone()))) - } - - #[napi] - pub fn add(&mut self, epoch: BigInt, entry: &StakeHistoryEntry) -> Result<()> { - Ok(self.0.add(bigint_to_u64(&epoch)?, entry.clone().0)) - } -} diff --git a/crates/node-litesvm/src/transaction_error.rs b/crates/node-litesvm/src/transaction_error.rs deleted file mode 100644 index f21a26e28..000000000 --- a/crates/node-litesvm/src/transaction_error.rs +++ /dev/null @@ -1,498 +0,0 @@ -use { - crate::to_string_js, - core::fmt, - napi::bindgen_prelude::{Either3, Either5}, - solana_instruction::error::InstructionError as InstructionErrorOriginal, - solana_transaction_error::TransactionError as TransactionErrorOriginal, -}; - -#[derive(Clone, Debug)] -#[napi] -pub struct InstructionErrorCustom { - pub code: u32, -} - -to_string_js!(InstructionErrorCustom); - -#[derive(Clone, Debug)] -#[napi] -pub struct InstructionErrorBorshIO { - pub msg: String, -} - -to_string_js!(InstructionErrorBorshIO); - -#[derive(Debug, Clone)] -#[napi] -pub enum InstructionErrorFieldless { - GenericError, - InvalidArgument, - InvalidInstructionData, - InvalidAccountData, - AccountDataTooSmall, - InsufficientFunds, - IncorrectProgramId, - MissingRequiredSignature, - AccountAlreadyInitialized, - UninitializedAccount, - UnbalancedInstruction, - ModifiedProgramId, - ExternalAccountLamportSpend, - ExternalAccountDataModified, - ReadonlyLamportChange, - ReadonlyDataModified, - DuplicateAccountIndex, - ExecutableModified, - RentEpochModified, - NotEnoughAccountKeys, - AccountDataSizeChanged, - AccountNotExecutable, - AccountBorrowFailed, - AccountBorrowOutstanding, - DuplicateAccountOutOfSync, - InvalidError, - ExecutableDataModified, - ExecutableLamportChange, - ExecutableAccountNotRentExempt, - UnsupportedProgramId, - CallDepth, - MissingAccount, - ReentrancyNotAllowed, - MaxSeedLengthExceeded, - InvalidSeeds, - InvalidRealloc, - ComputationalBudgetExceeded, - PrivilegeEscalation, - ProgramEnvironmentSetupFailure, - ProgramFailedToComplete, - ProgramFailedToCompile, - Immutable, - IncorrectAuthority, - AccountNotRentExempt, - InvalidAccountOwner, - ArithmeticOverflow, - UnsupportedSysvar, - IllegalOwner, - MaxAccountsDataAllocationsExceeded, - MaxAccountsExceeded, - MaxInstructionTraceLengthExceeded, - BuiltinProgramsMustConsumeComputeUnits, - BorshIoError, -} - -to_string_js!(InstructionErrorFieldless); - -pub type InstructionError = - Either3; - -// fn debug_instruction_error(err: InstructionError, f: &mut fmt::Formatter) -> fmt::Result { -// match err { -// InstructionError::A(fieldless) => write!(f, "{fieldless:?}"), -// InstructionError::B(custom) => write!(f, "{custom:?}"), -// InstructionError::C(borsh_err) => write!(f, "{borsh_err:?}"), -// } -// } - -fn convert_instruction_error(e: InstructionErrorOriginal) -> InstructionError { - match e { - InstructionErrorOriginal::Custom(code) => { - InstructionError::B(InstructionErrorCustom { code }) - } - InstructionErrorOriginal::BorshIoError => { - InstructionError::A(InstructionErrorFieldless::BorshIoError) - } - InstructionErrorOriginal::GenericError => { - InstructionError::A(InstructionErrorFieldless::GenericError) - } - InstructionErrorOriginal::InvalidArgument => { - InstructionError::A(InstructionErrorFieldless::InvalidArgument) - } - InstructionErrorOriginal::InvalidInstructionData => { - InstructionError::A(InstructionErrorFieldless::InvalidInstructionData) - } - InstructionErrorOriginal::InvalidAccountData => { - InstructionError::A(InstructionErrorFieldless::InvalidAccountData) - } - InstructionErrorOriginal::AccountDataTooSmall => { - InstructionError::A(InstructionErrorFieldless::AccountDataTooSmall) - } - InstructionErrorOriginal::InsufficientFunds => { - InstructionError::A(InstructionErrorFieldless::InsufficientFunds) - } - InstructionErrorOriginal::IncorrectProgramId => { - InstructionError::A(InstructionErrorFieldless::IncorrectProgramId) - } - InstructionErrorOriginal::MissingRequiredSignature => { - InstructionError::A(InstructionErrorFieldless::MissingRequiredSignature) - } - InstructionErrorOriginal::AccountAlreadyInitialized => { - InstructionError::A(InstructionErrorFieldless::AccountAlreadyInitialized) - } - InstructionErrorOriginal::UninitializedAccount => { - InstructionError::A(InstructionErrorFieldless::UninitializedAccount) - } - InstructionErrorOriginal::UnbalancedInstruction => { - InstructionError::A(InstructionErrorFieldless::UnbalancedInstruction) - } - InstructionErrorOriginal::ModifiedProgramId => { - InstructionError::A(InstructionErrorFieldless::ModifiedProgramId) - } - InstructionErrorOriginal::ExternalAccountLamportSpend => { - InstructionError::A(InstructionErrorFieldless::ExternalAccountLamportSpend) - } - InstructionErrorOriginal::ExternalAccountDataModified => { - InstructionError::A(InstructionErrorFieldless::ExternalAccountDataModified) - } - InstructionErrorOriginal::ReadonlyLamportChange => { - InstructionError::A(InstructionErrorFieldless::ReadonlyLamportChange) - } - InstructionErrorOriginal::ReadonlyDataModified => { - InstructionError::A(InstructionErrorFieldless::ReadonlyDataModified) - } - InstructionErrorOriginal::DuplicateAccountIndex => { - InstructionError::A(InstructionErrorFieldless::DuplicateAccountIndex) - } - InstructionErrorOriginal::ExecutableModified => { - InstructionError::A(InstructionErrorFieldless::ExecutableModified) - } - InstructionErrorOriginal::RentEpochModified => { - InstructionError::A(InstructionErrorFieldless::RentEpochModified) - } - #[allow(deprecated)] - InstructionErrorOriginal::NotEnoughAccountKeys => { - InstructionError::A(InstructionErrorFieldless::NotEnoughAccountKeys) - } - InstructionErrorOriginal::AccountDataSizeChanged => { - InstructionError::A(InstructionErrorFieldless::AccountDataSizeChanged) - } - InstructionErrorOriginal::AccountNotExecutable => { - InstructionError::A(InstructionErrorFieldless::AccountNotExecutable) - } - InstructionErrorOriginal::AccountBorrowFailed => { - InstructionError::A(InstructionErrorFieldless::AccountBorrowFailed) - } - InstructionErrorOriginal::AccountBorrowOutstanding => { - InstructionError::A(InstructionErrorFieldless::AccountBorrowOutstanding) - } - InstructionErrorOriginal::DuplicateAccountOutOfSync => { - InstructionError::A(InstructionErrorFieldless::DuplicateAccountOutOfSync) - } - InstructionErrorOriginal::InvalidError => { - InstructionError::A(InstructionErrorFieldless::InvalidError) - } - InstructionErrorOriginal::ExecutableDataModified => { - InstructionError::A(InstructionErrorFieldless::ExecutableDataModified) - } - InstructionErrorOriginal::ExecutableLamportChange => { - InstructionError::A(InstructionErrorFieldless::ExecutableLamportChange) - } - InstructionErrorOriginal::ExecutableAccountNotRentExempt => { - InstructionError::A(InstructionErrorFieldless::ExecutableAccountNotRentExempt) - } - InstructionErrorOriginal::UnsupportedProgramId => { - InstructionError::A(InstructionErrorFieldless::UnsupportedProgramId) - } - InstructionErrorOriginal::CallDepth => { - InstructionError::A(InstructionErrorFieldless::CallDepth) - } - InstructionErrorOriginal::MissingAccount => { - InstructionError::A(InstructionErrorFieldless::MissingAccount) - } - InstructionErrorOriginal::ReentrancyNotAllowed => { - InstructionError::A(InstructionErrorFieldless::ReentrancyNotAllowed) - } - InstructionErrorOriginal::MaxSeedLengthExceeded => { - InstructionError::A(InstructionErrorFieldless::MaxSeedLengthExceeded) - } - InstructionErrorOriginal::InvalidSeeds => { - InstructionError::A(InstructionErrorFieldless::InvalidSeeds) - } - InstructionErrorOriginal::InvalidRealloc => { - InstructionError::A(InstructionErrorFieldless::InvalidRealloc) - } - InstructionErrorOriginal::ComputationalBudgetExceeded => { - InstructionError::A(InstructionErrorFieldless::ComputationalBudgetExceeded) - } - InstructionErrorOriginal::PrivilegeEscalation => { - InstructionError::A(InstructionErrorFieldless::PrivilegeEscalation) - } - InstructionErrorOriginal::ProgramEnvironmentSetupFailure => { - InstructionError::A(InstructionErrorFieldless::ProgramEnvironmentSetupFailure) - } - InstructionErrorOriginal::ProgramFailedToComplete => { - InstructionError::A(InstructionErrorFieldless::ProgramFailedToComplete) - } - InstructionErrorOriginal::ProgramFailedToCompile => { - InstructionError::A(InstructionErrorFieldless::ProgramFailedToCompile) - } - InstructionErrorOriginal::Immutable => { - InstructionError::A(InstructionErrorFieldless::Immutable) - } - InstructionErrorOriginal::IncorrectAuthority => { - InstructionError::A(InstructionErrorFieldless::IncorrectAuthority) - } - InstructionErrorOriginal::AccountNotRentExempt => { - InstructionError::A(InstructionErrorFieldless::AccountNotRentExempt) - } - InstructionErrorOriginal::InvalidAccountOwner => { - InstructionError::A(InstructionErrorFieldless::InvalidAccountOwner) - } - InstructionErrorOriginal::ArithmeticOverflow => { - InstructionError::A(InstructionErrorFieldless::ArithmeticOverflow) - } - InstructionErrorOriginal::UnsupportedSysvar => { - InstructionError::A(InstructionErrorFieldless::UnsupportedSysvar) - } - InstructionErrorOriginal::IllegalOwner => { - InstructionError::A(InstructionErrorFieldless::IllegalOwner) - } - InstructionErrorOriginal::MaxAccountsDataAllocationsExceeded => { - InstructionError::A(InstructionErrorFieldless::MaxAccountsDataAllocationsExceeded) - } - InstructionErrorOriginal::MaxAccountsExceeded => { - InstructionError::A(InstructionErrorFieldless::MaxAccountsExceeded) - } - InstructionErrorOriginal::MaxInstructionTraceLengthExceeded => { - InstructionError::A(InstructionErrorFieldless::MaxInstructionTraceLengthExceeded) - } - InstructionErrorOriginal::BuiltinProgramsMustConsumeComputeUnits => { - InstructionError::A(InstructionErrorFieldless::BuiltinProgramsMustConsumeComputeUnits) - } - } -} - -#[derive(Debug)] -#[napi] -pub enum TransactionErrorFieldless { - AccountInUse, - AccountLoadedTwice, - AccountNotFound, - ProgramAccountNotFound, - InsufficientFundsForFee, - InvalidAccountForFee, - AlreadyProcessed, - BlockhashNotFound, - CallChainTooDeep, - MissingSignatureForFee, - InvalidAccountIndex, - SignatureFailure, - InvalidProgramForExecution, - SanitizeFailure, - ClusterMaintenance, - AccountBorrowOutstanding, - WouldExceedMaxBlockCostLimit, - UnsupportedVersion, - InvalidWritableAccount, - WouldExceedMaxAccountCostLimit, - WouldExceedAccountDataBlockLimit, - TooManyAccountLocks, - AddressLookupTableNotFound, - InvalidAddressLookupTableOwner, - InvalidAddressLookupTableData, - InvalidAddressLookupTableIndex, - InvalidRentPayingAccount, - WouldExceedMaxVoteCostLimit, - WouldExceedAccountDataTotalLimit, - MaxLoadedAccountsDataSizeExceeded, - ResanitizationNeeded, - InvalidLoadedAccountsDataSizeLimit, - UnbalancedTransaction, - ProgramCacheHitMaxLimit, - CommitCancelled, -} - -to_string_js!(TransactionErrorFieldless); - -#[derive(Clone)] -#[napi] -pub struct TransactionErrorInstructionError { - pub index: u8, - error: InstructionError, -} - -impl fmt::Debug for TransactionErrorInstructionError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("TransactionErrorInstructionError") - .field("index", &self.index) - .field( - "error", - match &self.error { - InstructionError::A(a) => a, - InstructionError::B(b) => b, - InstructionError::C(c) => c, - }, - ) - .finish() - } -} - -#[napi] -impl TransactionErrorInstructionError { - #[napi( - ts_return_type = "InstructionErrorFieldless | InstructionErrorCustom | InstructionErrorBorshIO" - )] - pub fn err(&self) -> InstructionError { - self.error.clone() - } -} - -to_string_js!(TransactionErrorInstructionError); - -#[derive(Debug)] -#[napi] -pub struct TransactionErrorDuplicateInstruction { - pub index: u8, -} - -to_string_js!(TransactionErrorDuplicateInstruction); - -#[derive(Debug)] -#[napi] -pub struct TransactionErrorInsufficientFundsForRent { - pub account_index: u8, -} - -to_string_js!(TransactionErrorInsufficientFundsForRent); - -#[derive(Debug)] -#[napi] -pub struct TransactionErrorProgramExecutionTemporarilyRestricted { - pub account_index: u8, -} - -to_string_js!(TransactionErrorProgramExecutionTemporarilyRestricted); - -pub type TransactionError = Either5< - TransactionErrorFieldless, - TransactionErrorInstructionError, - TransactionErrorDuplicateInstruction, - TransactionErrorInsufficientFundsForRent, - TransactionErrorProgramExecutionTemporarilyRestricted, ->; - -pub(crate) fn convert_transaction_error(w: TransactionErrorOriginal) -> TransactionError { - match w { - TransactionErrorOriginal::InstructionError(index, err) => { - TransactionError::B(TransactionErrorInstructionError { - index, - error: convert_instruction_error(err), - }) - } - TransactionErrorOriginal::DuplicateInstruction(index) => { - TransactionError::C(TransactionErrorDuplicateInstruction { index }) - } - TransactionErrorOriginal::InsufficientFundsForRent { account_index } => { - TransactionError::D(TransactionErrorInsufficientFundsForRent { account_index }) - } - TransactionErrorOriginal::ProgramExecutionTemporarilyRestricted { account_index } => { - TransactionError::E(TransactionErrorProgramExecutionTemporarilyRestricted { - account_index, - }) - } - TransactionErrorOriginal::AccountInUse => { - TransactionError::A(TransactionErrorFieldless::AccountInUse) - } - TransactionErrorOriginal::AccountLoadedTwice => { - TransactionError::A(TransactionErrorFieldless::AccountLoadedTwice) - } - TransactionErrorOriginal::AccountNotFound => { - TransactionError::A(TransactionErrorFieldless::AccountNotFound) - } - TransactionErrorOriginal::ProgramAccountNotFound => { - TransactionError::A(TransactionErrorFieldless::ProgramAccountNotFound) - } - TransactionErrorOriginal::InsufficientFundsForFee => { - TransactionError::A(TransactionErrorFieldless::InsufficientFundsForFee) - } - TransactionErrorOriginal::InvalidAccountForFee => { - TransactionError::A(TransactionErrorFieldless::InvalidAccountForFee) - } - TransactionErrorOriginal::AlreadyProcessed => { - TransactionError::A(TransactionErrorFieldless::AlreadyProcessed) - } - TransactionErrorOriginal::BlockhashNotFound => { - TransactionError::A(TransactionErrorFieldless::BlockhashNotFound) - } - TransactionErrorOriginal::CallChainTooDeep => { - TransactionError::A(TransactionErrorFieldless::CallChainTooDeep) - } - TransactionErrorOriginal::MissingSignatureForFee => { - TransactionError::A(TransactionErrorFieldless::MissingSignatureForFee) - } - TransactionErrorOriginal::InvalidAccountIndex => { - TransactionError::A(TransactionErrorFieldless::InvalidAccountIndex) - } - TransactionErrorOriginal::SignatureFailure => { - TransactionError::A(TransactionErrorFieldless::SignatureFailure) - } - TransactionErrorOriginal::InvalidProgramForExecution => { - TransactionError::A(TransactionErrorFieldless::InvalidProgramForExecution) - } - TransactionErrorOriginal::SanitizeFailure => { - TransactionError::A(TransactionErrorFieldless::SanitizeFailure) - } - TransactionErrorOriginal::ClusterMaintenance => { - TransactionError::A(TransactionErrorFieldless::ClusterMaintenance) - } - TransactionErrorOriginal::AccountBorrowOutstanding => { - TransactionError::A(TransactionErrorFieldless::AccountBorrowOutstanding) - } - TransactionErrorOriginal::WouldExceedMaxBlockCostLimit => { - TransactionError::A(TransactionErrorFieldless::WouldExceedMaxBlockCostLimit) - } - TransactionErrorOriginal::UnsupportedVersion => { - TransactionError::A(TransactionErrorFieldless::UnsupportedVersion) - } - TransactionErrorOriginal::InvalidWritableAccount => { - TransactionError::A(TransactionErrorFieldless::InvalidWritableAccount) - } - TransactionErrorOriginal::WouldExceedMaxAccountCostLimit => { - TransactionError::A(TransactionErrorFieldless::WouldExceedMaxAccountCostLimit) - } - TransactionErrorOriginal::WouldExceedAccountDataBlockLimit => { - TransactionError::A(TransactionErrorFieldless::WouldExceedAccountDataBlockLimit) - } - TransactionErrorOriginal::TooManyAccountLocks => { - TransactionError::A(TransactionErrorFieldless::TooManyAccountLocks) - } - TransactionErrorOriginal::AddressLookupTableNotFound => { - TransactionError::A(TransactionErrorFieldless::AddressLookupTableNotFound) - } - TransactionErrorOriginal::InvalidAddressLookupTableOwner => { - TransactionError::A(TransactionErrorFieldless::InvalidAddressLookupTableOwner) - } - TransactionErrorOriginal::InvalidAddressLookupTableData => { - TransactionError::A(TransactionErrorFieldless::InvalidAddressLookupTableData) - } - TransactionErrorOriginal::InvalidAddressLookupTableIndex => { - TransactionError::A(TransactionErrorFieldless::InvalidAddressLookupTableIndex) - } - TransactionErrorOriginal::InvalidRentPayingAccount => { - TransactionError::A(TransactionErrorFieldless::InvalidRentPayingAccount) - } - TransactionErrorOriginal::WouldExceedMaxVoteCostLimit => { - TransactionError::A(TransactionErrorFieldless::WouldExceedMaxVoteCostLimit) - } - TransactionErrorOriginal::WouldExceedAccountDataTotalLimit => { - TransactionError::A(TransactionErrorFieldless::WouldExceedAccountDataTotalLimit) - } - TransactionErrorOriginal::MaxLoadedAccountsDataSizeExceeded => { - TransactionError::A(TransactionErrorFieldless::MaxLoadedAccountsDataSizeExceeded) - } - TransactionErrorOriginal::ResanitizationNeeded => { - TransactionError::A(TransactionErrorFieldless::ResanitizationNeeded) - } - TransactionErrorOriginal::InvalidLoadedAccountsDataSizeLimit => { - TransactionError::A(TransactionErrorFieldless::InvalidLoadedAccountsDataSizeLimit) - } - TransactionErrorOriginal::UnbalancedTransaction => { - TransactionError::A(TransactionErrorFieldless::UnbalancedTransaction) - } - TransactionErrorOriginal::ProgramCacheHitMaxLimit => { - TransactionError::A(TransactionErrorFieldless::ProgramCacheHitMaxLimit) - } - TransactionErrorOriginal::CommitCancelled => { - TransactionError::A(TransactionErrorFieldless::CommitCancelled) - } - } -} diff --git a/crates/node-litesvm/src/transaction_metadata.rs b/crates/node-litesvm/src/transaction_metadata.rs deleted file mode 100644 index d37bb81bb..000000000 --- a/crates/node-litesvm/src/transaction_metadata.rs +++ /dev/null @@ -1,196 +0,0 @@ -use { - crate::{ - account::Account, - to_string_js, - transaction_error::{convert_transaction_error, TransactionError}, - }, - litesvm::types::{ - FailedTransactionMetadata as FailedTransactionMetadataOriginal, - SimulatedTransactionInfo as SimulatedTransactionInfoOriginal, - TransactionMetadata as TransactionMetadataOriginal, - }, - napi::bindgen_prelude::*, - solana_account::Account as AccountOriginal, - solana_message::{ - compiled_instruction::CompiledInstruction as CompiledInstructionOriginal, - inner_instruction::InnerInstruction as InnerInstructionOriginal, - }, - solana_transaction_context::transaction::TransactionReturnData as TransactionReturnDataOriginal, -}; - -#[derive(Debug, Clone)] -#[napi] -pub struct CompiledInstruction(CompiledInstructionOriginal); - -#[napi] -impl CompiledInstruction { - #[napi(constructor)] - pub fn new(program_id_index: u8, accounts: Uint8Array, data: Uint8Array) -> Self { - Self(CompiledInstructionOriginal { - program_id_index, - accounts: accounts.to_vec(), - data: data.to_vec(), - }) - } - - #[napi] - pub fn program_id_index(&self) -> u8 { - self.0.program_id_index - } - - #[napi] - pub fn accounts(&self) -> Uint8Array { - Uint8Array::new(self.0.accounts.clone()) - } - - #[napi] - pub fn data(&self) -> Uint8Array { - Uint8Array::new(self.0.data.clone()) - } -} - -to_string_js!(CompiledInstruction); - -#[derive(Debug, Clone)] -#[napi] -pub struct InnerInstruction(InnerInstructionOriginal); - -#[napi] -impl InnerInstruction { - #[napi] - pub fn instruction(&self) -> CompiledInstruction { - CompiledInstruction(self.0.instruction.clone()) - } - - #[napi] - pub fn stack_height(&self) -> u8 { - self.0.stack_height - } -} - -to_string_js!(InnerInstruction); - -#[derive(Debug, Clone)] -#[napi] -pub struct TransactionReturnData(TransactionReturnDataOriginal); - -#[napi] -impl TransactionReturnData { - #[napi] - pub fn program_id(&self) -> Uint8Array { - Uint8Array::with_data_copied(self.0.program_id) - } - - #[napi] - pub fn data(&self) -> Uint8Array { - Uint8Array::new(self.0.data.clone()) - } -} - -to_string_js!(TransactionReturnData); - -#[derive(Debug, Clone)] -#[napi] -pub struct TransactionMetadata(pub(crate) TransactionMetadataOriginal); - -#[napi] -impl TransactionMetadata { - #[napi] - pub fn signature(&self) -> Uint8Array { - Uint8Array::with_data_copied(self.0.signature) - } - - #[napi] - pub fn logs(&self) -> Vec { - self.0.logs.clone() - } - - #[napi] - pub fn inner_instructions(&self) -> Vec> { - self.0 - .inner_instructions - .clone() - .into_iter() - .map(|outer| outer.into_iter().map(InnerInstruction).collect()) - .collect() - } - - #[napi] - pub fn compute_units_consumed(&self) -> u64 { - self.0.compute_units_consumed - } - - #[napi] - pub fn return_data(&self) -> TransactionReturnData { - TransactionReturnData(self.0.return_data.clone()) - } - - #[napi] - pub fn pretty_logs(&self) -> String { - self.0.pretty_logs() - } -} - -to_string_js!(TransactionMetadata); - -#[derive(Debug, Clone)] -#[napi] -pub struct FailedTransactionMetadata(pub(crate) FailedTransactionMetadataOriginal); - -#[napi] -impl FailedTransactionMetadata { - #[napi( - ts_return_type = "TransactionErrorFieldless | TransactionErrorInstructionError | TransactionErrorDuplicateInstruction | TransactionErrorInsufficientFundsForRent | TransactionErrorProgramExecutionTemporarilyRestricted" - )] - pub fn err(&self) -> TransactionError { - convert_transaction_error(self.0.err.clone()) - } - - #[napi] - pub fn meta(&self) -> TransactionMetadata { - TransactionMetadata(self.0.meta.clone()) - } -} - -to_string_js!(FailedTransactionMetadata); - -#[napi] -pub struct AddressAndAccount { - pub address: Uint8Array, - pub(crate) account: Account, -} - -#[napi] -impl AddressAndAccount { - #[napi] - pub fn account(&self) -> Account { - self.account.clone() - } -} - -#[derive(Debug, Clone)] -#[napi] -pub struct SimulatedTransactionInfo(pub(crate) SimulatedTransactionInfoOriginal); - -#[napi] -impl SimulatedTransactionInfo { - #[napi] - pub fn meta(&self) -> TransactionMetadata { - TransactionMetadata(self.0.meta.clone()) - } - - #[napi] - pub fn post_accounts(&self) -> Vec { - self.0 - .post_accounts - .clone() - .into_iter() - .map(|x| AddressAndAccount { - address: Uint8Array::with_data_copied(x.0), - account: Account(AccountOriginal::from(x.1)), - }) - .collect() - } -} - -to_string_js!(SimulatedTransactionInfo); diff --git a/crates/node-litesvm/src/util.rs b/crates/node-litesvm/src/util.rs deleted file mode 100644 index 4c0f26c31..000000000 --- a/crates/node-litesvm/src/util.rs +++ /dev/null @@ -1,82 +0,0 @@ -use {napi::bindgen_prelude::*, solana_address::Address, solana_hash::Hash, std::str::FromStr}; - -pub(crate) fn convert_pubkey(address: &[u8]) -> Address { - Address::try_from(address).unwrap() -} - -pub(crate) fn try_parse_hash(raw: &str) -> Result { - Hash::from_str(raw).map_err(|e| { - Error::new( - Status::GenericFailure, - format!("Failed to parse blockhash: {e}"), - ) - }) -} - -pub(crate) fn bigint_to_u64(val: &BigInt) -> Result { - let res = val.get_u64(); - if res.0 { - return Err(Error::new( - Status::GenericFailure, - format!("Cannot convert negative bigint to u64: {val:?}"), - )); - } - if !res.2 { - return Err(Error::new( - Status::GenericFailure, - format!("Bigint too large for u64: {val:?}"), - )); - } - Ok(res.1) -} - -pub(crate) fn bigint_to_u128(val: &BigInt) -> Result { - let res = val.get_u128(); - if res.0 { - return Err(Error::new( - Status::GenericFailure, - format!("Cannot convert negative bigint to u128: {val:?}"), - )); - } - if !res.2 { - return Err(Error::new( - Status::GenericFailure, - format!("Bigint too large for u128: {val:?}"), - )); - } - Ok(res.1) -} - -pub(crate) fn bigint_to_i64(val: &BigInt) -> Result { - let res = val.get_i64(); - if !res.1 { - return Err(Error::new( - Status::GenericFailure, - format!("Bigint too large for i64: {val:?}"), - )); - } - Ok(res.0) -} - -pub(crate) fn bigint_to_usize(val: &BigInt) -> Result { - let res = val.get_u64(); - if res.0 { - return Err(Error::new( - Status::GenericFailure, - format!("Cannot convert negative bigint to usize: {val:?}"), - )); - } - if !res.2 { - return Err(Error::new( - Status::GenericFailure, - format!("Bigint too large for usize: {val:?}"), - )); - } - let val_u64 = res.1; - usize::try_from(val_u64).map_err(|_| { - Error::new( - Status::GenericFailure, - format!("Bigint too large for usize: {val_u64}"), - ) - }) -} diff --git a/crates/node-litesvm/tests/computeLimit.test.ts b/crates/node-litesvm/tests/computeLimit.test.ts index ccf7788db..8f3fe6a6c 100644 --- a/crates/node-litesvm/tests/computeLimit.test.ts +++ b/crates/node-litesvm/tests/computeLimit.test.ts @@ -1,6 +1,6 @@ import { generateKeyPairSigner, lamports } from "@solana/kit"; -import { TransactionErrorFieldless } from "internal"; import { FailedTransactionMetadata, LiteSVM } from "litesvm"; +import { TransactionErrorFieldless } from "litesvm-core"; import assert from "node:assert/strict"; import { test } from "node:test"; import { diff --git a/crates/node-litesvm/tests/getTransaction.test.ts b/crates/node-litesvm/tests/getTransaction.test.ts index ebdf0377d..def435d9d 100644 --- a/crates/node-litesvm/tests/getTransaction.test.ts +++ b/crates/node-litesvm/tests/getTransaction.test.ts @@ -3,7 +3,6 @@ import { getSignatureFromTransaction, lamports, } from "@solana/kit"; -import { TransactionMetadata } from "internal"; import assert from "node:assert/strict"; import { test } from "node:test"; import { @@ -14,7 +13,7 @@ import { setHelloWorldAccount, setHelloWorldProgram, } from "./util"; -import { LiteSVM } from "index"; +import { LiteSVM, TransactionMetadata } from "litesvm"; test("get transaction", async () => { // Given the following addresses and signers. diff --git a/crates/node-litesvm/tests/nonExistentProgram.test.ts b/crates/node-litesvm/tests/nonExistentProgram.test.ts index 0b41ec5ab..532d7bf15 100644 --- a/crates/node-litesvm/tests/nonExistentProgram.test.ts +++ b/crates/node-litesvm/tests/nonExistentProgram.test.ts @@ -1,6 +1,6 @@ import { generateKeyPairSigner, lamports } from "@solana/kit"; -import { TransactionErrorFieldless } from "internal"; import { FailedTransactionMetadata, LiteSVM } from "litesvm"; +import { TransactionErrorFieldless } from "litesvm-core"; import assert from "node:assert/strict"; import { test } from "node:test"; import { diff --git a/yarn.lock b/yarn.lock index 59dec853d..6927dee47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3973,7 +3973,7 @@ __metadata: languageName: node linkType: hard -"litesvm-core@workspace:crates/node-litesvm-core": +"litesvm-core@workspace:^, litesvm-core@workspace:crates/node-litesvm-core": version: 0.0.0-use.local resolution: "litesvm-core@workspace:crates/node-litesvm-core" dependencies: @@ -3986,13 +3986,13 @@ __metadata: resolution: "litesvm@workspace:crates/node-litesvm" dependencies: "@algolia/client-search": "npm:^5.55.0" - "@napi-rs/cli": "npm:^3.7.2" "@solana-program/system": "npm:^0.12.2" "@solana-program/token": "npm:^0.14.0" "@solana/kit": "npm:^6.10.0" "@types/markdown-it": "npm:^14.1.2" "@types/node": "npm:22.19.21" "@types/node-fetch": "npm:^2.6.13" + litesvm-core: "workspace:^" markdown-it: "npm:^14.2.0" markdown-it-include: "npm:^2.0.0" rome: "npm:^12.1.3" From 648cbe56607814addd8f6eebb4cf088555dee786 Mon Sep 17 00:00:00 2001 From: Chii Yuen Date: Tue, 21 Jul 2026 14:53:52 +0800 Subject: [PATCH 3/7] feat(node): add web3js wrapper package --- .github/workflows/node-build.yml | 10 +- .github/workflows/node-lint.yml | 2 + .github/workflows/release-node.yml | 9 +- README.md | 5 +- crates/node-litesvm-web3js/.gitignore | 3 + crates/node-litesvm-web3js/README.md | 57 ++ crates/node-litesvm-web3js/package.json | 37 ++ crates/node-litesvm-web3js/rome.json | 12 + crates/node-litesvm-web3js/src/index.ts | 544 ++++++++++++++++++ .../tests/web3jsSmoke.test.ts | 136 +++++ crates/node-litesvm-web3js/tsconfig.json | 19 + package.json | 4 + yarn.lock | 96 +++- 13 files changed, 922 insertions(+), 12 deletions(-) create mode 100644 crates/node-litesvm-web3js/.gitignore create mode 100644 crates/node-litesvm-web3js/README.md create mode 100644 crates/node-litesvm-web3js/package.json create mode 100644 crates/node-litesvm-web3js/rome.json create mode 100644 crates/node-litesvm-web3js/src/index.ts create mode 100644 crates/node-litesvm-web3js/tests/web3jsSmoke.test.ts create mode 100644 crates/node-litesvm-web3js/tsconfig.json diff --git a/.github/workflows/node-build.yml b/.github/workflows/node-build.yml index 20a426f9c..7374e056d 100644 --- a/.github/workflows/node-build.yml +++ b/.github/workflows/node-build.yml @@ -41,6 +41,8 @@ jobs: run: corepack enable && yarn install --immutable - name: Lint Kit wrapper run: yarn workspace litesvm lint + - name: Lint Web3.js wrapper + run: yarn workspace litesvm-web3js lint - name: Cargo fmt run: cargo fmt --all -- --check - name: Clippy core @@ -159,11 +161,13 @@ jobs: - name: List bindings run: ls -R crates/node-litesvm-core/litesvm shell: bash - - name: Test core and Kit wrapper + - name: Test core and wrappers run: | yarn workspace litesvm-core test yarn workspace litesvm build yarn workspace litesvm test + yarn workspace litesvm-web3js build + yarn workspace litesvm-web3js test test-linux-binding: name: Test ${{ matrix.target }} - node@${{ matrix.node }} @@ -220,9 +224,9 @@ jobs: - name: List bindings run: ls -R crates/node-litesvm-core/litesvm shell: bash - - name: Test core and Kit wrapper + - name: Test core and wrappers uses: addnab/docker-run-action@v3 with: image: ${{ steps.docker.outputs.IMAGE }} options: -v ${{ github.workspace }}:${{ github.workspace }} -w ${{ github.workspace }} --platform ${{ steps.docker.outputs.PLATFORM }} -e RUST_MIN_STACK=8388608 - run: corepack enable && yarn workspace litesvm-core test && yarn workspace litesvm build && yarn workspace litesvm test + run: corepack enable && yarn workspace litesvm-core test && yarn workspace litesvm build && yarn workspace litesvm test && yarn workspace litesvm-web3js build && yarn workspace litesvm-web3js test diff --git a/.github/workflows/node-lint.yml b/.github/workflows/node-lint.yml index 78d20ffab..626b839c9 100644 --- a/.github/workflows/node-lint.yml +++ b/.github/workflows/node-lint.yml @@ -32,3 +32,5 @@ jobs: run: yarn workspace litesvm-core lint - name: Lint Kit wrapper run: yarn workspace litesvm lint:js + - name: Lint Web3.js wrapper + run: yarn workspace litesvm-web3js lint diff --git a/.github/workflows/release-node.yml b/.github/workflows/release-node.yml index ee7770a3e..d2d9873ce 100644 --- a/.github/workflows/release-node.yml +++ b/.github/workflows/release-node.yml @@ -30,16 +30,19 @@ jobs: path: crates/node-litesvm-core/artifacts - name: Move native artifacts run: yarn workspace litesvm-core artifacts - - name: Build Kit wrapper - run: yarn workspace litesvm build + - name: Build wrappers + run: | + yarn workspace litesvm build + yarn workspace litesvm-web3js build - name: List native packages run: ls -R crates/node-litesvm-core/npm - - name: Publish core and Kit wrapper + - name: Publish core and wrappers run: | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc TAG=latest [[ "$GITHUB_REF_NAME" == *-rc.* ]] && TAG=next yarn workspace litesvm-core npm publish --access public --tag "$TAG" yarn workspace litesvm npm publish --access public --tag "$TAG" + yarn workspace litesvm-web3js npm publish --access public --tag "$TAG" env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index 3dc255ca8..9942e4aa2 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,10 @@ See the [anchor-litesvm testing guide](https://www.litesvm.com/docs/additional-c - Full documentation: [litesvm.com](https://litesvm.com) - Full API reference: [docs.rs/litesvm](https://docs.rs/litesvm/latest/litesvm/) -- Node.js wrapper: [`litesvm` on npm](https://www.npmjs.com/package/litesvm) — see [`crates/node-litesvm`](crates/node-litesvm) for its README and tutorial +- Node.js packages: + - [`litesvm`](https://www.npmjs.com/package/litesvm) provides the `@solana/kit` wrapper. + - [`litesvm-web3js`](https://www.npmjs.com/package/litesvm-web3js) provides the `@solana/web3.js` v3 wrapper. + - [`litesvm-core`](https://www.npmjs.com/package/litesvm-core) exposes the raw NAPI bindings used by both wrappers. ### 🛠️ Developing litesvm diff --git a/crates/node-litesvm-web3js/.gitignore b/crates/node-litesvm-web3js/.gitignore new file mode 100644 index 000000000..f4e2c6d6b --- /dev/null +++ b/crates/node-litesvm-web3js/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +*.tsbuildinfo diff --git a/crates/node-litesvm-web3js/README.md b/crates/node-litesvm-web3js/README.md new file mode 100644 index 000000000..d091209d8 --- /dev/null +++ b/crates/node-litesvm-web3js/README.md @@ -0,0 +1,57 @@ +# LiteSVM for Web3.js + +`litesvm-web3js` provides a Web3.js v3 interface for +[LiteSVM](https://github.com/LiteSVM/litesvm). It uses `litesvm-core` for native +execution and `@solana/web3.js@3.0.0-rc.2` for addresses, accounts, and +transactions. + +```sh +yarn add litesvm-web3js @solana/web3.js@3.0.0-rc.2 +``` + +Use the `litesvm` package instead when building transactions with +`@solana/kit`. + +## Example + +```ts +import { + FailedTransactionMetadata, + LiteSVM, +} from "litesvm-web3js"; +import { + Keypair, + LAMPORTS_PER_SOL, + SystemProgram, + Transaction, +} from "@solana/web3.js"; + +const svm = new LiteSVM(); +const payer = await Keypair.generate(); +const recipient = await Keypair.generate(); + +svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL)); + +const transaction = new Transaction({ + feePayer: payer.publicKey, + blockhash: svm.latestBlockhash(), + lastValidBlockHeight: 0n, +}).add( + SystemProgram.transfer({ + fromPubkey: payer.publicKey, + toPubkey: recipient.publicKey, + lamports: 1_000_000n, + }), +); + +await transaction.sign(payer); +const result = await svm.sendTransaction(transaction); +if (result instanceof FailedTransactionMetadata) { + throw new Error(`Transaction failed: ${result.err()}`); +} +``` + +Web3.js v3 signing and legacy transaction serialization are asynchronous, so +`sendTransaction` and `simulateTransaction` return promises in this package. +Account data uses `Uint8Array`; lamports, rent epochs, account space, slots, and +block heights use `bigint`. diff --git a/crates/node-litesvm-web3js/package.json b/crates/node-litesvm-web3js/package.json new file mode 100644 index 000000000..48a929f8d --- /dev/null +++ b/crates/node-litesvm-web3js/package.json @@ -0,0 +1,37 @@ +{ + "name": "litesvm-web3js", + "version": "1.3.0", + "description": "LiteSVM wrapper for @solana/web3.js v3", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/" + ], + "license": "MIT", + "repository": "https://github.com/LiteSVM/litesvm", + "dependencies": { + "@solana/web3.js": "3.0.0-rc.2", + "litesvm-core": "workspace:^" + }, + "devDependencies": { + "@types/node": "22.19.21", + "rome": "^12.1.3", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">= 20" + }, + "scripts": { + "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"", + "build": "yarn clean && tsc --build", + "prepublishOnly": "yarn build", + "fmt": "rome format --write src/ tests/ tsconfig.json package.json", + "lint": "rome check src/ tests/ tsconfig.json package.json", + "test": "node -r ts-node/register -r tsconfig-paths/register --test --test-concurrency=1 tests/*.test.ts", + "bumpPatch": "yarn version patch", + "bumpMinor": "yarn version minor", + "bumpMajor": "yarn version major" + } +} diff --git a/crates/node-litesvm-web3js/rome.json b/crates/node-litesvm-web3js/rome.json new file mode 100644 index 000000000..b946f01c1 --- /dev/null +++ b/crates/node-litesvm-web3js/rome.json @@ -0,0 +1,12 @@ +{ + "$schema": "./node_modules/rome/configuration_schema.json", + "organizeImports": { + "enabled": false + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } +} diff --git a/crates/node-litesvm-web3js/src/index.ts b/crates/node-litesvm-web3js/src/index.ts new file mode 100644 index 000000000..7414a894b --- /dev/null +++ b/crates/node-litesvm-web3js/src/index.ts @@ -0,0 +1,544 @@ +import { + Account, + AddressAndAccount, + Clock, + ComputeBudget, + EpochRewards, + EpochSchedule, + FailedTransactionMetadata, + FeatureSet, + LiteSvm as LiteSVMInner, + Rent, + SimulatedTransactionInfo as SimulatedTransactionInfoInner, + SlotHash, + SlotHistory, + StakeHistory, + TransactionMetadata, +} from "litesvm-core"; +export { + Account, + Clock, + ComputeBudget, + EpochRewards, + EpochSchedule, + FailedTransactionMetadata, + FeatureSet, + InnerInstruction, + Rent, + SlotHash, + SlotHistory, + SlotHistoryCheck, + StakeHistory, + StakeHistoryEntry, + TransactionMetadata, + TransactionReturnData, +} from "litesvm-core"; +import { + AccountInfo, + Address, + Blockhash, + Transaction, + VersionedTransaction, +} from "@solana/web3.js"; + +export type AccountInfoBytes = Readonly< + AccountInfo & { space: bigint } +>; + +function toAccountInfo(acc: Account): AccountInfoBytes { + const data = acc.data(); + const owner = new Address(acc.owner()); + return { + executable: acc.executable(), + owner, + lamports: acc.lamports(), + data, + rentEpoch: acc.rentEpoch(), + space: BigInt(data.length), + }; +} + +function fromAccountInfo(acc: AccountInfoBytes): Account { + return new Account( + acc.lamports, + acc.data, + acc.owner.toBytes(), + acc.executable, + acc.rentEpoch, + ); +} + +function convertAddressAndAccount(val: AddressAndAccount): [Address, Account] { + return [new Address(val.address), val.account()]; +} + +async function serializeTransaction( + tx: Transaction | VersionedTransaction, + verifySignatures: boolean, +): Promise { + return tx instanceof Transaction + ? tx.serialize({ + requireAllSignatures: true, + verifySignatures, + }) + : tx.serialize(); +} + +export class SimulatedTransactionInfo { + constructor(inner: SimulatedTransactionInfoInner) { + this.inner = inner; + } + private inner: SimulatedTransactionInfoInner; + meta(): TransactionMetadata { + return this.inner.meta(); + } + postAccounts(): [Address, Account][] { + return this.inner.postAccounts().map(convertAddressAndAccount); + } +} + +/** + * The main class in the litesvm library. + * + * Use this to send transactions, query accounts and configure the runtime. + */ +export class LiteSVM { + /** Create a new LiteSVM instance with standard functionality enabled */ + constructor() { + const inner = new LiteSVMInner(); + this.inner = inner; + } + private inner: LiteSVMInner; + + /** Create a new LiteSVM instance with minimal functionality enabled */ + static default(): LiteSVM { + const svm = new LiteSVM(); + const inner = LiteSVMInner.default(); + svm.inner = inner; + return svm; + } + + /** + * Set the compute budget + * @param budget - The new compute budget + * @returns The modified LiteSVM instance + */ + withComputeBudget(budget: ComputeBudget): LiteSVM { + this.inner.setComputeBudget(budget); + return this; + } + + /** + * Enable or disable sigverify + * @param sigverify - if false, transaction signatures will not be checked. + * @returns The modified LiteSVM instance + */ + withSigverify(sigverify: boolean): LiteSVM { + this.inner.setSigverify(sigverify); + return this; + } + + /** + * Enables or disables transaction blockhash checking. + * @param check - If false, the blockhash check will be skipped + * @returns The modified LiteSVM instance + */ + withBlockhashCheck(check: boolean): LiteSVM { + this.inner.setBlockhashCheck(check); + return this; + } + + /** + * Sets up the standard sysvars. + * @returns The modified LiteSVM instance + */ + withSysvars(): LiteSVM { + this.inner.setSysvars(); + return this; + } + + /** + * Set the FeatureSet used by the VM instance. + * @param featureSet The FeatureSet to use. + * @returns The modified LiteSVM instance + */ + withFeatureSet(featureSet: FeatureSet): LiteSVM { + this.inner.setFeatureSet(featureSet); + return this; + } + + /** + * Adds the standard builtin programs. Use `withFeatureSet` beforehand to change change what builtins are added. + * @returns The modified LiteSVM instance + */ + withBuiltins(): LiteSVM { + this.inner.setBuiltins(); + return this; + } + + /** + * Changes the initial lamports in LiteSVM's airdrop account. + * @param lamports - The number of lamports to set in the airdrop account + * @returns The modified LiteSVM instance + */ + withLamports(lamports: bigint): LiteSVM { + this.inner.setLamports(lamports); + return this; + } + + /** + * Adds the standard SPL programs. + * @returns The modified LiteSVM instance + */ + withDefaultPrograms(): LiteSVM { + this.inner.setDefaultPrograms(); + return this; + } + + /** + * Adds the native mint accounts for SPL Token and Token-2022, if the programs are loaded. + * @returns The modified LiteSVM instance + */ + withNativeMints(): LiteSVM { + this.inner.withNativeMints(); + return this; + } + + /** + * Changes the capacity of the transaction history. + * @param capacity - How many transactions to store in history. + * Set this to 0 to disable transaction history and allow duplicate transactions. + * @returns The modified LiteSVM instance + */ + withTransactionHistory(capacity: bigint): LiteSVM { + this.inner.setTransactionHistory(capacity); + return this; + } + + /** + * Set a limit for transaction logs, beyond which they will be truncated. + * @param limit - The limit in bytes. If null, no limit is enforced. + * @returns The modified LiteSVM instance + */ + withLogBytesLimit(limit?: bigint): LiteSVM { + this.inner.setLogBytesLimit(limit); + return this; + } + + /** + * Adds the standard precompiles. Use `withFeatureSet` beforehand to change change what builtins are added. + * @returns The modified LiteSVM instance + */ + withPrecompiles(): LiteSVM { + this.inner.setPrecompiles(); + return this; + } + + /** + * Calculates the minimum balance required to make an account with specified data length rent exempt. + * @param dataLen - The number of bytes in the account. + * @returns The required balance in lamports + */ + minimumBalanceForRentExemption(dataLen: bigint): bigint { + return this.inner.minimumBalanceForRentExemption(dataLen); + } + + /** + * Return the account at the given address. + * If the account is not found, None is returned. + * @param address - The account address to look up. + * @returns The account object, if the account exists. + */ + getAccount(address: Address): AccountInfoBytes | null { + const inner = this.inner.getAccount(address.toBytes()); + return inner === null ? null : toAccountInfo(inner); + } + + /** + * Return all accounts owned by the given program, together with their addresses. + * @param programAddress - The owner program address to look up. + * @returns The list of accounts owned by the program. + */ + getProgramAccounts(programAddress: Address): [Address, AccountInfoBytes][] { + return this.inner + .getProgramAccounts(programAddress.toBytes()) + .map((addressAndAccount: AddressAndAccount) => [ + new Address(addressAndAccount.address), + toAccountInfo(addressAndAccount.account()), + ]); + } + + /** + * Create or overwrite an account, subverting normal runtime checks. + * + * This method exists to make it easier to set up artificial situations + * that would be difficult to replicate by sending individual transactions. + * Beware that it can be used to create states that would not be reachable + * by sending transactions! + * + * @param address - The address to write to. + * @param account - The account object to write. + */ + setAccount(address: Address, account: AccountInfoBytes) { + this.inner.setAccount(address.toBytes(), fromAccountInfo(account)); + } + + /** + * Gets the balance of the provided account address. + * @param address - The account address. + * @returns The account's balance in lamports. + */ + getBalance(address: Address): bigint | null { + return this.inner.getBalance(address.toBytes()); + } + + /** + * Gets the latest blockhash. + * Since LiteSVM doesn't have blocks, this is an arbitrary value controlled by LiteSVM + * @returns The designated latest blockhash. + */ + latestBlockhash(): Blockhash { + return this.inner.latestBlockhash() as Blockhash; + } + + /** + * Gets a transaction from the transaction history. + * @param signature - The transaction signature bytes + * @returns The transaction, if it is found in the history. + */ + getTransaction( + signature: Uint8Array, + ): TransactionMetadata | FailedTransactionMetadata | null { + return this.inner.getTransaction(signature); + } + + /** + * Airdrops the lamport amount specified to the given address. + * @param address The airdrop recipient. + * @param lamports - The amount to airdrop. + * @returns The transaction result. + */ + airdrop( + address: Address, + lamports: bigint, + ): TransactionMetadata | FailedTransactionMetadata | null { + return this.inner.airdrop(address.toBytes(), lamports); + } + + /** + * Adds an SBF program to the test environment from the file specified. + * @param programId - The program ID. + * @param path - The path to the .so file. + */ + addProgramFromFile(programId: Address, path: string) { + return this.inner.addProgramFromFile(programId.toBytes(), path); + } + + /** + * Adds am SBF program to the test environment. + * @param programId - The program ID. + * @param programBytes - The raw bytes of the compiled program. + */ + addProgram(programId: Address, programBytes: Uint8Array) { + return this.inner.addProgram(programId.toBytes(), programBytes); + } + + /** + * Adds an SBF program with a specific loader. + * @param programId - The program ID. + * @param programBytes - The raw bytes of the compiled program. + * @param loaderId - The loader program ID. + */ + addProgramWithLoader( + programId: Address, + programBytes: Uint8Array, + loaderId: Address, + ) { + return this.inner.addProgramWithLoader( + programId.toBytes(), + programBytes, + loaderId.toBytes(), + ); + } + + /** + * Processes a transaction and returns the result. + * @param tx - The transaction to send. + * @returns TransactionMetadata if the transaction succeeds, else FailedTransactionMetadata + */ + async sendTransaction( + tx: Transaction | VersionedTransaction, + ): Promise { + const internal = this.inner; + const serialized = await serializeTransaction(tx, internal.getSigverify()); + + if (tx instanceof Transaction) { + return internal.sendLegacyTransaction(serialized); + } + return internal.sendVersionedTransaction(serialized); + } + + /** + * Simulates a transaction + * @param tx The transaction to simulate + * @returns SimulatedTransactionInfo if simulation succeeds, else FailedTransactionMetadata + */ + async simulateTransaction( + tx: Transaction | VersionedTransaction, + ): Promise { + const internal = this.inner; + const serialized = await serializeTransaction(tx, internal.getSigverify()); + const inner = + tx instanceof Transaction + ? internal.simulateLegacyTransaction(serialized) + : internal.simulateVersionedTransaction(serialized); + return inner instanceof FailedTransactionMetadata + ? inner + : new SimulatedTransactionInfo(inner); + } + + /** + * Expires the current blockhash. + * The return value of `latestBlockhash()` will be different after calling this. + */ + expireBlockhash() { + this.inner.expireBlockhash(); + } + + /** + * Warps the clock to the specified slot. This is a convenience wrapper + * around `setClock()`. + * @param slot - The new slot. + */ + warpToSlot(slot: bigint) { + this.inner.warpToSlot(slot); + } + + /** + * Get the cluster clock. + * @returns the clock object. + */ + getClock(): Clock { + return this.inner.getClock(); + } + + /** + * Overwrite the clock sysvar. + * @param clock - The clock object. + */ + setClock(clock: Clock) { + this.inner.setClock(clock); + } + + /** + * Get the EpochRewards sysvar. + * @returns the EpochRewards object. + */ + getEpochRewards(): EpochRewards { + return this.inner.getEpochRewards(); + } + + /** + * Overwrite the EpochRewards sysvar. + * @param rewards - The EpochRewards object. + */ + setEpochRewards(rewards: EpochRewards) { + this.inner.setEpochRewards(rewards); + } + + /** + * Get the EpochSchedule sysvar. + * @returns the EpochSchedule object. + */ + getEpochSchedule(): EpochSchedule { + return this.inner.getEpochSchedule(); + } + + /** + * Overwrite the EpochSchedule sysvar. + * @param schedule - The EpochSchedule object. + */ + setEpochSchedule(schedule: EpochSchedule) { + this.inner.setEpochSchedule(schedule); + } + + /** + * Get the last restart slot sysvar. + * @returns the last restart slot. + */ + getLastRestartSlot(): bigint { + return this.inner.getLastRestartSlot(); + } + + /** + * Overwrite the last restart slot sysvar. + * @param slot - The last restart slot. + */ + setLastRestartSlot(slot: bigint) { + this.inner.setLastRestartSlot(slot); + } + + /** + * Get the cluster rent. + * @returns The rent object. + */ + getRent(): Rent { + return this.inner.getRent(); + } + + /** + * Overwrite the rent sysvar. + * @param rent - The new rent object. + */ + setRent(rent: Rent) { + this.inner.setRent(rent); + } + + /** + * Get the SlotHashes sysvar. + * @returns The SlotHash array. + */ + getSlotHashes(): SlotHash[] { + return this.inner.getSlotHashes(); + } + + /** + * Overwrite the SlotHashes sysvar. + * @param hashes - The SlotHash array. + */ + setSlotHashes(hashes: SlotHash[]) { + this.inner.setSlotHashes(hashes); + } + + /** + * Get the SlotHistory sysvar. + * @returns The SlotHistory object. + */ + getSlotHistory(): SlotHistory { + return this.inner.getSlotHistory(); + } + + /** + * Overwrite the SlotHistory sysvar. + * @param history - The SlotHistory object + */ + setSlotHistory(history: SlotHistory) { + this.inner.setSlotHistory(history); + } + + /** + * Get the StakeHistory sysvar. + * @returns The StakeHistory object. + */ + getStakeHistory(): StakeHistory { + return this.inner.getStakeHistory(); + } + + /** + * Overwrite the StakeHistory sysvar. + * @param history - The StakeHistory object + */ + setStakeHistory(history: StakeHistory) { + this.inner.setStakeHistory(history); + } +} diff --git a/crates/node-litesvm-web3js/tests/web3jsSmoke.test.ts b/crates/node-litesvm-web3js/tests/web3jsSmoke.test.ts new file mode 100644 index 000000000..901993d73 --- /dev/null +++ b/crates/node-litesvm-web3js/tests/web3jsSmoke.test.ts @@ -0,0 +1,136 @@ +import { + FailedTransactionMetadata, + LiteSVM, + TransactionMetadata, + type AccountInfoBytes, +} from "litesvm-web3js"; +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { + Address, + Keypair, + LAMPORTS_PER_SOL, + SystemProgram, + Transaction, + TransactionMessage, + VersionedTransaction, +} from "@solana/web3.js"; + +test("web3.js legacy transactions can simulate, send, and read state", async () => { + const svm = new LiteSVM(); + const [payer, recipientKeypair, dataKeypair] = await Promise.all([ + Keypair.generate(), + Keypair.generate(), + Keypair.generate(), + ]); + const recipient = recipientKeypair.publicKey; + + const airdropResult = svm.airdrop( + payer.publicKey, + BigInt(LAMPORTS_PER_SOL), + ); + assert(!(airdropResult instanceof FailedTransactionMetadata)); + + const transferLamports = 1_000_000n; + const transaction = new Transaction({ + feePayer: payer.publicKey, + blockhash: svm.latestBlockhash(), + lastValidBlockHeight: 0n, + }).add( + SystemProgram.transfer({ + fromPubkey: payer.publicKey, + toPubkey: recipient, + lamports: transferLamports, + }), + ); + await transaction.sign(payer); + + const simulation = await svm.simulateTransaction(transaction); + assert(!(simulation instanceof FailedTransactionMetadata)); + + const result = await svm.sendTransaction(transaction); + assert(result instanceof TransactionMetadata); + assert.strictEqual(svm.getBalance(recipient), transferLamports); + assert(transaction.signature !== null); + assert(svm.getTransaction(transaction.signature) instanceof TransactionMetadata); + + const recipientAccount = svm.getAccount(recipient); + assert(recipientAccount !== null); + assert(recipientAccount.owner instanceof Address); + assert.equal(recipientAccount.space, 0n); + + const account: AccountInfoBytes = { + executable: false, + owner: Address.default, + lamports: BigInt(LAMPORTS_PER_SOL), + data: new Uint8Array([1, 2, 3]), + rentEpoch: 0n, + space: 3n, + }; + svm.setAccount(dataKeypair.publicKey, account); + assert.deepStrictEqual(svm.getAccount(dataKeypair.publicKey)?.data, account.data); + const programAccounts = svm.getProgramAccounts(Address.default); + assert( + programAccounts.some( + ([address, programAccount]) => + address.equals(dataKeypair.publicKey) && + programAccount.space === account.space, + ), + ); +}); + +test("web3.js v0 transactions use the versioned native path", async () => { + const svm = new LiteSVM(); + const [payer, recipient] = await Promise.all([ + Keypair.generate(), + Keypair.generate(), + ]); + svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL)); + + const transferLamports = 2_000_000n; + const message = new TransactionMessage({ + payerKey: payer.publicKey, + recentBlockhash: svm.latestBlockhash(), + instructions: [ + SystemProgram.transfer({ + fromPubkey: payer.publicKey, + toPubkey: recipient.publicKey, + lamports: transferLamports, + }), + ], + }).compileToV0Message(); + const transaction = new VersionedTransaction(message); + await transaction.sign([payer]); + + const result = await svm.sendTransaction(transaction); + assert(result instanceof TransactionMetadata); + assert.equal(svm.getBalance(recipient.publicKey), transferLamports); + assert(svm.getTransaction(transaction.signatures[0]) instanceof TransactionMetadata); +}); + +test("disabled signature verification accepts an invalid legacy signature", async () => { + const svm = new LiteSVM().withSigverify(false); + const [payer, recipient] = await Promise.all([ + Keypair.generate(), + Keypair.generate(), + ]); + svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL)); + + const transaction = new Transaction({ + feePayer: payer.publicKey, + blockhash: svm.latestBlockhash(), + lastValidBlockHeight: 0n, + }).add( + SystemProgram.transfer({ + fromPubkey: payer.publicKey, + toPubkey: recipient.publicKey, + lamports: 1_000_000n, + }), + ); + await transaction.sign(payer); + transaction.signatures[0].signature = new Uint8Array(64).fill(42); + + const result = await svm.sendTransaction(transaction); + assert(result instanceof TransactionMetadata); + assert.equal(svm.getBalance(recipient.publicKey), 1_000_000n); +}); diff --git a/crates/node-litesvm-web3js/tsconfig.json b/crates/node-litesvm-web3js/tsconfig.json new file mode 100644 index 000000000..8a9f87420 --- /dev/null +++ b/crates/node-litesvm-web3js/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "declaration": true, + "module": "CommonJS", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "target": "es2020", + "noImplicitAny": true, + "sourceMap": true, + "outDir": "dist", + "baseUrl": "./src", + "paths": { + "litesvm-web3js": ["./"] + }, + "resolveJsonModule": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/package.json b/package.json index d5cfe560e..89bb8fdeb 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,11 @@ "crates/node-litesvm-web3js" ], "scripts": { + "build": "yarn build:core && yarn workspace litesvm build && yarn workspace litesvm-web3js build", + "build:debug": "yarn build:core:debug && yarn workspace litesvm build && yarn workspace litesvm-web3js build", "build:core": "yarn workspace litesvm-core build", "build:core:debug": "yarn workspace litesvm-core build:debug", + "test": "yarn test:core && yarn workspace litesvm test && yarn workspace litesvm-web3js test", + "lint": "yarn workspace litesvm-core lint && yarn workspace litesvm lint && yarn workspace litesvm-web3js lint" } } diff --git a/yarn.lock b/yarn.lock index 6927dee47..364d41551 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1416,6 +1416,22 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:^1.4.2": + version: 1.9.7 + resolution: "@noble/curves@npm:1.9.7" + dependencies: + "@noble/hashes": "npm:1.8.0" + checksum: 10c0/150014751ebe8ca06a8654ca2525108452ea9ee0be23430332769f06808cddabfe84f248b6dbf836916bc869c27c2092957eec62c7506d68a1ed0a624017c2a3 + languageName: node + linkType: hard + +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.4.0": + version: 1.8.0 + resolution: "@noble/hashes@npm:1.8.0" + checksum: 10c0/06a0b52c81a6fa7f04d67762e08b2c476a00285858150caeaaff4037356dd5e119f45b2a530f638b77a5eeca013168ec1b655db41bae3236cb2e9d511484fc77 + languageName: node + linkType: hard + "@npmcli/agent@npm:^3.0.0": version: 3.0.0 resolution: "@npmcli/agent@npm:3.0.0" @@ -1869,6 +1885,42 @@ __metadata: languageName: node linkType: hard +"@solana-program/address-lookup-table@npm:^0.11.0": + version: 0.11.0 + resolution: "@solana-program/address-lookup-table@npm:0.11.0" + peerDependencies: + "@solana/kit": ^6.0 + checksum: 10c0/28271e452a9e6caa1cb5ad0365cf7f4a193920fd3fd3f5261fa655fb84e7b6a3d911e602125ad5718cdb4a8d6980e09d54f9f97016423b9d13051c3dde332cac + languageName: node + linkType: hard + +"@solana-program/compute-budget@npm:^0.15.0": + version: 0.15.0 + resolution: "@solana-program/compute-budget@npm:0.15.0" + peerDependencies: + "@solana/kit": ^6.3.0 + checksum: 10c0/b2c29063932dee2f5da77d63ab7db95834a0c6fc6ff79f99951093accebdd942d14a6db5bd97412a0e8b58f8b4e0ed20c084e60389ce6751da39afdd651a9d2b + languageName: node + linkType: hard + +"@solana-program/loader-v3@npm:^0.3.0": + version: 0.3.0 + resolution: "@solana-program/loader-v3@npm:0.3.0" + peerDependencies: + "@solana/kit": ^6.0.0 + checksum: 10c0/b1acdf7591f96b41e99b3dbd4b64eb77887a4e4c954e35046bc89402acc8c76f7938e8f9be7decb6aaf6bbb9c6c04fce8b3d761f5803d6cddb5294b69d9ee528 + languageName: node + linkType: hard + +"@solana-program/stake@npm:^0.6.1": + version: 0.6.1 + resolution: "@solana-program/stake@npm:0.6.1" + peerDependencies: + "@solana/kit": ^6.0 + checksum: 10c0/8f4fa7e54f2509ee968c11057f001f1123e1a94b6820f9a9d1ba2894db4352e3078f3321d0ea95365c093546e4989197e0daff80a7a4ba4d193a2f5a4124863e + languageName: node + linkType: hard + "@solana-program/system@npm:^0.12.0": version: 0.12.0 resolution: "@solana-program/system@npm:0.12.0" @@ -1935,7 +1987,7 @@ __metadata: languageName: node linkType: hard -"@solana/assertions@npm:6.10.0": +"@solana/assertions@npm:6.10.0, @solana/assertions@npm:^6.8.0": version: 6.10.0 resolution: "@solana/assertions@npm:6.10.0" dependencies: @@ -2049,7 +2101,7 @@ __metadata: languageName: node linkType: hard -"@solana/fast-stable-stringify@npm:6.10.0": +"@solana/fast-stable-stringify@npm:6.10.0, @solana/fast-stable-stringify@npm:^6.8.0": version: 6.10.0 resolution: "@solana/fast-stable-stringify@npm:6.10.0" peerDependencies: @@ -2141,7 +2193,7 @@ __metadata: languageName: node linkType: hard -"@solana/kit@npm:^6.10.0": +"@solana/kit@npm:^6.10.0, @solana/kit@npm:^6.8.0": version: 6.10.0 resolution: "@solana/kit@npm:6.10.0" dependencies: @@ -2347,7 +2399,7 @@ __metadata: languageName: node linkType: hard -"@solana/rpc-spec-types@npm:6.10.0": +"@solana/rpc-spec-types@npm:6.10.0, @solana/rpc-spec-types@npm:^6.8.0": version: 6.10.0 resolution: "@solana/rpc-spec-types@npm:6.10.0" peerDependencies: @@ -2531,7 +2583,7 @@ __metadata: languageName: node linkType: hard -"@solana/signers@npm:6.10.0": +"@solana/signers@npm:6.10.0, @solana/signers@npm:^6.8.0": version: 6.10.0 resolution: "@solana/signers@npm:6.10.0" dependencies: @@ -2657,6 +2709,26 @@ __metadata: languageName: node linkType: hard +"@solana/web3.js@npm:3.0.0-rc.2": + version: 3.0.0-rc.2 + resolution: "@solana/web3.js@npm:3.0.0-rc.2" + dependencies: + "@noble/curves": "npm:^1.4.2" + "@noble/hashes": "npm:^1.4.0" + "@solana-program/address-lookup-table": "npm:^0.11.0" + "@solana-program/compute-budget": "npm:^0.15.0" + "@solana-program/loader-v3": "npm:^0.3.0" + "@solana-program/stake": "npm:^0.6.1" + "@solana-program/system": "npm:^0.12.0" + "@solana/assertions": "npm:^6.8.0" + "@solana/fast-stable-stringify": "npm:^6.8.0" + "@solana/kit": "npm:^6.8.0" + "@solana/rpc-spec-types": "npm:^6.8.0" + "@solana/signers": "npm:^6.8.0" + checksum: 10c0/99bf934c5b83470bb08fcf15aedd3c9b4cbb04d8cad2c5a28627fb68bb1157ef5d5e69cfe5287afe55613fbef7d5d0b852a53b76b89318a0df4a3335cab04c65 + languageName: node + linkType: hard + "@tsconfig/node10@npm:^1.0.7": version: 1.0.11 resolution: "@tsconfig/node10@npm:1.0.11" @@ -3981,6 +4053,20 @@ __metadata: languageName: unknown linkType: soft +"litesvm-web3js@workspace:crates/node-litesvm-web3js": + version: 0.0.0-use.local + resolution: "litesvm-web3js@workspace:crates/node-litesvm-web3js" + dependencies: + "@solana/web3.js": "npm:3.0.0-rc.2" + "@types/node": "npm:22.19.21" + litesvm-core: "workspace:^" + rome: "npm:^12.1.3" + ts-node: "npm:^10.9.2" + tsconfig-paths: "npm:^4.2.0" + typescript: "npm:^5.9.3" + languageName: unknown + linkType: soft + "litesvm@workspace:crates/node-litesvm": version: 0.0.0-use.local resolution: "litesvm@workspace:crates/node-litesvm" From 34df74f931c94ae18f0540b6ba44f1ad0778a8b8 Mon Sep 17 00:00:00 2001 From: Chii Yuen Date: Sat, 1 Aug 2026 03:25:06 +0800 Subject: [PATCH 4/7] chore: remove duplicate lint workflows --- .github/workflows/node-build.yml | 26 -------------------------- .github/workflows/node-lint.yml | 4 +++- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/.github/workflows/node-build.yml b/.github/workflows/node-build.yml index 7374e056d..d75cfb9cb 100644 --- a/.github/workflows/node-build.yml +++ b/.github/workflows/node-build.yml @@ -22,32 +22,6 @@ on: - crates/node-litesvm/docs/** jobs: - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Setup node - uses: actions/setup-node@v6 - with: - node-version: 22 - cache: yarn - cache-dependency-path: yarn.lock - - name: Install Rust - uses: dtolnay/rust-toolchain@nightly - with: - components: clippy, rustfmt - - name: Install dependencies - run: corepack enable && yarn install --immutable - - name: Lint Kit wrapper - run: yarn workspace litesvm lint - - name: Lint Web3.js wrapper - run: yarn workspace litesvm-web3js lint - - name: Cargo fmt - run: cargo fmt --all -- --check - - name: Clippy core - run: yarn workspace litesvm-core lint - build: strategy: fail-fast: false diff --git a/.github/workflows/node-lint.yml b/.github/workflows/node-lint.yml index 626b839c9..71039cfe9 100644 --- a/.github/workflows/node-lint.yml +++ b/.github/workflows/node-lint.yml @@ -25,9 +25,11 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - components: clippy + components: clippy, rustfmt - name: Install dependencies run: corepack enable && yarn install --immutable + - name: Check Rust formatting + run: cargo fmt --all -- --check - name: Lint Rust core run: yarn workspace litesvm-core lint - name: Lint Kit wrapper From 28833431afb1fb2a7e2fd6ecff117b8087402a2a Mon Sep 17 00:00:00 2001 From: Chii Yuen Date: Sat, 1 Aug 2026 03:34:16 +0800 Subject: [PATCH 5/7] chore: set wrapper SDK as peers --- crates/node-litesvm-web3js/package.json | 5 ++++- crates/node-litesvm/package.json | 5 ++++- yarn.lock | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/node-litesvm-web3js/package.json b/crates/node-litesvm-web3js/package.json index 48a929f8d..f88006a51 100644 --- a/crates/node-litesvm-web3js/package.json +++ b/crates/node-litesvm-web3js/package.json @@ -10,10 +10,13 @@ "license": "MIT", "repository": "https://github.com/LiteSVM/litesvm", "dependencies": { - "@solana/web3.js": "3.0.0-rc.2", "litesvm-core": "workspace:^" }, + "peerDependencies": { + "@solana/web3.js": ">=3.0.0" + }, "devDependencies": { + "@solana/web3.js": "3.0.0-rc.2", "@types/node": "22.19.21", "rome": "^12.1.3", "ts-node": "^10.9.2", diff --git a/crates/node-litesvm/package.json b/crates/node-litesvm/package.json index 870c44fd8..00f03b6bc 100644 --- a/crates/node-litesvm/package.json +++ b/crates/node-litesvm/package.json @@ -10,11 +10,14 @@ "dependencies": { "@solana-program/system": "^0.12.2", "@solana-program/token": "^0.14.0", - "@solana/kit": "^6.10.0", "litesvm-core": "workspace:^" }, + "peerDependencies": { + "@solana/kit": ">=6.0.0" + }, "devDependencies": { "@algolia/client-search": "^5.55.0", + "@solana/kit": "^6.10.0", "@types/markdown-it": "^14.1.2", "@types/node": "22.19.21", "@types/node-fetch": "^2.6.13", diff --git a/yarn.lock b/yarn.lock index 364d41551..7b357de6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4064,6 +4064,8 @@ __metadata: ts-node: "npm:^10.9.2" tsconfig-paths: "npm:^4.2.0" typescript: "npm:^5.9.3" + peerDependencies: + "@solana/web3.js": ">=3.0.0" languageName: unknown linkType: soft @@ -4091,6 +4093,8 @@ __metadata: typescript: "npm:^5.9.3" vitepress: "npm:^1.6.4" ws: "npm:^8.21.0" + peerDependencies: + "@solana/kit": ">=6.0.0" languageName: unknown linkType: soft From 807bb0ffb65b418743bdcc865b3ce40229d58056 Mon Sep 17 00:00:00 2001 From: Aursen <6496148+Aursen@users.noreply.github.com> Date: Sat, 26 Sep 2026 15:07:12 +0700 Subject: [PATCH 6/7] Fix wrapper snapshots, core version pins and initial npm publishing --- .github/workflows/release-node.yml | 6 +++ crates/node-litesvm-web3js/package.json | 2 +- crates/node-litesvm-web3js/src/index.ts | 40 +++++++++++++++++++ .../tests/snapshot.test.ts | 36 +++++++++++++++++ crates/node-litesvm/package.json | 2 +- yarn.lock | 6 +-- 6 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 crates/node-litesvm-web3js/tests/snapshot.test.ts diff --git a/.github/workflows/release-node.yml b/.github/workflows/release-node.yml index cc9ab9bbe..d6e9c943d 100644 --- a/.github/workflows/release-node.yml +++ b/.github/workflows/release-node.yml @@ -21,6 +21,7 @@ jobs: with: node-version: 22 check-latest: true + registry-url: https://registry.npmjs.org cache: yarn cache-dependency-path: yarn.lock - name: Upgrade npm for trusted publishing @@ -43,6 +44,11 @@ jobs: - name: List native packages run: ls -R crates/node-litesvm-core/npm - name: Publish core and wrappers + env: + # Bootstrap the eight new packages with a granular write token that can + # create packages and bypass 2FA. After configuring trusted publishing + # for litesvm-core, litesvm-web3js and all six platform packages, remove it. + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | TAG=latest [[ "$GITHUB_REF_NAME" == *-rc.* ]] && TAG=next diff --git a/crates/node-litesvm-web3js/package.json b/crates/node-litesvm-web3js/package.json index 08caa9821..23e45e96d 100644 --- a/crates/node-litesvm-web3js/package.json +++ b/crates/node-litesvm-web3js/package.json @@ -13,7 +13,7 @@ "url": "git+https://github.com/LiteSVM/litesvm.git" }, "dependencies": { - "litesvm-core": "workspace:^" + "litesvm-core": "workspace:*" }, "peerDependencies": { "@solana/web3.js": "^3.0.0-rc.2" diff --git a/crates/node-litesvm-web3js/src/index.ts b/crates/node-litesvm-web3js/src/index.ts index 7414a894b..84875b433 100644 --- a/crates/node-litesvm-web3js/src/index.ts +++ b/crates/node-litesvm-web3js/src/index.ts @@ -118,6 +118,46 @@ export class LiteSVM { return svm; } + /** + * Restore a LiteSVM instance from a snapshot file written by {@link saveToFile}. + * @param path - The path of the snapshot file + * @returns A new LiteSVM instance with the saved state + */ + static loadFromFile(path: string): LiteSVM { + const svm = new LiteSVM(); + svm.inner = LiteSVMInner.loadFromFile(path); + return svm; + } + + /** + * Restore a LiteSVM instance from snapshot bytes produced by {@link toBytes}. + * @param bytes - The snapshot bytes + * @returns A new LiteSVM instance with the saved state + */ + static fromBytes(bytes: Uint8Array): LiteSVM { + const svm = new LiteSVM(); + svm.inner = LiteSVMInner.fromBytes(bytes); + return svm; + } + + /** + * Save the full state (accounts, sysvars, feature set, transaction history) + * to a file. Restore it with {@link LiteSVM.loadFromFile}. + * @param path - The path to write the snapshot to + */ + saveToFile(path: string) { + this.inner.saveToFile(path); + } + + /** + * Serialize the full state (accounts, sysvars, feature set, transaction history) + * to bytes. Restore it with {@link LiteSVM.fromBytes}. + * @returns The snapshot bytes + */ + toBytes(): Uint8Array { + return this.inner.toBytes(); + } + /** * Set the compute budget * @param budget - The new compute budget diff --git a/crates/node-litesvm-web3js/tests/snapshot.test.ts b/crates/node-litesvm-web3js/tests/snapshot.test.ts new file mode 100644 index 000000000..b68cf18a4 --- /dev/null +++ b/crates/node-litesvm-web3js/tests/snapshot.test.ts @@ -0,0 +1,36 @@ +import { Keypair } from "@solana/web3.js"; +import { LiteSVM, TransactionMetadata } from "litesvm-web3js"; +import assert from "node:assert/strict"; +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { test } from "node:test"; + +test("web3.js snapshots round trip through bytes and files", async () => { + const svm = new LiteSVM(); + const { publicKey } = await Keypair.generate(); + assert(svm.airdrop(publicKey, 1_000_000n) instanceof TransactionMetadata); + svm.warpToSlot(42n); + const directory = mkdtempSync(join(tmpdir(), "litesvm-web3js-snapshot-")); + try { + const path = join(directory, "state.bin"); + svm.saveToFile(path); + for (const restored of [ + LiteSVM.fromBytes(svm.toBytes()), + LiteSVM.loadFromFile(path), + ]) { + assert(restored instanceof LiteSVM); + assert.deepEqual(restored.getAccount(publicKey), svm.getAccount(publicKey)); + assert.equal(restored.getClock().slot, 42n); + assert.equal(restored.latestBlockhash(), svm.latestBlockhash()); + restored.warpToSlot(43n); + assert.equal(svm.getClock().slot, 42n); + } + } finally { + rmSync(directory, { recursive: true, force: true }); + } + assert.throws( + () => LiteSVM.fromBytes(new Uint8Array([255, 1, 2, 3])), + /Failed to load snapshot/, + ); +}); diff --git a/crates/node-litesvm/package.json b/crates/node-litesvm/package.json index 09de19ab0..2df4d3b11 100644 --- a/crates/node-litesvm/package.json +++ b/crates/node-litesvm/package.json @@ -14,7 +14,7 @@ "dependencies": { "@solana-program/system": "^0.14.0", "@solana-program/token": "^0.16.0", - "litesvm-core": "workspace:^" + "litesvm-core": "workspace:*" }, "peerDependencies": { "@solana/kit": "^8.0.0" diff --git a/yarn.lock b/yarn.lock index 42d55556f..ac2cebe85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4643,7 +4643,7 @@ __metadata: languageName: node linkType: hard -"litesvm-core@workspace:^, litesvm-core@workspace:crates/node-litesvm-core": +"litesvm-core@workspace:*, litesvm-core@workspace:crates/node-litesvm-core": version: 0.0.0-use.local resolution: "litesvm-core@workspace:crates/node-litesvm-core" dependencies: @@ -4657,7 +4657,7 @@ __metadata: dependencies: "@solana/web3.js": "npm:3.0.0-rc.2" "@types/node": "npm:22.20.1" - litesvm-core: "workspace:^" + litesvm-core: "workspace:*" rome: "npm:^12.1.3" ts-node: "npm:^10.9.2" tsconfig-paths: "npm:^4.2.0" @@ -4675,7 +4675,7 @@ __metadata: "@solana-program/token": "npm:^0.16.0" "@solana/kit": "npm:^8.0.0" "@types/node": "npm:22.20.1" - litesvm-core: "workspace:^" + litesvm-core: "workspace:*" rome: "npm:^12.1.3" ts-node: "npm:^10.9.2" tsconfig-paths: "npm:^4.2.0" From c0de53f6f75a53206d628a8d086645f4bb2f4ece Mon Sep 17 00:00:00 2001 From: Aursen <6496148+Aursen@users.noreply.github.com> Date: Sat, 26 Sep 2026 20:49:39 +0700 Subject: [PATCH 7/7] Align simulated account shapes and guard Node release versions --- .github/workflows/release-node.yml | 10 ++++++++++ crates/node-litesvm-web3js/src/index.ts | 13 ++++++------- .../node-litesvm-web3js/tests/web3jsSmoke.test.ts | 8 ++++++++ crates/node-litesvm/README.md | 12 +++++++++++- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release-node.yml b/.github/workflows/release-node.yml index d6e9c943d..5a129307a 100644 --- a/.github/workflows/release-node.yml +++ b/.github/workflows/release-node.yml @@ -24,6 +24,16 @@ jobs: registry-url: https://registry.npmjs.org cache: yarn cache-dependency-path: yarn.lock + - name: Check package versions match the release tag + run: | + node <<'NODE' + const assert = require('node:assert/strict'); + const version = process.env.GITHUB_REF_NAME.replace(/^node-v/, ''); + for (const workspace of require('./package.json').workspaces) { + const pkg = require(`./${workspace}/package.json`); + assert.equal(pkg.version, version, `${pkg.name} must match release version ${version}`); + } + NODE - name: Upgrade npm for trusted publishing run: npm install -g npm@latest - name: Install dependencies diff --git a/crates/node-litesvm-web3js/src/index.ts b/crates/node-litesvm-web3js/src/index.ts index 84875b433..f3daa6912 100644 --- a/crates/node-litesvm-web3js/src/index.ts +++ b/crates/node-litesvm-web3js/src/index.ts @@ -68,8 +68,10 @@ function fromAccountInfo(acc: AccountInfoBytes): Account { ); } -function convertAddressAndAccount(val: AddressAndAccount): [Address, Account] { - return [new Address(val.address), val.account()]; +function convertAddressAndAccount( + val: AddressAndAccount, +): [Address, AccountInfoBytes] { + return [new Address(val.address), toAccountInfo(val.account())]; } async function serializeTransaction( @@ -92,7 +94,7 @@ export class SimulatedTransactionInfo { meta(): TransactionMetadata { return this.inner.meta(); } - postAccounts(): [Address, Account][] { + postAccounts(): [Address, AccountInfoBytes][] { return this.inner.postAccounts().map(convertAddressAndAccount); } } @@ -302,10 +304,7 @@ export class LiteSVM { getProgramAccounts(programAddress: Address): [Address, AccountInfoBytes][] { return this.inner .getProgramAccounts(programAddress.toBytes()) - .map((addressAndAccount: AddressAndAccount) => [ - new Address(addressAndAccount.address), - toAccountInfo(addressAndAccount.account()), - ]); + .map(convertAddressAndAccount); } /** diff --git a/crates/node-litesvm-web3js/tests/web3jsSmoke.test.ts b/crates/node-litesvm-web3js/tests/web3jsSmoke.test.ts index 901993d73..82fc0401e 100644 --- a/crates/node-litesvm-web3js/tests/web3jsSmoke.test.ts +++ b/crates/node-litesvm-web3js/tests/web3jsSmoke.test.ts @@ -47,6 +47,13 @@ test("web3.js legacy transactions can simulate, send, and read state", async () const simulation = await svm.simulateTransaction(transaction); assert(!(simulation instanceof FailedTransactionMetadata)); + const postAccounts: [Address, AccountInfoBytes][] = simulation.postAccounts(); + const simulatedRecipient = postAccounts.find(([address]) => + address.equals(recipient), + )?.[1]; + assert(simulatedRecipient); + assert(simulatedRecipient.owner instanceof Address); + assert.equal(simulatedRecipient.lamports, transferLamports); const result = await svm.sendTransaction(transaction); assert(result instanceof TransactionMetadata); @@ -58,6 +65,7 @@ test("web3.js legacy transactions can simulate, send, and read state", async () assert(recipientAccount !== null); assert(recipientAccount.owner instanceof Address); assert.equal(recipientAccount.space, 0n); + assert.deepEqual(simulatedRecipient, recipientAccount); const account: AccountInfoBytes = { executable: false, diff --git a/crates/node-litesvm/README.md b/crates/node-litesvm/README.md index fceca579e..b3179f9e5 100644 --- a/crates/node-litesvm/README.md +++ b/crates/node-litesvm/README.md @@ -88,4 +88,14 @@ yarn add litesvm Make sure you have Yarn and the Rust toolchain installed. -Then run `yarn` to install deps, run `yarn build` to build the binary and `yarn test` to run the tests. +From the repository root, run: + +```sh +yarn install --immutable +yarn build +yarn test +``` + +The root build compiles the native binary and both wrappers. Running `yarn build` +inside this package only compiles TypeScript; to build the native binary from +there, run `yarn workspace litesvm-core build` first.